@stack-spot/citric-react 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/citric.css +2580 -0
- package/dist/components/Accordion.d.ts +33 -0
- package/dist/components/Accordion.d.ts.map +1 -0
- package/dist/components/Accordion.js +19 -0
- package/dist/components/Accordion.js.map +1 -0
- package/dist/components/Alert.d.ts +11 -0
- package/dist/components/Alert.d.ts.map +1 -0
- package/dist/components/Alert.js +5 -0
- package/dist/components/Alert.js.map +1 -0
- package/dist/components/AsyncContent.d.ts +30 -0
- package/dist/components/AsyncContent.d.ts.map +1 -0
- package/dist/components/AsyncContent.js +33 -0
- package/dist/components/AsyncContent.js.map +1 -0
- package/dist/components/Avatar.d.ts +22 -0
- package/dist/components/Avatar.d.ts.map +1 -0
- package/dist/components/Avatar.js +9 -0
- package/dist/components/Avatar.js.map +1 -0
- package/dist/components/AvatarGroup.d.ts +25 -0
- package/dist/components/AvatarGroup.d.ts.map +1 -0
- package/dist/components/AvatarGroup.js +9 -0
- package/dist/components/AvatarGroup.js.map +1 -0
- package/dist/components/Badge.d.ts +18 -0
- package/dist/components/Badge.d.ts.map +1 -0
- package/dist/components/Badge.js +7 -0
- package/dist/components/Badge.js.map +1 -0
- package/dist/components/Blockquote.d.ts +5 -0
- package/dist/components/Blockquote.d.ts.map +1 -0
- package/dist/components/Blockquote.js +4 -0
- package/dist/components/Blockquote.js.map +1 -0
- package/dist/components/Breadcrumb.d.ts +12 -0
- package/dist/components/Breadcrumb.d.ts.map +1 -0
- package/dist/components/Breadcrumb.js +8 -0
- package/dist/components/Breadcrumb.js.map +1 -0
- package/dist/components/Button.d.ts +42 -0
- package/dist/components/Button.d.ts.map +1 -0
- package/dist/components/Button.js +25 -0
- package/dist/components/Button.js.map +1 -0
- package/dist/components/Card.d.ts +19 -0
- package/dist/components/Card.d.ts.map +1 -0
- package/dist/components/Card.js +5 -0
- package/dist/components/Card.js.map +1 -0
- package/dist/components/Checkbox.d.ts +14 -0
- package/dist/components/Checkbox.d.ts.map +1 -0
- package/dist/components/Checkbox.js +7 -0
- package/dist/components/Checkbox.js.map +1 -0
- package/dist/components/CheckboxGroup.d.ts +53 -0
- package/dist/components/CheckboxGroup.d.ts.map +1 -0
- package/dist/components/CheckboxGroup.js +17 -0
- package/dist/components/CheckboxGroup.js.map +1 -0
- package/dist/components/Circle.d.ts +18 -0
- package/dist/components/Circle.d.ts.map +1 -0
- package/dist/components/Circle.js +5 -0
- package/dist/components/Circle.js.map +1 -0
- package/dist/components/CitricComponent.d.ts +14 -0
- package/dist/components/CitricComponent.d.ts.map +1 -0
- package/dist/components/CitricComponent.js +15 -0
- package/dist/components/CitricComponent.js.map +1 -0
- package/dist/components/Divider.d.ts +14 -0
- package/dist/components/Divider.d.ts.map +1 -0
- package/dist/components/Divider.js +5 -0
- package/dist/components/Divider.js.map +1 -0
- package/dist/components/ErrorBoundary.d.ts +32 -0
- package/dist/components/ErrorBoundary.d.ts.map +1 -0
- package/dist/components/ErrorBoundary.js +46 -0
- package/dist/components/ErrorBoundary.js.map +1 -0
- package/dist/components/ErrorMessage.d.ts +4 -0
- package/dist/components/ErrorMessage.d.ts.map +1 -0
- package/dist/components/ErrorMessage.js +7 -0
- package/dist/components/ErrorMessage.js.map +1 -0
- package/dist/components/FallbackBoundary.d.ts +13 -0
- package/dist/components/FallbackBoundary.d.ts.map +1 -0
- package/dist/components/FallbackBoundary.js +11 -0
- package/dist/components/FallbackBoundary.js.map +1 -0
- package/dist/components/Favorite.d.ts +23 -0
- package/dist/components/Favorite.d.ts.map +1 -0
- package/dist/components/Favorite.js +5 -0
- package/dist/components/Favorite.js.map +1 -0
- package/dist/components/FieldGroup.d.ts +14 -0
- package/dist/components/FieldGroup.d.ts.map +1 -0
- package/dist/components/FieldGroup.js +5 -0
- package/dist/components/FieldGroup.js.map +1 -0
- package/dist/components/Form.d.ts +5 -0
- package/dist/components/Form.d.ts.map +1 -0
- package/dist/components/Form.js +6 -0
- package/dist/components/Form.js.map +1 -0
- package/dist/components/FormGroup.d.ts +22 -0
- package/dist/components/FormGroup.d.ts.map +1 -0
- package/dist/components/FormGroup.js +8 -0
- package/dist/components/FormGroup.js.map +1 -0
- package/dist/components/IconBox.d.ts +46 -0
- package/dist/components/IconBox.d.ts.map +1 -0
- package/dist/components/IconBox.js +29 -0
- package/dist/components/IconBox.js.map +1 -0
- package/dist/components/Input.d.ts +15 -0
- package/dist/components/Input.d.ts.map +1 -0
- package/dist/components/Input.js +18 -0
- package/dist/components/Input.js.map +1 -0
- package/dist/components/Link.d.ts +20 -0
- package/dist/components/Link.d.ts.map +1 -0
- package/dist/components/Link.js +21 -0
- package/dist/components/Link.js.map +1 -0
- package/dist/components/LoadingPanel.d.ts +2 -0
- package/dist/components/LoadingPanel.d.ts.map +1 -0
- package/dist/components/LoadingPanel.js +5 -0
- package/dist/components/LoadingPanel.js.map +1 -0
- package/dist/components/MenuOverlay/Menu.d.ts +6 -0
- package/dist/components/MenuOverlay/Menu.d.ts.map +1 -0
- package/dist/components/MenuOverlay/Menu.js +100 -0
- package/dist/components/MenuOverlay/Menu.js.map +1 -0
- package/dist/components/MenuOverlay/context.d.ts +6 -0
- package/dist/components/MenuOverlay/context.d.ts.map +1 -0
- package/dist/components/MenuOverlay/context.js +16 -0
- package/dist/components/MenuOverlay/context.js.map +1 -0
- package/dist/components/MenuOverlay/index.d.ts +3 -0
- package/dist/components/MenuOverlay/index.d.ts.map +1 -0
- package/dist/components/MenuOverlay/index.js +23 -0
- package/dist/components/MenuOverlay/index.js.map +1 -0
- package/dist/components/MenuOverlay/keyboard.d.ts +2 -0
- package/dist/components/MenuOverlay/keyboard.d.ts.map +1 -0
- package/dist/components/MenuOverlay/keyboard.js +66 -0
- package/dist/components/MenuOverlay/keyboard.js.map +1 -0
- package/dist/components/MenuOverlay/types.d.ts +166 -0
- package/dist/components/MenuOverlay/types.d.ts.map +1 -0
- package/dist/components/MenuOverlay/types.js +2 -0
- package/dist/components/MenuOverlay/types.js.map +1 -0
- package/dist/components/Overlay/context.d.ts +4 -0
- package/dist/components/Overlay/context.d.ts.map +1 -0
- package/dist/components/Overlay/context.js +7 -0
- package/dist/components/Overlay/context.js.map +1 -0
- package/dist/components/Overlay/index.d.ts +14 -0
- package/dist/components/Overlay/index.d.ts.map +1 -0
- package/dist/components/Overlay/index.js +120 -0
- package/dist/components/Overlay/index.js.map +1 -0
- package/dist/components/Overlay/types.d.ts +67 -0
- package/dist/components/Overlay/types.d.ts.map +1 -0
- package/dist/components/Overlay/types.js +2 -0
- package/dist/components/Overlay/types.js.map +1 -0
- package/dist/components/Pagination.d.ts +28 -0
- package/dist/components/Pagination.d.ts.map +1 -0
- package/dist/components/Pagination.js +30 -0
- package/dist/components/Pagination.js.map +1 -0
- package/dist/components/ProgressBar.d.ts +12 -0
- package/dist/components/ProgressBar.d.ts.map +1 -0
- package/dist/components/ProgressBar.js +7 -0
- package/dist/components/ProgressBar.js.map +1 -0
- package/dist/components/ProgressCircular.d.ts +16 -0
- package/dist/components/ProgressCircular.d.ts.map +1 -0
- package/dist/components/ProgressCircular.js +7 -0
- package/dist/components/ProgressCircular.js.map +1 -0
- package/dist/components/RadioGroup.d.ts +48 -0
- package/dist/components/RadioGroup.d.ts.map +1 -0
- package/dist/components/RadioGroup.js +17 -0
- package/dist/components/RadioGroup.js.map +1 -0
- package/dist/components/Rating.d.ts +13 -0
- package/dist/components/Rating.d.ts.map +1 -0
- package/dist/components/Rating.js +4 -0
- package/dist/components/Rating.js.map +1 -0
- package/dist/components/Select/RichSelect.d.ts +5 -0
- package/dist/components/Select/RichSelect.d.ts.map +1 -0
- package/dist/components/Select/RichSelect.js +152 -0
- package/dist/components/Select/RichSelect.js.map +1 -0
- package/dist/components/Select/SimpleSelect.d.ts +5 -0
- package/dist/components/Select/SimpleSelect.d.ts.map +1 -0
- package/dist/components/Select/SimpleSelect.js +24 -0
- package/dist/components/Select/SimpleSelect.js.map +1 -0
- package/dist/components/Select/index.d.ts +4 -0
- package/dist/components/Select/index.d.ts.map +1 -0
- package/dist/components/Select/index.js +7 -0
- package/dist/components/Select/index.js.map +1 -0
- package/dist/components/Select/types.d.ts +118 -0
- package/dist/components/Select/types.d.ts.map +1 -0
- package/dist/components/Select/types.js +2 -0
- package/dist/components/Select/types.js.map +1 -0
- package/dist/components/SelectBox.d.ts +65 -0
- package/dist/components/SelectBox.d.ts.map +1 -0
- package/dist/components/SelectBox.js +26 -0
- package/dist/components/SelectBox.js.map +1 -0
- package/dist/components/Skeleton.d.ts +30 -0
- package/dist/components/Skeleton.d.ts.map +1 -0
- package/dist/components/Skeleton.js +5 -0
- package/dist/components/Skeleton.js.map +1 -0
- package/dist/components/Slider.d.ts +32 -0
- package/dist/components/Slider.d.ts.map +1 -0
- package/dist/components/Slider.js +19 -0
- package/dist/components/Slider.js.map +1 -0
- package/dist/components/SmartTable.d.ts +87 -0
- package/dist/components/SmartTable.d.ts.map +1 -0
- package/dist/components/SmartTable.js +16 -0
- package/dist/components/SmartTable.js.map +1 -0
- package/dist/components/Stepper.d.ts +52 -0
- package/dist/components/Stepper.d.ts.map +1 -0
- package/dist/components/Stepper.js +53 -0
- package/dist/components/Stepper.js.map +1 -0
- package/dist/components/Switch.d.ts +10 -0
- package/dist/components/Switch.d.ts.map +1 -0
- package/dist/components/Switch.js +7 -0
- package/dist/components/Switch.js.map +1 -0
- package/dist/components/Table.d.ts +106 -0
- package/dist/components/Table.d.ts.map +1 -0
- package/dist/components/Table.js +86 -0
- package/dist/components/Table.js.map +1 -0
- package/dist/components/Tabs/TabController.d.ts +11 -0
- package/dist/components/Tabs/TabController.d.ts.map +1 -0
- package/dist/components/Tabs/TabController.js +39 -0
- package/dist/components/Tabs/TabController.js.map +1 -0
- package/dist/components/Tabs/index.d.ts +5 -0
- package/dist/components/Tabs/index.d.ts.map +1 -0
- package/dist/components/Tabs/index.js +37 -0
- package/dist/components/Tabs/index.js.map +1 -0
- package/dist/components/Tabs/types.d.ts +46 -0
- package/dist/components/Tabs/types.d.ts.map +1 -0
- package/dist/components/Tabs/types.js +2 -0
- package/dist/components/Tabs/types.js.map +1 -0
- package/dist/components/Tabs/utils.d.ts +3 -0
- package/dist/components/Tabs/utils.d.ts.map +1 -0
- package/dist/components/Tabs/utils.js +5 -0
- package/dist/components/Tabs/utils.js.map +1 -0
- package/dist/components/Text.d.ts +27 -0
- package/dist/components/Text.d.ts.map +1 -0
- package/dist/components/Text.js +45 -0
- package/dist/components/Text.js.map +1 -0
- package/dist/components/Textarea.d.ts +8 -0
- package/dist/components/Textarea.d.ts.map +1 -0
- package/dist/components/Textarea.js +4 -0
- package/dist/components/Textarea.js.map +1 -0
- package/dist/components/Tooltip.d.ts +25 -0
- package/dist/components/Tooltip.d.ts.map +1 -0
- package/dist/components/Tooltip.js +18 -0
- package/dist/components/Tooltip.js.map +1 -0
- package/dist/components/layout.d.ts +46 -0
- package/dist/components/layout.d.ts.map +1 -0
- package/dist/components/layout.js +18 -0
- package/dist/components/layout.js.map +1 -0
- package/dist/context/CitricContext.d.ts +3 -0
- package/dist/context/CitricContext.d.ts.map +1 -0
- package/dist/context/CitricContext.js +3 -0
- package/dist/context/CitricContext.js.map +1 -0
- package/dist/context/CitricProvider.d.ts +9 -0
- package/dist/context/CitricProvider.d.ts.map +1 -0
- package/dist/context/CitricProvider.js +8 -0
- package/dist/context/CitricProvider.js.map +1 -0
- package/dist/context/hooks.d.ts +2 -0
- package/dist/context/hooks.d.ts.map +1 -0
- package/dist/context/hooks.js +6 -0
- package/dist/context/hooks.js.map +1 -0
- package/dist/index.d.ts +48 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +48 -0
- package/dist/index.js.map +1 -0
- package/dist/overlay.d.ts +83 -0
- package/dist/overlay.d.ts.map +1 -0
- package/dist/overlay.js +199 -0
- package/dist/overlay.js.map +1 -0
- package/dist/theme.css +419 -0
- package/dist/types.d.ts +175 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/ValueController.d.ts +10 -0
- package/dist/utils/ValueController.d.ts.map +1 -0
- package/dist/utils/ValueController.js +32 -0
- package/dist/utils/ValueController.js.map +1 -0
- package/dist/utils/acessibility.d.ts +52 -0
- package/dist/utils/acessibility.d.ts.map +1 -0
- package/dist/utils/acessibility.js +80 -0
- package/dist/utils/acessibility.js.map +1 -0
- package/dist/utils/css.d.ts +12 -0
- package/dist/utils/css.d.ts.map +1 -0
- package/dist/utils/css.js +72 -0
- package/dist/utils/css.js.map +1 -0
- package/dist/utils/options.d.ts +3 -0
- package/dist/utils/options.d.ts.map +1 -0
- package/dist/utils/options.js +7 -0
- package/dist/utils/options.js.map +1 -0
- package/package.json +51 -0
- package/scripts/build-css.ts +49 -0
- package/src/components/Accordion.tsx +74 -0
- package/src/components/Alert.tsx +16 -0
- package/src/components/AsyncContent.tsx +54 -0
- package/src/components/Avatar.tsx +34 -0
- package/src/components/AvatarGroup.tsx +40 -0
- package/src/components/Badge.tsx +28 -0
- package/src/components/Blockquote.tsx +9 -0
- package/src/components/Breadcrumb.tsx +24 -0
- package/src/components/Button.tsx +88 -0
- package/src/components/Card.tsx +32 -0
- package/src/components/Checkbox.tsx +36 -0
- package/src/components/CheckboxGroup.tsx +93 -0
- package/src/components/Circle.tsx +26 -0
- package/src/components/CitricComponent.ts +34 -0
- package/src/components/Divider.tsx +22 -0
- package/src/components/ErrorBoundary.tsx +62 -0
- package/src/components/ErrorMessage.tsx +11 -0
- package/src/components/FallbackBoundary.tsx +29 -0
- package/src/components/Favorite.tsx +37 -0
- package/src/components/FieldGroup.tsx +22 -0
- package/src/components/Form.tsx +17 -0
- package/src/components/FormGroup.tsx +45 -0
- package/src/components/IconBox.tsx +78 -0
- package/src/components/Input.tsx +32 -0
- package/src/components/Link.tsx +40 -0
- package/src/components/LoadingPanel.tsx +8 -0
- package/src/components/MenuOverlay/Menu.tsx +157 -0
- package/src/components/MenuOverlay/context.ts +20 -0
- package/src/components/MenuOverlay/index.tsx +35 -0
- package/src/components/MenuOverlay/keyboard.ts +60 -0
- package/src/components/MenuOverlay/types.ts +178 -0
- package/src/components/Overlay/context.ts +10 -0
- package/src/components/Overlay/index.tsx +137 -0
- package/src/components/Overlay/types.ts +71 -0
- package/src/components/Pagination.tsx +90 -0
- package/src/components/ProgressBar.tsx +25 -0
- package/src/components/ProgressCircular.tsx +29 -0
- package/src/components/RadioGroup.tsx +87 -0
- package/src/components/Rating.tsx +25 -0
- package/src/components/Select/RichSelect.tsx +214 -0
- package/src/components/Select/SimpleSelect.tsx +66 -0
- package/src/components/Select/index.tsx +8 -0
- package/src/components/Select/types.ts +121 -0
- package/src/components/SelectBox.tsx +134 -0
- package/src/components/Skeleton.tsx +41 -0
- package/src/components/Slider.tsx +77 -0
- package/src/components/SmartTable.tsx +148 -0
- package/src/components/Stepper.tsx +142 -0
- package/src/components/Switch.tsx +29 -0
- package/src/components/Table.tsx +219 -0
- package/src/components/Tabs/TabController.ts +40 -0
- package/src/components/Tabs/index.tsx +64 -0
- package/src/components/Tabs/types.ts +48 -0
- package/src/components/Tabs/utils.ts +6 -0
- package/src/components/Text.ts +75 -0
- package/src/components/Textarea.tsx +12 -0
- package/src/components/Tooltip.tsx +53 -0
- package/src/components/layout.tsx +53 -0
- package/src/context/CitricContext.tsx +4 -0
- package/src/context/CitricProvider.tsx +14 -0
- package/src/context/hooks.ts +6 -0
- package/src/index.ts +47 -0
- package/src/overlay.ts +276 -0
- package/src/types.ts +226 -0
- package/src/utils/ValueController.ts +28 -0
- package/src/utils/acessibility.ts +92 -0
- package/src/utils/css.ts +106 -0
- package/src/utils/options.ts +7 -0
- package/tsconfig.json +10 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { OverlayOptions } from '../../overlay'
|
|
2
|
+
import { HTMLExtension } from '../../types'
|
|
3
|
+
|
|
4
|
+
export type TriggerOn = 'hover' | 'click'
|
|
5
|
+
|
|
6
|
+
export interface BaseOverlayProps<T extends keyof React.JSX.IntrinsicElements> extends Omit<OverlayOptions<T>, 'target'> {
|
|
7
|
+
/**
|
|
8
|
+
* When should the overlay element be created? When the child is clicked or when the child is hovered?
|
|
9
|
+
*
|
|
10
|
+
* Accessibility:
|
|
11
|
+
* Click = focus + press enter to open; focus + enter to close OR esc.
|
|
12
|
+
* Hover = focus to open; blur to close.
|
|
13
|
+
*
|
|
14
|
+
* @default 'hover'
|
|
15
|
+
*/
|
|
16
|
+
triggerOn?: TriggerOn,
|
|
17
|
+
/**
|
|
18
|
+
* TODO: not implemented yet.
|
|
19
|
+
*
|
|
20
|
+
* Only valid if `triggerOn` is "hover".
|
|
21
|
+
*
|
|
22
|
+
* If the overlay is hidden right after the mouse leaves the element, then it becomes impossible to interact with anything inside the
|
|
23
|
+
* tooltip. This sets a delay for the overlay to disappear, giving it time for the user to hover the overlay or gocus one of its children,
|
|
24
|
+
* which will prevent it from closing.
|
|
25
|
+
*
|
|
26
|
+
* When `hoverDelayMS` is greater then zero, the hover effect is also applied to the overlay itself instead of just the child element.
|
|
27
|
+
*
|
|
28
|
+
* When set to "auto", a hover delay of 1 second will be used if the overlay contains any focusable element.
|
|
29
|
+
*
|
|
30
|
+
* @default 'auto'
|
|
31
|
+
*/
|
|
32
|
+
hoverDelayMS?: number | 'auto',
|
|
33
|
+
/**
|
|
34
|
+
* TODO: not implemented yet.
|
|
35
|
+
*
|
|
36
|
+
* - Never: the focus won't changes when the overlay opens.
|
|
37
|
+
* - All: the focus always changes when the overlay opens (given there's a focusable element in the overlay).
|
|
38
|
+
* - Keyboard (default): the focus only changes when the overlay is opened via the keyboard.
|
|
39
|
+
*
|
|
40
|
+
* The first focusable element in the overlay will be focused as soon as it's rendered. When it's closed, the child element
|
|
41
|
+
* (`children`) regains focus.
|
|
42
|
+
*
|
|
43
|
+
* The focus control will be such that, after the last element in the overlay is focused, the next focus will move to the child element
|
|
44
|
+
* (`children`).
|
|
45
|
+
*
|
|
46
|
+
* If the overlay has no focusable element, this properties makes no difference.
|
|
47
|
+
*
|
|
48
|
+
* Attention: focusable elements inside the overlay can be ignored by setting `auto-focus` to false.
|
|
49
|
+
*
|
|
50
|
+
* @default 'keyboard'
|
|
51
|
+
*/
|
|
52
|
+
autoFocusBehavior?: 'never' | 'always' | 'keyboard',
|
|
53
|
+
/**
|
|
54
|
+
* The element to receive the overlay.
|
|
55
|
+
*/
|
|
56
|
+
children: React.ReactElement,
|
|
57
|
+
/**
|
|
58
|
+
* Function to run when the child is rendered. It receives the child element as a parameter.
|
|
59
|
+
*
|
|
60
|
+
* This is useful for easily adding attributes to the element (mainly accessibility ones).
|
|
61
|
+
* @param element the child element, the one that receives the overlay.
|
|
62
|
+
*/
|
|
63
|
+
onRenderChild?: (element: HTMLElement) => void,
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export type OverlayProps<T extends keyof React.JSX.IntrinsicElements> =
|
|
67
|
+
HTMLExtension<'div', BaseOverlayProps<T>, 'children' | 'content'>
|
|
68
|
+
|
|
69
|
+
export interface OverlayController {
|
|
70
|
+
close: () => Promise<void>,
|
|
71
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { Dictionary, interpolate, useTranslate } from '@stack-spot/portal-translate'
|
|
2
|
+
import { useMemo } from 'react'
|
|
3
|
+
import { HTMLExtension } from '../types'
|
|
4
|
+
import { CitricComponent } from './CitricComponent'
|
|
5
|
+
import { IconButton } from './IconBox'
|
|
6
|
+
|
|
7
|
+
export interface BasePaginationProps {
|
|
8
|
+
/**
|
|
9
|
+
* The options for the page size.
|
|
10
|
+
*
|
|
11
|
+
* @default [10, 20, 30]
|
|
12
|
+
*/
|
|
13
|
+
pageSizeOptions?: number[],
|
|
14
|
+
/**
|
|
15
|
+
* Total number of pages
|
|
16
|
+
*/
|
|
17
|
+
totalPages: number,
|
|
18
|
+
/**
|
|
19
|
+
* The first page is 1. If "0" is provided, it will be treated as if it was "1".
|
|
20
|
+
*/
|
|
21
|
+
page: number,
|
|
22
|
+
/**
|
|
23
|
+
* The current number of items in a page.
|
|
24
|
+
*/
|
|
25
|
+
pageSize: number,
|
|
26
|
+
/**
|
|
27
|
+
* Function to run whenever the page size or current page changes.
|
|
28
|
+
*/
|
|
29
|
+
onChange: (page: number, size: number) => void,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export type PaginationProps = HTMLExtension<'div', BasePaginationProps, 'children' | 'onChange'>
|
|
33
|
+
|
|
34
|
+
export const Pagination = (
|
|
35
|
+
{ pageSizeOptions = [10, 20, 30], pageSize, totalPages, page, onChange, ...props }: PaginationProps,
|
|
36
|
+
) => {
|
|
37
|
+
const t = useTranslate(dictionary)
|
|
38
|
+
const sizeOptions = useMemo(() => pageSizeOptions.map(o => <option key={o} selected={pageSize === o}>{o}</option>), [pageSizeOptions])
|
|
39
|
+
const pageOptions = useMemo(() => {
|
|
40
|
+
const options: React.ReactElement[] = []
|
|
41
|
+
for (let i = 1; i <= totalPages; i++) {
|
|
42
|
+
options.push(<option key={i} value={i} selected={page === i}>{i}</option>)
|
|
43
|
+
}
|
|
44
|
+
return options
|
|
45
|
+
}, [page, totalPages])
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<CitricComponent tag="div" component="pagination" {...props}>
|
|
49
|
+
<div className="page-size">
|
|
50
|
+
<label>
|
|
51
|
+
{t.itemsPerPage}:
|
|
52
|
+
<select name="itemsPerPage" onChange={e => onChange(page, parseInt(e.target.value))}>{sizeOptions}</select>
|
|
53
|
+
</label>
|
|
54
|
+
</div>
|
|
55
|
+
<div className="page-number">
|
|
56
|
+
<label>
|
|
57
|
+
<select name="page" onChange={e => onChange(parseInt(e.target.value), pageSize)}>{pageOptions}</select>
|
|
58
|
+
</label>
|
|
59
|
+
{totalPages > 1 ? interpolate(t.ofTotalPlural, totalPages) : t.ofTotalSingular}
|
|
60
|
+
<IconButton
|
|
61
|
+
icon="ChevronLeft"
|
|
62
|
+
aria-label="previous"
|
|
63
|
+
title="previous"
|
|
64
|
+
disabled={page === 1}
|
|
65
|
+
onClick={() => onChange(page - 1, pageSize)}
|
|
66
|
+
/>
|
|
67
|
+
<IconButton
|
|
68
|
+
icon="ChevronRight"
|
|
69
|
+
aria-label="next"
|
|
70
|
+
title="next"
|
|
71
|
+
disabled={page === totalPages}
|
|
72
|
+
onClick={() => onChange(page + 1, pageSize)}
|
|
73
|
+
/>
|
|
74
|
+
</div>
|
|
75
|
+
</CitricComponent>
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const dictionary = {
|
|
80
|
+
en: {
|
|
81
|
+
itemsPerPage: 'Items per page',
|
|
82
|
+
ofTotalSingular: 'of 1 page',
|
|
83
|
+
ofTotalPlural: 'of $0 pages',
|
|
84
|
+
},
|
|
85
|
+
pt: {
|
|
86
|
+
itemsPerPage: 'Itens por página',
|
|
87
|
+
ofTotalSingular: 'de 1 página',
|
|
88
|
+
ofTotalPlural: 'de $0 páginas',
|
|
89
|
+
},
|
|
90
|
+
} satisfies Dictionary
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { listToClass } from '@stack-spot/portal-theme'
|
|
2
|
+
import { isNil } from 'lodash'
|
|
3
|
+
import { HTMLExtension, WithColorPalette, WithColorScheme } from '../types'
|
|
4
|
+
import { applyCSSVariable } from '../utils/css'
|
|
5
|
+
import { CitricComponent } from './CitricComponent'
|
|
6
|
+
|
|
7
|
+
export interface BaseProgressBarProps extends WithColorScheme, WithColorPalette {
|
|
8
|
+
/**
|
|
9
|
+
* A number varying from 0 to 100.
|
|
10
|
+
*
|
|
11
|
+
* If not provided, the progress will be indeterminate, causing an animation loop.
|
|
12
|
+
*/
|
|
13
|
+
progress?: number,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type ProgressBarProps = HTMLExtension<'div', BaseProgressBarProps>
|
|
17
|
+
|
|
18
|
+
export const ProgressBar = ({ progress, style, className, ...props }: ProgressBarProps) =>
|
|
19
|
+
<CitricComponent
|
|
20
|
+
tag="div"
|
|
21
|
+
component="progress-bar"
|
|
22
|
+
className={listToClass([className, isNil(progress) && 'indeterminate'])}
|
|
23
|
+
style={progress === undefined ? style : applyCSSVariable(style, 'progress', progress)}
|
|
24
|
+
{...props}
|
|
25
|
+
/>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { listToClass } from '@stack-spot/portal-theme'
|
|
2
|
+
import { isNil } from 'lodash'
|
|
3
|
+
import { HTMLExtension, WithColorPalette, WithColorScheme } from '../types'
|
|
4
|
+
import { applyCSSVariable } from '../utils/css'
|
|
5
|
+
import { CitricComponent } from './CitricComponent'
|
|
6
|
+
|
|
7
|
+
export interface BaseProgressCircularProps extends WithColorScheme, WithColorPalette {
|
|
8
|
+
/**
|
|
9
|
+
* A number varying from 0 to 100.
|
|
10
|
+
*
|
|
11
|
+
* If not provided, the progress will be indeterminate, causing an animation loop.
|
|
12
|
+
*/
|
|
13
|
+
progress?: number,
|
|
14
|
+
/**
|
|
15
|
+
* @default 'md'
|
|
16
|
+
*/
|
|
17
|
+
size?: 'xs' | 'sm' | 'md' | 'lg',
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type ProgressCircularProps = HTMLExtension<'div', BaseProgressCircularProps>
|
|
21
|
+
|
|
22
|
+
export const ProgressCircular = ({ progress, size, style, className, ...props }: ProgressCircularProps) =>
|
|
23
|
+
<CitricComponent
|
|
24
|
+
tag="div"
|
|
25
|
+
component="progress-circular"
|
|
26
|
+
className={listToClass([size, className, isNil(progress) && 'indeterminate'])}
|
|
27
|
+
style={progress === undefined ? style : applyCSSVariable(style, 'progress', progress)}
|
|
28
|
+
{...props}
|
|
29
|
+
/>
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { isNil } from 'lodash'
|
|
2
|
+
import { useMemo } from 'react'
|
|
3
|
+
import { HTMLExtension, WithColorScheme } from '../types'
|
|
4
|
+
import { defaultRenderKey, defaultRenderLabel } from '../utils/options'
|
|
5
|
+
import { CitricComponent } from './CitricComponent'
|
|
6
|
+
import { Column } from './layout'
|
|
7
|
+
|
|
8
|
+
export interface BaseRadioGroupProps<T> extends WithColorScheme {
|
|
9
|
+
/**
|
|
10
|
+
* The field name.
|
|
11
|
+
*/
|
|
12
|
+
name?: string,
|
|
13
|
+
/**
|
|
14
|
+
* The current value.
|
|
15
|
+
*/
|
|
16
|
+
value?: T,
|
|
17
|
+
/**
|
|
18
|
+
* All the items (radio buttons) to render.
|
|
19
|
+
*/
|
|
20
|
+
options: T[],
|
|
21
|
+
/**
|
|
22
|
+
* Called whenever the selected radio button changes.
|
|
23
|
+
* @param value the currently selected item.
|
|
24
|
+
*/
|
|
25
|
+
onChange?: (value: T) => void,
|
|
26
|
+
/**
|
|
27
|
+
* A function to render the item label.
|
|
28
|
+
* @example
|
|
29
|
+
* `(option) => option.name`
|
|
30
|
+
* @default "the item's toString() result."
|
|
31
|
+
* @param option the item to render.
|
|
32
|
+
* @returns a React Node to render.
|
|
33
|
+
*/
|
|
34
|
+
renderLabel?: (option: T) => React.ReactNode,
|
|
35
|
+
/**
|
|
36
|
+
* A function to render the item value, a unique identifier for the option.
|
|
37
|
+
* @example
|
|
38
|
+
* `(option) => option.id`
|
|
39
|
+
* @default "if the item is a string or a number, the stringified item. Otherwise, undefined."
|
|
40
|
+
* @param option the item to compute a key for.
|
|
41
|
+
* @returns a string key.
|
|
42
|
+
*/
|
|
43
|
+
renderKey?: (option: T) => string | number | undefined,
|
|
44
|
+
/**
|
|
45
|
+
* If this function returns true for the item, this option is disabled.
|
|
46
|
+
* @default "nothing is disabled"
|
|
47
|
+
* @param option the item to calculate "disabled" for.
|
|
48
|
+
* @returns true if the item should be disabled, false otherwise.
|
|
49
|
+
*/
|
|
50
|
+
isDisabled?: (option: T) => boolean,
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export type RadioGroupProps<T> = HTMLExtension<'div', BaseRadioGroupProps<T>, 'onChange'>
|
|
54
|
+
|
|
55
|
+
export function RadioGroup<T>({
|
|
56
|
+
name,
|
|
57
|
+
value,
|
|
58
|
+
options,
|
|
59
|
+
onChange,
|
|
60
|
+
renderLabel = defaultRenderLabel,
|
|
61
|
+
renderKey = defaultRenderKey,
|
|
62
|
+
isDisabled,
|
|
63
|
+
colorScheme,
|
|
64
|
+
style,
|
|
65
|
+
...props
|
|
66
|
+
}: RadioGroupProps<T>) {
|
|
67
|
+
const items = useMemo(() => {
|
|
68
|
+
const valueKey = value ? renderKey(value) : undefined
|
|
69
|
+
return options.map((o) => {
|
|
70
|
+
const key = renderKey(o)
|
|
71
|
+
return (
|
|
72
|
+
<CitricComponent tag="label" component="radio-row" key={key} colorScheme={colorScheme}>
|
|
73
|
+
<input
|
|
74
|
+
type="radio"
|
|
75
|
+
name={name}
|
|
76
|
+
value={key}
|
|
77
|
+
checked={value === o || (!isNil(key) && valueKey === key)}
|
|
78
|
+
onChange={() => onChange?.(o)}
|
|
79
|
+
disabled={isDisabled?.(o)}
|
|
80
|
+
/>
|
|
81
|
+
{renderLabel(o)}
|
|
82
|
+
</CitricComponent>
|
|
83
|
+
)
|
|
84
|
+
})
|
|
85
|
+
}, [options, value, name, colorScheme])
|
|
86
|
+
return <Column {...props} style={{ gap: '8px', ...style }}>{items}</Column>
|
|
87
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { HTMLExtension } from '../types'
|
|
2
|
+
import { CitricComponent } from './CitricComponent'
|
|
3
|
+
|
|
4
|
+
export type RatingValue = 1 | 2 | 3 | 4 | 5
|
|
5
|
+
|
|
6
|
+
export interface BaseRatingProps {
|
|
7
|
+
value: RatingValue | undefined,
|
|
8
|
+
onChange: (value: RatingValue) => void,
|
|
9
|
+
/**
|
|
10
|
+
* The name of this input.
|
|
11
|
+
*/
|
|
12
|
+
name?: string,
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export type RatingProps = HTMLExtension<'div', BaseRatingProps, 'children' | 'onChange'>
|
|
16
|
+
|
|
17
|
+
export const Rating = ({ value, onChange, name, ...props }: RatingProps) => (
|
|
18
|
+
<CitricComponent tag="div" component="rating" {...props}>
|
|
19
|
+
<input type="radio" value="1" name={name} checked={value === 5} onChange={() => onChange(5)} />
|
|
20
|
+
<input type="radio" value="2" name={name} checked={value === 4} onChange={() => onChange(4)} />
|
|
21
|
+
<input type="radio" value="3" name={name} checked={value === 3} onChange={() => onChange(3)} />
|
|
22
|
+
<input type="radio" value="4" name={name} checked={value === 2} onChange={() => onChange(2)} />
|
|
23
|
+
<input type="radio" value="5" name={name} checked={value === 1} onChange={() => onChange(1)} />
|
|
24
|
+
</CitricComponent>
|
|
25
|
+
)
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { listToClass } from '@stack-spot/portal-theme'
|
|
2
|
+
import { useTranslate } from '@stack-spot/portal-translate'
|
|
3
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
|
4
|
+
import { applyCSSVariable } from '../../utils/css'
|
|
5
|
+
import { defaultRenderKey, defaultRenderLabel } from '../../utils/options'
|
|
6
|
+
import { CitricComponent } from '../CitricComponent'
|
|
7
|
+
import { Input } from '../Input'
|
|
8
|
+
import { ProgressCircular } from '../ProgressCircular'
|
|
9
|
+
import { SimpleSelect } from './SimpleSelect'
|
|
10
|
+
import { SelectProps } from './types'
|
|
11
|
+
|
|
12
|
+
export function RichSelect<T>({
|
|
13
|
+
options,
|
|
14
|
+
value,
|
|
15
|
+
onChange,
|
|
16
|
+
renderLabel = defaultRenderLabel,
|
|
17
|
+
renderKey = defaultRenderKey,
|
|
18
|
+
required = true,
|
|
19
|
+
disabled,
|
|
20
|
+
loading,
|
|
21
|
+
renderOption,
|
|
22
|
+
renderHeader,
|
|
23
|
+
searchable,
|
|
24
|
+
maxHeight,
|
|
25
|
+
style,
|
|
26
|
+
className,
|
|
27
|
+
onFocus,
|
|
28
|
+
onBlur,
|
|
29
|
+
showArrow,
|
|
30
|
+
...props
|
|
31
|
+
}: SelectProps<T> & { type?: 'rich' }) {
|
|
32
|
+
const [search, setSearch] = useState('')
|
|
33
|
+
const element = useRef<HTMLDivElement | null>(null)
|
|
34
|
+
const [open, setOpen] = useState(false)
|
|
35
|
+
const [focused, setFocused] = useState(false)
|
|
36
|
+
const t = useTranslate(dictionary)
|
|
37
|
+
|
|
38
|
+
const change = useCallback((option: T | undefined) => () => {
|
|
39
|
+
onChange?.(option)
|
|
40
|
+
setOpen(false)
|
|
41
|
+
}, [])
|
|
42
|
+
|
|
43
|
+
const renderedOptions = useMemo(() => {
|
|
44
|
+
const items = required ? [] : [<li key="" className="empty" onClick={change(undefined)}>{t.empty}</li>]
|
|
45
|
+
options.forEach((o) => {
|
|
46
|
+
const key = renderKey(o)
|
|
47
|
+
const label = renderLabel(o)
|
|
48
|
+
if (!search.trim() || label.toLocaleLowerCase().includes(search.trim().toLocaleLowerCase())) {
|
|
49
|
+
items.push(
|
|
50
|
+
<li key={key} onClick={change(o)}>
|
|
51
|
+
{renderOption?.(o) ?? label}
|
|
52
|
+
</li>,
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
return items
|
|
57
|
+
}, [options, value, required, search])
|
|
58
|
+
|
|
59
|
+
/* this runs whenever the selection panel is opened */
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
if (open) {
|
|
62
|
+
setSearch('')
|
|
63
|
+
const selectionPanel = element.current?.querySelector('.selection-panel') as HTMLElement | undefined
|
|
64
|
+
selectionPanel?.querySelector('ul')?.scrollTo({ top: 0 })
|
|
65
|
+
const getCurrent = () => selectionPanel?.querySelector('li.focused') as HTMLElement | undefined
|
|
66
|
+
const scrollTo = (li: HTMLElement) => {
|
|
67
|
+
const ul = li.closest('ul')
|
|
68
|
+
if (!ul) return
|
|
69
|
+
const { top: ulTop, height: ulHeight } = ul.getBoundingClientRect()
|
|
70
|
+
const { height: liHeight, top: liTop } = li.getBoundingClientRect()
|
|
71
|
+
const offset = liTop + ul.scrollTop - ulTop
|
|
72
|
+
if ((ul.scrollTop + ulHeight < offset + liHeight) || ul.scrollTop > offset) {
|
|
73
|
+
ul.scrollTo({ top: offset })
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/* keyboard and mouse controls */
|
|
77
|
+
const listenToMouse = (event: Event) => {
|
|
78
|
+
if (!selectionPanel?.contains(event.target as HTMLElement)) {
|
|
79
|
+
setOpen(false)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const listenToKeyboard = (event: KeyboardEvent) => {
|
|
83
|
+
const isCharacter = event.key.length === 1
|
|
84
|
+
if (['Escape', 'ArrowUp', 'ArrowDown', 'Enter'].includes(event.key) || (searchable && (isCharacter || event.key === 'Backspace'))) {
|
|
85
|
+
event.preventDefault()
|
|
86
|
+
event.stopPropagation()
|
|
87
|
+
}
|
|
88
|
+
if (event.key === 'Escape') setOpen(false)
|
|
89
|
+
if (searchable) {
|
|
90
|
+
if (isCharacter) setSearch(v => `${v}${event.key}`)
|
|
91
|
+
if (event.key === 'Backspace') setSearch(v => v.substring(0, v.length - 1))
|
|
92
|
+
}
|
|
93
|
+
if (event.key === 'ArrowDown') {
|
|
94
|
+
const current = getCurrent()
|
|
95
|
+
const next = (current?.nextElementSibling ?? selectionPanel?.querySelector('li')) as HTMLAreaElement | undefined
|
|
96
|
+
if (next) {
|
|
97
|
+
current?.classList.remove('focused')
|
|
98
|
+
next.classList.add('focused')
|
|
99
|
+
scrollTo(next)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (event.key === 'ArrowUp') {
|
|
103
|
+
const current = getCurrent()
|
|
104
|
+
const prev = (current?.previousElementSibling ?? selectionPanel?.querySelector('li:last-child')) as HTMLAreaElement | undefined
|
|
105
|
+
if (prev) {
|
|
106
|
+
current?.classList.remove('focused')
|
|
107
|
+
prev.classList.add('focused')
|
|
108
|
+
scrollTo(prev)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (event.key === 'Enter') {
|
|
112
|
+
setTimeout(() => getCurrent()?.click(), 0)
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
document.addEventListener('click', listenToMouse)
|
|
117
|
+
document.addEventListener('keydown', listenToKeyboard)
|
|
118
|
+
return () => {
|
|
119
|
+
document.removeEventListener('click', listenToMouse)
|
|
120
|
+
document.removeEventListener('keydown', listenToKeyboard)
|
|
121
|
+
getCurrent()?.classList.remove('focused')
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}, [open])
|
|
125
|
+
|
|
126
|
+
/* this runs whenever the select is focused */
|
|
127
|
+
useEffect(() => {
|
|
128
|
+
if (focused) {
|
|
129
|
+
const listenToMouse = (event: MouseEvent) => {
|
|
130
|
+
if (!element.current?.contains(event.target as HTMLElement)) {
|
|
131
|
+
setFocused(false)
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
const listenToKeyboard = (event: KeyboardEvent) => {
|
|
135
|
+
if (['Enter', 'ArrowDown', 'ArrowUp'].includes(event.key)) {
|
|
136
|
+
event.preventDefault()
|
|
137
|
+
if (!element.current?.classList.contains('open')) setOpen(true)
|
|
138
|
+
}
|
|
139
|
+
if (event.key === 'Tab') {
|
|
140
|
+
setFocused(false)
|
|
141
|
+
if (element.current?.classList.contains('open')) setOpen(false)
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
document.addEventListener('click', listenToMouse)
|
|
145
|
+
document.addEventListener('keydown', listenToKeyboard)
|
|
146
|
+
return () => {
|
|
147
|
+
document.removeEventListener('click', listenToMouse)
|
|
148
|
+
document.removeEventListener('keydown', listenToKeyboard)
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}, [focused])
|
|
152
|
+
|
|
153
|
+
useEffect(() => {
|
|
154
|
+
if (disabled) {
|
|
155
|
+
setOpen(false)
|
|
156
|
+
setFocused(false)
|
|
157
|
+
}
|
|
158
|
+
}, [disabled])
|
|
159
|
+
|
|
160
|
+
return (
|
|
161
|
+
<CitricComponent
|
|
162
|
+
tag="div"
|
|
163
|
+
component="rich-select"
|
|
164
|
+
style={maxHeight ? applyCSSVariable(style, 'max-height', `${maxHeight}px`) : style}
|
|
165
|
+
className={listToClass([className, showArrow === false && 'hide-arrow', open && 'open', focused && 'focused'])}
|
|
166
|
+
ref={element}
|
|
167
|
+
aria-busy={loading}
|
|
168
|
+
{...props}
|
|
169
|
+
>
|
|
170
|
+
<SimpleSelect
|
|
171
|
+
options={options}
|
|
172
|
+
value={value}
|
|
173
|
+
renderLabel={renderLabel}
|
|
174
|
+
renderKey={renderKey}
|
|
175
|
+
required={required}
|
|
176
|
+
disabled={disabled}
|
|
177
|
+
onChange={onChange}
|
|
178
|
+
onFocus={onFocus}
|
|
179
|
+
onBlur={onBlur}
|
|
180
|
+
wrap={false}
|
|
181
|
+
/>
|
|
182
|
+
<header
|
|
183
|
+
onClick={(e) => {
|
|
184
|
+
if (disabled) return
|
|
185
|
+
if (!open) e.stopPropagation()
|
|
186
|
+
setFocused(true)
|
|
187
|
+
setOpen(true)
|
|
188
|
+
}}
|
|
189
|
+
aria-hidden
|
|
190
|
+
>
|
|
191
|
+
{renderHeader?.(value) ?? (value ? (renderOption?.(value) ?? renderLabel(value)) : <span></span>)}
|
|
192
|
+
{loading && <ProgressCircular size="xs" className="loader" />}
|
|
193
|
+
</header>
|
|
194
|
+
<div className="selection-panel" aria-hidden>
|
|
195
|
+
{searchable && <div className="search-bar">
|
|
196
|
+
<div data-citric="field-group" className="auto">
|
|
197
|
+
<i data-citric="icon-box" className="citric-icon outline Search"></i>
|
|
198
|
+
<Input type="search" value={search} onChange={setSearch} tabIndex={-1} />
|
|
199
|
+
</div>
|
|
200
|
+
</div>}
|
|
201
|
+
<ul>{renderedOptions}</ul>
|
|
202
|
+
</div>
|
|
203
|
+
</CitricComponent>
|
|
204
|
+
)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const dictionary = {
|
|
208
|
+
en: {
|
|
209
|
+
empty: 'Empty',
|
|
210
|
+
},
|
|
211
|
+
pt: {
|
|
212
|
+
empty: 'Vazio',
|
|
213
|
+
},
|
|
214
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { isNil } from 'lodash'
|
|
2
|
+
import { useCallback, useMemo } from 'react'
|
|
3
|
+
import { defaultRenderKey, defaultRenderLabel } from '../../utils/options'
|
|
4
|
+
import { CitricComponent } from '../CitricComponent'
|
|
5
|
+
import { ProgressCircular } from '../ProgressCircular'
|
|
6
|
+
import { SelectProps } from './types'
|
|
7
|
+
|
|
8
|
+
export function SimpleSelect<T>({
|
|
9
|
+
options,
|
|
10
|
+
value,
|
|
11
|
+
onChange,
|
|
12
|
+
renderLabel = defaultRenderLabel,
|
|
13
|
+
renderKey = defaultRenderKey,
|
|
14
|
+
required = true,
|
|
15
|
+
loading,
|
|
16
|
+
disabled,
|
|
17
|
+
onBlur,
|
|
18
|
+
onFocus,
|
|
19
|
+
wrap,
|
|
20
|
+
...props
|
|
21
|
+
}: SelectProps<T> & { wrap?: boolean }) {
|
|
22
|
+
const handleChange = useCallback((e: React.ChangeEvent<HTMLSelectElement>) => {
|
|
23
|
+
const selectedIndex = e.target.options.selectedIndex + (required ? 0 : 1)
|
|
24
|
+
onChange?.(options[selectedIndex])
|
|
25
|
+
}, [options])
|
|
26
|
+
|
|
27
|
+
const renderedOptions = useMemo(() => {
|
|
28
|
+
const valueKey = value ? renderKey(value) : undefined
|
|
29
|
+
const items = (!value || !required) ? [<option key=""></option>] : []
|
|
30
|
+
options.forEach((o) => {
|
|
31
|
+
const key = renderKey(o)
|
|
32
|
+
items.push(
|
|
33
|
+
<option key={key} value={key} selected={o === value || (!isNil(key) && key === valueKey)}>
|
|
34
|
+
{renderLabel(o)}
|
|
35
|
+
</option>,
|
|
36
|
+
)
|
|
37
|
+
})
|
|
38
|
+
return items
|
|
39
|
+
}, [options, value, required])
|
|
40
|
+
|
|
41
|
+
const children = <>
|
|
42
|
+
{required ? undefined : <option></option>}
|
|
43
|
+
{renderedOptions}
|
|
44
|
+
</>
|
|
45
|
+
|
|
46
|
+
return wrap === false ? (
|
|
47
|
+
<CitricComponent
|
|
48
|
+
required={required}
|
|
49
|
+
onChange={handleChange}
|
|
50
|
+
disabled={disabled || loading}
|
|
51
|
+
tag="select"
|
|
52
|
+
component="select"
|
|
53
|
+
onFocus={onFocus}
|
|
54
|
+
onBlur={onBlur}
|
|
55
|
+
>
|
|
56
|
+
{children}
|
|
57
|
+
</CitricComponent>
|
|
58
|
+
) : (
|
|
59
|
+
<CitricComponent tag="div" component="select" aria-busy={loading} {...props}>
|
|
60
|
+
<select required={required} onChange={handleChange} disabled={disabled || loading} onFocus={onFocus} onBlur={onBlur}>
|
|
61
|
+
{children}
|
|
62
|
+
</select>
|
|
63
|
+
{loading && <ProgressCircular className="loader" size="xs" />}
|
|
64
|
+
</CitricComponent>
|
|
65
|
+
)
|
|
66
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { RichSelect } from './RichSelect'
|
|
2
|
+
import { SimpleSelect } from './SimpleSelect'
|
|
3
|
+
import { SelectProps } from './types'
|
|
4
|
+
export type * from './types'
|
|
5
|
+
|
|
6
|
+
export function Select<T>(props: SelectProps<T>) {
|
|
7
|
+
return props.type === 'simple' ? <SimpleSelect {...props} /> : <RichSelect {...props} />
|
|
8
|
+
}
|