@rovula/ui 0.1.6 → 0.1.8
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/cjs/bundle.css +630 -467
- package/dist/cjs/bundle.js +1545 -1545
- package/dist/cjs/bundle.js.map +1 -1
- package/dist/cjs/types/components/AlertDialog/AlertDialog.stories.d.ts +3 -0
- package/dist/cjs/types/components/Dialog/Dialog.d.ts +7 -1
- package/dist/cjs/types/components/Dialog/Dialog.stories.d.ts +3 -0
- package/dist/cjs/types/components/Dropdown/Dropdown.d.ts +2 -0
- package/dist/cjs/types/components/Dropdown/Dropdown.stories.d.ts +2 -0
- package/dist/cjs/types/components/Form/Field.d.ts +26 -0
- package/dist/cjs/types/components/Form/FieldMessage.d.ts +7 -0
- package/dist/cjs/types/components/Form/Form.d.ts +49 -11
- package/dist/cjs/types/components/Form/Form.stories.d.ts +23 -0
- package/dist/cjs/types/components/Form/ValidationHintList.d.ts +17 -0
- package/dist/cjs/types/components/Form/ValidationHintList.stories.d.ts +9 -0
- package/dist/cjs/types/components/Form/index.d.ts +10 -0
- package/dist/cjs/types/components/Form/useOptionBridge.d.ts +17 -0
- package/dist/cjs/types/components/OtpInput/OtpInput.d.ts +17 -0
- package/dist/cjs/types/components/OtpInput/OtpInput.stories.d.ts +15 -0
- package/dist/cjs/types/components/OtpInput/OtpInputGroup.d.ts +25 -0
- package/dist/cjs/types/components/OtpInput/index.d.ts +5 -0
- package/dist/cjs/types/components/TextInput/TextInput.styles.d.ts +3 -0
- package/dist/cjs/types/index.d.ts +5 -0
- package/dist/cjs/types/theme/ThemeColorCoverageRuntime.stories.d.ts +10 -0
- package/dist/cjs/types/utils/colors.d.ts +351 -267
- package/dist/components/ActionButton/ActionButton.stories.js +2 -2
- package/dist/components/ActionButton/ActionButton.styles.js +1 -1
- package/dist/components/AlertDialog/AlertDialog.js +6 -6
- package/dist/components/AlertDialog/AlertDialog.stories.js +3 -0
- package/dist/components/Avatar/Avatar.stories.js +1 -1
- package/dist/components/Avatar/Avatar.styles.js +1 -1
- package/dist/components/Avatar/AvatarBase.js +1 -1
- package/dist/components/Avatar/AvatarGroup.stories.js +1 -1
- package/dist/components/Button/Buttons.stories.js +2 -2
- package/dist/components/Calendar/Calendar.js +1 -1
- package/dist/components/Checkbox/Checkbox.js +1 -1
- package/dist/components/Checkbox/Checkbox.stories.js +17 -7
- package/dist/components/Collapsible/Collapsible.styles.js +1 -1
- package/dist/components/DataTable/DataTable.js +2 -2
- package/dist/components/Dialog/Dialog.js +12 -7
- package/dist/components/Dialog/Dialog.stories.js +90 -2
- package/dist/components/Dropdown/Dropdown.js +2 -2
- package/dist/components/DropdownMenu/DropdownMenu.js +3 -3
- package/dist/components/FocusedScrollView/FocusedScrollView.stories.js +6 -6
- package/dist/components/Form/Field.js +60 -0
- package/dist/components/Form/FieldMessage.js +24 -0
- package/dist/components/Form/Form.js +73 -41
- package/dist/components/Form/Form.stories.js +221 -0
- package/dist/components/Form/ValidationHintList.js +30 -0
- package/dist/components/Form/ValidationHintList.stories.js +50 -0
- package/dist/components/Form/index.js +5 -0
- package/dist/components/Form/useOptionBridge.js +27 -0
- package/dist/components/InputFilter/InputFilter.js +5 -4
- package/dist/components/InputFilter/InputFilter.stories.js +1 -1
- package/dist/components/InputFilter/InputFilter.styles.js +14 -1
- package/dist/components/Label/Label.styles.js +1 -1
- package/dist/components/Menu/Menu.js +2 -2
- package/dist/components/NumberInput/NumberInput.stories.js +1 -1
- package/dist/components/OtpInput/OtpInput.js +118 -0
- package/dist/components/OtpInput/OtpInput.stories.js +60 -0
- package/dist/components/OtpInput/OtpInputGroup.js +23 -0
- package/dist/components/OtpInput/index.js +3 -0
- package/dist/components/PasswordInput/PasswordInput.stories.js +1 -1
- package/dist/components/Popover/Popover.js +1 -1
- package/dist/components/RadioGroup/RadioGroup.js +1 -1
- package/dist/components/RadioGroup/RadioGroup.stories.js +2 -2
- package/dist/components/Search/Search.js +13 -1
- package/dist/components/Search/Search.stories.js +1 -1
- package/dist/components/Slider/Slider.js +1 -1
- package/dist/components/Slider/Slider.stories.js +5 -5
- package/dist/components/Switch/Switch.stories.js +2 -2
- package/dist/components/Switch/Switch.styles.js +1 -1
- package/dist/components/Table/Table.js +5 -5
- package/dist/components/Tabs/Tabs.js +12 -9
- package/dist/components/Tabs/Tabs.stories.js +1 -1
- package/dist/components/Text/Text.js +1 -1
- package/dist/components/Text/Text.stories.js +1 -1
- package/dist/components/TextArea/TextArea.stories.js +1 -1
- package/dist/components/TextArea/TextArea.styles.js +3 -3
- package/dist/components/TextInput/TextInput.js +3 -2
- package/dist/components/TextInput/TextInput.stories.js +3 -3
- package/dist/components/TextInput/TextInput.styles.js +41 -19
- package/dist/components/Toast/Toast.js +4 -2
- package/dist/components/Toast/Toast.stories.js +1 -1
- package/dist/components/Toast/Toast.styles.js +4 -4
- package/dist/components/Toast/Toaster.js +2 -2
- package/dist/components/Tree/Tree.stories.js +1 -1
- package/dist/components/Tree/TreeItem.js +1 -1
- package/dist/esm/bundle.css +630 -467
- package/dist/esm/bundle.js +1545 -1545
- package/dist/esm/bundle.js.map +1 -1
- package/dist/esm/types/components/AlertDialog/AlertDialog.stories.d.ts +3 -0
- package/dist/esm/types/components/Dialog/Dialog.d.ts +7 -1
- package/dist/esm/types/components/Dialog/Dialog.stories.d.ts +3 -0
- package/dist/esm/types/components/Dropdown/Dropdown.d.ts +2 -0
- package/dist/esm/types/components/Dropdown/Dropdown.stories.d.ts +2 -0
- package/dist/esm/types/components/Form/Field.d.ts +26 -0
- package/dist/esm/types/components/Form/FieldMessage.d.ts +7 -0
- package/dist/esm/types/components/Form/Form.d.ts +49 -11
- package/dist/esm/types/components/Form/Form.stories.d.ts +23 -0
- package/dist/esm/types/components/Form/ValidationHintList.d.ts +17 -0
- package/dist/esm/types/components/Form/ValidationHintList.stories.d.ts +9 -0
- package/dist/esm/types/components/Form/index.d.ts +10 -0
- package/dist/esm/types/components/Form/useOptionBridge.d.ts +17 -0
- package/dist/esm/types/components/OtpInput/OtpInput.d.ts +17 -0
- package/dist/esm/types/components/OtpInput/OtpInput.stories.d.ts +15 -0
- package/dist/esm/types/components/OtpInput/OtpInputGroup.d.ts +25 -0
- package/dist/esm/types/components/OtpInput/index.d.ts +5 -0
- package/dist/esm/types/components/TextInput/TextInput.styles.d.ts +3 -0
- package/dist/esm/types/index.d.ts +5 -0
- package/dist/esm/types/theme/ThemeColorCoverageRuntime.stories.d.ts +10 -0
- package/dist/esm/types/utils/colors.d.ts +351 -267
- package/dist/index.d.ts +512 -269
- package/dist/index.js +3 -0
- package/dist/src/theme/global.css +2739 -2681
- package/dist/theme/ThemeColorCoverageRuntime.stories.js +91 -0
- package/dist/utils/colors.js +359 -267
- package/package.json +4 -2
- package/src/components/ActionButton/ActionButton.stories.tsx +6 -6
- package/src/components/ActionButton/ActionButton.styles.ts +1 -1
- package/src/components/AlertDialog/AlertDialog.stories.tsx +22 -0
- package/src/components/AlertDialog/AlertDialog.tsx +6 -6
- package/src/components/Avatar/Avatar.stories.tsx +1 -1
- package/src/components/Avatar/Avatar.styles.ts +1 -1
- package/src/components/Avatar/AvatarBase.tsx +1 -1
- package/src/components/Avatar/AvatarGroup.stories.tsx +1 -1
- package/src/components/Button/Buttons.stories.tsx +25 -17
- package/src/components/Calendar/Calendar.tsx +3 -3
- package/src/components/Checkbox/Checkbox.stories.tsx +35 -12
- package/src/components/Checkbox/Checkbox.tsx +7 -5
- package/src/components/Collapsible/Collapsible.styles.ts +1 -1
- package/src/components/DataTable/DataTable.tsx +2 -2
- package/src/components/Dialog/Dialog.stories.tsx +173 -0
- package/src/components/Dialog/Dialog.tsx +32 -15
- package/src/components/Dropdown/Dropdown.styles.ts +1 -1
- package/src/components/Dropdown/Dropdown.tsx +16 -14
- package/src/components/DropdownMenu/DropdownMenu.tsx +3 -3
- package/src/components/FocusedScrollView/FocusedScrollView.stories.tsx +10 -10
- package/src/components/Form/Field.tsx +160 -0
- package/src/components/Form/FieldMessage.tsx +38 -0
- package/src/components/Form/Form.docs.mdx +67 -0
- package/src/components/Form/Form.stories.tsx +490 -0
- package/src/components/Form/Form.tsx +185 -87
- package/src/components/Form/README.md +284 -0
- package/src/components/Form/ValidationHintList.stories.tsx +118 -0
- package/src/components/Form/ValidationHintList.tsx +82 -0
- package/src/components/Form/index.ts +28 -0
- package/src/components/Form/useOptionBridge.ts +55 -0
- package/src/components/InputFilter/InputFilter.stories.tsx +1 -1
- package/src/components/InputFilter/InputFilter.styles.ts +14 -1
- package/src/components/InputFilter/InputFilter.tsx +33 -28
- package/src/components/Label/Label.styles.ts +2 -2
- package/src/components/Label/Label.tsx +1 -1
- package/src/components/Menu/Menu.tsx +12 -12
- package/src/components/NumberInput/NumberInput.stories.tsx +1 -1
- package/src/components/OtpInput/OtpInput.stories.tsx +168 -0
- package/src/components/OtpInput/OtpInput.tsx +210 -0
- package/src/components/OtpInput/OtpInputGroup.tsx +74 -0
- package/src/components/OtpInput/index.ts +5 -0
- package/src/components/PasswordInput/PasswordInput.stories.tsx +1 -1
- package/src/components/Popover/Popover.tsx +1 -1
- package/src/components/RadioGroup/RadioGroup.stories.tsx +4 -4
- package/src/components/RadioGroup/RadioGroup.tsx +2 -1
- package/src/components/Search/Search.stories.tsx +1 -1
- package/src/components/Search/Search.tsx +6 -2
- package/src/components/Slider/Slider.stories.tsx +7 -7
- package/src/components/Slider/Slider.tsx +1 -1
- package/src/components/Switch/Switch.stories.tsx +4 -4
- package/src/components/Switch/Switch.styles.ts +1 -1
- package/src/components/Table/Table.tsx +5 -5
- package/src/components/Tabs/Tabs.stories.tsx +1 -1
- package/src/components/Tabs/Tabs.tsx +29 -18
- package/src/components/Text/Text.stories.tsx +1 -1
- package/src/components/Text/Text.tsx +1 -1
- package/src/components/TextArea/TextArea.stories.tsx +1 -1
- package/src/components/TextArea/TextArea.styles.ts +3 -3
- package/src/components/TextInput/TextInput.stories.tsx +7 -7
- package/src/components/TextInput/TextInput.styles.ts +42 -19
- package/src/components/TextInput/TextInput.tsx +3 -1
- package/src/components/Toast/Toast.stories.tsx +1 -1
- package/src/components/Toast/Toast.styles.tsx +7 -7
- package/src/components/Toast/Toast.tsx +5 -4
- package/src/components/Toast/Toaster.tsx +17 -20
- package/src/components/Tree/Tree.stories.tsx +1 -1
- package/src/components/Tree/TreeItem.tsx +1 -1
- package/src/index.ts +5 -0
- package/src/theme/THEME_MAPPING.md +36 -37
- package/src/theme/ThemeColorCoverageRuntime.stories.tsx +236 -0
- package/src/theme/direct-token-migration-plan.md +121 -0
- package/src/theme/figma-mcp-check-report.md +225 -0
- package/src/theme/figma-mcp-component-checklist.json +1250 -0
- package/src/theme/global.css +7 -3
- package/src/theme/presets/colors.js +173 -64
- package/src/theme/themes/skyller/baseline.css +0 -4
- package/src/theme/themes/variable-mapping.css +1064 -0
- package/src/theme/themes/variable.css +248 -230
- package/src/theme/themes/xspector/baseline.css +0 -4
- package/src/theme/themes/xspector/components/dropdown-menu.css +4 -4
- package/src/theme/themes/xspector/components/loading.css +2 -2
- package/src/theme/tokens/baseline.css +0 -3
- package/src/theme/tokens/color.css +36 -65
- package/src/theme/tokens/components/action-button.css +6 -6
- package/src/theme/tokens/components/button.css +189 -189
- package/src/theme/tokens/components/dropdown-menu.css +5 -5
- package/src/theme/tokens/components/footer.css +1 -1
- package/src/theme/tokens/components/loading.css +2 -2
- package/src/theme/tokens/components/switch.css +11 -11
- package/src/theme/tokens/typography.css +28 -28
- package/src/theme/tokens_old/baseline.css +13 -0
- package/src/theme/tokens_old/color.css +78 -0
- package/src/theme/tokens_old/components/action-button.css +127 -0
- package/src/theme/tokens_old/components/button.css +512 -0
- package/src/theme/tokens_old/components/dropdown-menu.css +27 -0
- package/src/theme/tokens_old/components/footer.css +9 -0
- package/src/theme/tokens_old/components/loading.css +11 -0
- package/src/theme/tokens_old/components/navbar.css +9 -0
- package/src/theme/tokens_old/components/progress-bar.css +8 -0
- package/src/theme/tokens_old/components/switch.css +29 -0
- package/src/theme/tokens_old/typography.css +199 -0
- package/src/theme/tokens_old/variables.css +28 -0
- package/src/theme/utils.js +172 -33
- package/src/utils/colors.ts +367 -278
- package/src/theme/themes/skyller/color.css +0 -79
- package/src/theme/themes/skyller/palette.css +0 -143
- package/src/theme/themes/skyller/state.css +0 -94
- package/src/theme/themes/skyller/transparent.css +0 -94
- package/src/theme/themes/xspector/color.css +0 -83
- package/src/theme/themes/xspector/palette.css +0 -142
- package/src/theme/themes/xspector/state.css +0 -94
- package/src/theme/themes/xspector/transparent.css +0 -93
- /package/src/theme/{tokens → tokens_old}/palette.css +0 -0
- /package/src/theme/{tokens → tokens_old}/state.css +0 -0
- /package/src/theme/{tokens → tokens_old}/transparent.css +0 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { CheckCircleIcon } from "@heroicons/react/24/outline";
|
|
3
|
+
import { cn } from "@/utils/cn";
|
|
4
|
+
|
|
5
|
+
export type ValidationHintState = "pending" | "valid" | "invalid";
|
|
6
|
+
export type ValidationHintMode = ValidationHintState[];
|
|
7
|
+
|
|
8
|
+
export type ValidationHintRule<TValues> = {
|
|
9
|
+
id: string;
|
|
10
|
+
label: string;
|
|
11
|
+
validate: (values: TValues) => boolean;
|
|
12
|
+
when?: (values: TValues) => boolean;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type ValidationHintListProps<TValues> = {
|
|
16
|
+
values: TValues;
|
|
17
|
+
rules: ValidationHintRule<TValues>[];
|
|
18
|
+
mode?: ValidationHintMode;
|
|
19
|
+
className?: string;
|
|
20
|
+
itemClassName?: string;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const resolveHintState = <TValues,>(
|
|
24
|
+
values: TValues,
|
|
25
|
+
rule: ValidationHintRule<TValues>,
|
|
26
|
+
): ValidationHintState => {
|
|
27
|
+
const shouldEvaluate = rule.when ? rule.when(values) : true;
|
|
28
|
+
if (!shouldEvaluate) return "pending";
|
|
29
|
+
|
|
30
|
+
return rule.validate(values) ? "valid" : "invalid";
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const hintTextStateClass: Record<ValidationHintState, string> = {
|
|
34
|
+
valid: "text-success",
|
|
35
|
+
invalid: "text-input-error",
|
|
36
|
+
pending: "text-text-g-contrast-medium",
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const hintIconStateClass: Record<ValidationHintState, string> = {
|
|
40
|
+
valid: "opacity-100",
|
|
41
|
+
invalid: "opacity-100",
|
|
42
|
+
pending: "opacity-40",
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const ValidationHintList = <TValues,>({
|
|
46
|
+
values,
|
|
47
|
+
rules,
|
|
48
|
+
mode = ["pending", "valid", "invalid"],
|
|
49
|
+
className,
|
|
50
|
+
itemClassName,
|
|
51
|
+
}: ValidationHintListProps<TValues>) => {
|
|
52
|
+
const enabledStates = new Set<ValidationHintState>(mode);
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<ul className={cn("mt-2 flex flex-col gap-3", className)}>
|
|
56
|
+
{rules.map((rule) => {
|
|
57
|
+
const state = resolveHintState(values, rule);
|
|
58
|
+
const normalizedState: ValidationHintState = enabledStates.has(state)
|
|
59
|
+
? state
|
|
60
|
+
: "pending";
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<li
|
|
64
|
+
key={rule.id}
|
|
65
|
+
className={cn(
|
|
66
|
+
"flex items-center gap-2 typography-small2",
|
|
67
|
+
hintTextStateClass[normalizedState],
|
|
68
|
+
itemClassName,
|
|
69
|
+
)}
|
|
70
|
+
>
|
|
71
|
+
<CheckCircleIcon
|
|
72
|
+
className={cn("size-4", hintIconStateClass[normalizedState])}
|
|
73
|
+
/>
|
|
74
|
+
<span>{rule.label}</span>
|
|
75
|
+
</li>
|
|
76
|
+
);
|
|
77
|
+
})}
|
|
78
|
+
</ul>
|
|
79
|
+
);
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export default ValidationHintList;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export {
|
|
2
|
+
Form,
|
|
3
|
+
createControlledForm,
|
|
4
|
+
createYupResolver,
|
|
5
|
+
useControlledForm,
|
|
6
|
+
} from "./Form";
|
|
7
|
+
export type {
|
|
8
|
+
ControlledFormFactoryOptions,
|
|
9
|
+
FormController,
|
|
10
|
+
FormProps,
|
|
11
|
+
UseControlledFormOptions,
|
|
12
|
+
} from "./Form";
|
|
13
|
+
export { Field } from "./Field";
|
|
14
|
+
export type { FieldProps } from "./Field";
|
|
15
|
+
export { FieldMessage } from "./FieldMessage";
|
|
16
|
+
export type { FieldMessageProps } from "./FieldMessage";
|
|
17
|
+
export { ValidationHintList } from "./ValidationHintList";
|
|
18
|
+
export type {
|
|
19
|
+
ValidationHintListProps,
|
|
20
|
+
ValidationHintMode,
|
|
21
|
+
ValidationHintRule,
|
|
22
|
+
ValidationHintState,
|
|
23
|
+
} from "./ValidationHintList";
|
|
24
|
+
export { useOptionBridge } from "./useOptionBridge";
|
|
25
|
+
export type {
|
|
26
|
+
OptionLike,
|
|
27
|
+
UseOptionBridgeOptions,
|
|
28
|
+
} from "./useOptionBridge";
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
2
|
+
|
|
3
|
+
type OptionValue = string | number;
|
|
4
|
+
|
|
5
|
+
export type OptionLike<TValue extends OptionValue = string> = {
|
|
6
|
+
value: TValue;
|
|
7
|
+
label?: string;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export type UseOptionBridgeOptions<
|
|
11
|
+
TValue extends OptionValue,
|
|
12
|
+
TOption extends OptionLike<TValue>,
|
|
13
|
+
> = {
|
|
14
|
+
options: TOption[];
|
|
15
|
+
loading?: boolean;
|
|
16
|
+
buildFallbackOption?: (value: TValue, loading: boolean) => TOption;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const useOptionBridge = <
|
|
20
|
+
TValue extends OptionValue = string,
|
|
21
|
+
TOption extends OptionLike<TValue> = OptionLike<TValue>,
|
|
22
|
+
>({
|
|
23
|
+
options,
|
|
24
|
+
loading = false,
|
|
25
|
+
buildFallbackOption,
|
|
26
|
+
}: UseOptionBridgeOptions<TValue, TOption>) => {
|
|
27
|
+
const optionsByValue = useMemo(() => {
|
|
28
|
+
return new Map<TValue, TOption>(
|
|
29
|
+
options.map((option) => [option.value as TValue, option])
|
|
30
|
+
);
|
|
31
|
+
}, [options]);
|
|
32
|
+
|
|
33
|
+
const toOption = (value: TValue | null | undefined) => {
|
|
34
|
+
if (value === null || value === undefined) return undefined;
|
|
35
|
+
return optionsByValue.get(value);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const toValue = (option: TOption | null | undefined) => {
|
|
39
|
+
return option?.value;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const toOptionWithFallback = (value: TValue | null | undefined) => {
|
|
43
|
+
if (value === null || value === undefined) return undefined;
|
|
44
|
+
return toOption(value) ?? buildFallbackOption?.(value, loading);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
toOption,
|
|
49
|
+
toOptionWithFallback,
|
|
50
|
+
toValue,
|
|
51
|
+
optionsByValue,
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export default useOptionBridge;
|
|
@@ -30,7 +30,7 @@ export const filterIconVariant = cva(
|
|
|
30
30
|
},
|
|
31
31
|
rounded: {
|
|
32
32
|
none: "rounded-r-none",
|
|
33
|
-
normal: "
|
|
33
|
+
normal: "",
|
|
34
34
|
full: "rounded-r-full",
|
|
35
35
|
},
|
|
36
36
|
error: {
|
|
@@ -56,12 +56,25 @@ export const filterIconVariant = cva(
|
|
|
56
56
|
},
|
|
57
57
|
disabled: {
|
|
58
58
|
true: [
|
|
59
|
+
"cursor-default pointer-events-none",
|
|
59
60
|
"border-l-input-disable-stroke",
|
|
60
61
|
"fill-input-disable-stroke",
|
|
61
62
|
"stroke-input-disable-stroke",
|
|
62
63
|
],
|
|
63
64
|
},
|
|
64
65
|
},
|
|
66
|
+
compoundVariants: [
|
|
67
|
+
{
|
|
68
|
+
rounded: "normal",
|
|
69
|
+
size: "sm",
|
|
70
|
+
className: "rounded-r-sm",
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
rounded: "normal",
|
|
74
|
+
size: ["md", "lg"],
|
|
75
|
+
className: "rounded-r-md",
|
|
76
|
+
},
|
|
77
|
+
],
|
|
65
78
|
defaultVariants: {
|
|
66
79
|
size: "md",
|
|
67
80
|
rounded: "normal",
|
|
@@ -78,7 +78,7 @@ const InputFilter = forwardRef<HTMLInputElement, InputFilterProps>(
|
|
|
78
78
|
optionContainerClassName,
|
|
79
79
|
...props
|
|
80
80
|
},
|
|
81
|
-
ref
|
|
81
|
+
ref,
|
|
82
82
|
) => {
|
|
83
83
|
const _id = id || `${label}-select`;
|
|
84
84
|
|
|
@@ -117,19 +117,19 @@ const InputFilter = forwardRef<HTMLInputElement, InputFilterProps>(
|
|
|
117
117
|
clearMismatchValues(event as any);
|
|
118
118
|
}
|
|
119
119
|
},
|
|
120
|
-
[onChangeText]
|
|
120
|
+
[onChangeText],
|
|
121
121
|
);
|
|
122
122
|
|
|
123
123
|
const handleOptionClick = useCallback(
|
|
124
124
|
(option: Options) => {
|
|
125
125
|
const isSelected = selectedOptions.some(
|
|
126
|
-
(selected) => selected.value === option.value
|
|
126
|
+
(selected) => selected.value === option.value,
|
|
127
127
|
);
|
|
128
128
|
let newSelectedOptions = [...selectedOptions];
|
|
129
129
|
|
|
130
130
|
if (isSelected) {
|
|
131
131
|
newSelectedOptions = newSelectedOptions.filter(
|
|
132
|
-
(selected) => selected.value !== option.value
|
|
132
|
+
(selected) => selected.value !== option.value,
|
|
133
133
|
);
|
|
134
134
|
} else {
|
|
135
135
|
newSelectedOptions.push(option);
|
|
@@ -137,11 +137,11 @@ const InputFilter = forwardRef<HTMLInputElement, InputFilterProps>(
|
|
|
137
137
|
|
|
138
138
|
setSelectedOptions(newSelectedOptions);
|
|
139
139
|
setTextValue(
|
|
140
|
-
newSelectedOptions.map((option) => option.label).join(", ")
|
|
140
|
+
newSelectedOptions.map((option) => option.label).join(", "),
|
|
141
141
|
);
|
|
142
142
|
onSelect?.(newSelectedOptions);
|
|
143
143
|
},
|
|
144
|
-
[selectedOptions, onSelect]
|
|
144
|
+
[selectedOptions, onSelect],
|
|
145
145
|
);
|
|
146
146
|
|
|
147
147
|
const optionsFiltered = useMemo(() => {
|
|
@@ -151,7 +151,7 @@ const InputFilter = forwardRef<HTMLInputElement, InputFilterProps>(
|
|
|
151
151
|
return options.filter(
|
|
152
152
|
(option) =>
|
|
153
153
|
!filterMode ||
|
|
154
|
-
option.label?.toLowerCase().includes(filterText?.toLowerCase())
|
|
154
|
+
option.label?.toLowerCase().includes(filterText?.toLowerCase()),
|
|
155
155
|
);
|
|
156
156
|
}, [options, filterMode, textValue]);
|
|
157
157
|
|
|
@@ -167,8 +167,8 @@ const InputFilter = forwardRef<HTMLInputElement, InputFilterProps>(
|
|
|
167
167
|
return (
|
|
168
168
|
<ul
|
|
169
169
|
className={cn(
|
|
170
|
-
"absolute mt-1 w-full bg-
|
|
171
|
-
optionContainerClassName
|
|
170
|
+
"absolute text-text-g-contrast-high mt-1 w-full bg-modal-surface border border-modal-surface text-text-contrast-low rounded-md shadow-md z-10 max-h-60 overflow-y-auto",
|
|
171
|
+
optionContainerClassName,
|
|
172
172
|
)}
|
|
173
173
|
>
|
|
174
174
|
{optionsFiltered.map((option) => {
|
|
@@ -179,12 +179,13 @@ const InputFilter = forwardRef<HTMLInputElement, InputFilterProps>(
|
|
|
179
179
|
value: option.value,
|
|
180
180
|
label: option.label,
|
|
181
181
|
handleOnClick: () => handleOptionClick(option),
|
|
182
|
-
className: `p-4 typography-subtitle4 hover:bg-gray-100 cursor-pointer flex items-center gap-3 ${
|
|
183
|
-
(
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
182
|
+
className: `p-4 typography-subtitle4 hover:bg-gray-100 cursor-pointer flex items-center gap-3 ${
|
|
183
|
+
selectedOptions.some(
|
|
184
|
+
(selected) => selected.value === option.value,
|
|
185
|
+
)
|
|
186
|
+
? "bg-gray-200"
|
|
187
|
+
: ""
|
|
188
|
+
}`,
|
|
188
189
|
})}
|
|
189
190
|
</Fragment>
|
|
190
191
|
);
|
|
@@ -193,16 +194,17 @@ const InputFilter = forwardRef<HTMLInputElement, InputFilterProps>(
|
|
|
193
194
|
<li
|
|
194
195
|
key={option.value}
|
|
195
196
|
onMouseDown={() => handleOptionClick(option)}
|
|
196
|
-
className={`p-4 typography-subtitle4 hover:bg-primary-hover-bg cursor-pointer flex items-center gap-3 ${
|
|
197
|
-
(
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
197
|
+
className={`p-4 text-text-g-contrast-high typography-subtitle4 hover:bg-primary-hover-bg cursor-pointer flex items-center gap-3 ${
|
|
198
|
+
selectedOptions.some(
|
|
199
|
+
(selected) => selected.value === option.value,
|
|
200
|
+
)
|
|
201
|
+
? "bg-modal-highlight"
|
|
202
|
+
: ""
|
|
203
|
+
}`}
|
|
202
204
|
>
|
|
203
205
|
<Checkbox
|
|
204
206
|
checked={selectedOptions.some(
|
|
205
|
-
(selected) => selected.value === option.value
|
|
207
|
+
(selected) => selected.value === option.value,
|
|
206
208
|
)}
|
|
207
209
|
/>
|
|
208
210
|
{option.label}
|
|
@@ -223,14 +225,14 @@ const InputFilter = forwardRef<HTMLInputElement, InputFilterProps>(
|
|
|
223
225
|
setIsFocused(true);
|
|
224
226
|
props?.onFocus?.(e);
|
|
225
227
|
},
|
|
226
|
-
[props?.onFocus]
|
|
228
|
+
[props?.onFocus],
|
|
227
229
|
);
|
|
228
230
|
|
|
229
231
|
const clearMismatchValues = useCallback(
|
|
230
232
|
(e: React.FocusEvent<HTMLInputElement, Element>) => {
|
|
231
233
|
const matchSelectedValues = optionsFiltered.filter(
|
|
232
234
|
(opt) =>
|
|
233
|
-
opt.value === e.target?.value || opt.label === e.target?.value
|
|
235
|
+
opt.value === e.target?.value || opt.label === e.target?.value,
|
|
234
236
|
);
|
|
235
237
|
|
|
236
238
|
if (keyCode.current === "Enter") {
|
|
@@ -239,11 +241,11 @@ const InputFilter = forwardRef<HTMLInputElement, InputFilterProps>(
|
|
|
239
241
|
|
|
240
242
|
setSelectedOptions(matchSelectedValues);
|
|
241
243
|
setTextValue(
|
|
242
|
-
matchSelectedValues.map((option) => option.label).join(", ")
|
|
244
|
+
matchSelectedValues.map((option) => option.label).join(", "),
|
|
243
245
|
);
|
|
244
246
|
onSelect?.(matchSelectedValues);
|
|
245
247
|
},
|
|
246
|
-
[optionsFiltered, textValue]
|
|
248
|
+
[optionsFiltered, textValue],
|
|
247
249
|
);
|
|
248
250
|
|
|
249
251
|
const handleOnKeyDown = useCallback(
|
|
@@ -251,7 +253,7 @@ const InputFilter = forwardRef<HTMLInputElement, InputFilterProps>(
|
|
|
251
253
|
keyCode.current = e.code;
|
|
252
254
|
props?.onKeyDown?.(e);
|
|
253
255
|
},
|
|
254
|
-
[props?.onKeyDown]
|
|
256
|
+
[props?.onKeyDown],
|
|
255
257
|
);
|
|
256
258
|
|
|
257
259
|
const filterIconClassName = filterIconVariant({
|
|
@@ -261,6 +263,8 @@ const InputFilter = forwardRef<HTMLInputElement, InputFilterProps>(
|
|
|
261
263
|
active: !!values.length,
|
|
262
264
|
disabled,
|
|
263
265
|
});
|
|
266
|
+
const filterIconSizeClassName =
|
|
267
|
+
size === "lg" ? "size-8" : size === "md" ? "size-[22px]" : "size-[18px]";
|
|
264
268
|
|
|
265
269
|
return (
|
|
266
270
|
<div
|
|
@@ -277,6 +281,7 @@ const InputFilter = forwardRef<HTMLInputElement, InputFilterProps>(
|
|
|
277
281
|
color="inherit"
|
|
278
282
|
stroke="inherit"
|
|
279
283
|
fill="transparent"
|
|
284
|
+
className={filterIconSizeClassName}
|
|
280
285
|
/>
|
|
281
286
|
}
|
|
282
287
|
classes={{ endIconWrapper: filterIconClassName }}
|
|
@@ -305,7 +310,7 @@ const InputFilter = forwardRef<HTMLInputElement, InputFilterProps>(
|
|
|
305
310
|
{isFocused && renderOptions()}
|
|
306
311
|
</div>
|
|
307
312
|
);
|
|
308
|
-
}
|
|
313
|
+
},
|
|
309
314
|
);
|
|
310
315
|
|
|
311
316
|
export { InputFilter };
|
|
@@ -2,7 +2,7 @@ import { cva } from "class-variance-authority";
|
|
|
2
2
|
|
|
3
3
|
export const labelVariant = cva(
|
|
4
4
|
[
|
|
5
|
-
"block cursor-pointer duration-450 transition-all px-[2px] text-
|
|
5
|
+
"block cursor-pointer duration-450 transition-all px-[2px] text-input-default-text peer-focus:text-input-text-active",
|
|
6
6
|
],
|
|
7
7
|
{
|
|
8
8
|
variants: {
|
|
@@ -57,5 +57,5 @@ export const labelVariant = cva(
|
|
|
57
57
|
error: false,
|
|
58
58
|
isFloatable: false,
|
|
59
59
|
},
|
|
60
|
-
}
|
|
60
|
+
},
|
|
61
61
|
);
|
|
@@ -56,15 +56,15 @@ export type MenuProps = {
|
|
|
56
56
|
export const Menu = forwardRef<HTMLDivElement, MenuProps>(
|
|
57
57
|
(
|
|
58
58
|
{ items, selectedValues = [], onSelect, className, style, isAbove = false },
|
|
59
|
-
ref
|
|
59
|
+
ref,
|
|
60
60
|
) => {
|
|
61
61
|
return (
|
|
62
62
|
<div
|
|
63
63
|
ref={ref}
|
|
64
64
|
className={cn(
|
|
65
|
-
"z-50 min-w-[154px] overflow-hidden rounded-md bg-
|
|
66
|
-
"border border-
|
|
67
|
-
className
|
|
65
|
+
"z-50 min-w-[154px] overflow-hidden rounded-md bg-modal-surface text-text-g-contrast-high",
|
|
66
|
+
"border border-modal-surface",
|
|
67
|
+
className,
|
|
68
68
|
)}
|
|
69
69
|
style={{
|
|
70
70
|
boxShadow: "var(--dropdown-menu-shadow)",
|
|
@@ -115,7 +115,7 @@ export const Menu = forwardRef<HTMLDivElement, MenuProps>(
|
|
|
115
115
|
})}
|
|
116
116
|
</div>
|
|
117
117
|
);
|
|
118
|
-
}
|
|
118
|
+
},
|
|
119
119
|
);
|
|
120
120
|
|
|
121
121
|
Menu.displayName = "Menu";
|
|
@@ -169,7 +169,7 @@ export const MenuItem = forwardRef<HTMLDivElement, MenuItemProps>(
|
|
|
169
169
|
"pointer-events-none opacity-50": option.disabled,
|
|
170
170
|
"text-red-500": option.danger,
|
|
171
171
|
},
|
|
172
|
-
className
|
|
172
|
+
className,
|
|
173
173
|
)}
|
|
174
174
|
onMouseDown={option.disabled ? undefined : onSelect}
|
|
175
175
|
>
|
|
@@ -178,7 +178,7 @@ export const MenuItem = forwardRef<HTMLDivElement, MenuItemProps>(
|
|
|
178
178
|
{option.label}
|
|
179
179
|
</div>
|
|
180
180
|
);
|
|
181
|
-
}
|
|
181
|
+
},
|
|
182
182
|
);
|
|
183
183
|
|
|
184
184
|
MenuItem.displayName = "MenuItem";
|
|
@@ -196,11 +196,11 @@ export const MenuSeparator = forwardRef<HTMLDivElement, MenuSeparatorProps>(
|
|
|
196
196
|
ref={ref}
|
|
197
197
|
className={cn(
|
|
198
198
|
"-mx-2 my-2 h-px bg-[var(--dropdown-menu-seperator-bg)]",
|
|
199
|
-
className
|
|
199
|
+
className,
|
|
200
200
|
)}
|
|
201
201
|
/>
|
|
202
202
|
);
|
|
203
|
-
}
|
|
203
|
+
},
|
|
204
204
|
);
|
|
205
205
|
|
|
206
206
|
MenuSeparator.displayName = "MenuSeparator";
|
|
@@ -218,14 +218,14 @@ export const MenuLabel = forwardRef<HTMLDivElement, MenuLabelProps>(
|
|
|
218
218
|
<div
|
|
219
219
|
ref={ref}
|
|
220
220
|
className={cn(
|
|
221
|
-
"px-3 py-2 typography-small4 text-text-
|
|
222
|
-
className
|
|
221
|
+
"px-3 py-2 typography-small4 text-text-g-contrast-medium",
|
|
222
|
+
className,
|
|
223
223
|
)}
|
|
224
224
|
>
|
|
225
225
|
{children}
|
|
226
226
|
</div>
|
|
227
227
|
);
|
|
228
|
-
}
|
|
228
|
+
},
|
|
229
229
|
);
|
|
230
230
|
|
|
231
231
|
MenuLabel.displayName = "MenuLabel";
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
3
|
+
import * as yup from "yup";
|
|
4
|
+
import Button from "@/components/Button/Button";
|
|
5
|
+
import { Field } from "@/components/Form/Field";
|
|
6
|
+
import { Form } from "@/components/Form/Form";
|
|
7
|
+
import OtpInput from "./OtpInput";
|
|
8
|
+
import { OtpInputGroup } from "./OtpInputGroup";
|
|
9
|
+
|
|
10
|
+
const meta: Meta = {
|
|
11
|
+
title: "Components/OtpInput",
|
|
12
|
+
tags: ["autodocs"],
|
|
13
|
+
parameters: {
|
|
14
|
+
layout: "centered",
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export default meta;
|
|
19
|
+
type Story = StoryObj;
|
|
20
|
+
|
|
21
|
+
export const Basic = {
|
|
22
|
+
render: () => {
|
|
23
|
+
const [value, setValue] = useState("");
|
|
24
|
+
const [completed, setCompleted] = useState("-");
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<div className="w-[420px] max-w-full rounded-md bg-bg-bg2 p-4">
|
|
28
|
+
<div className="flex flex-col gap-3">
|
|
29
|
+
<OtpInput
|
|
30
|
+
value={value}
|
|
31
|
+
onChange={setValue}
|
|
32
|
+
onComplete={(code) => setCompleted(code)}
|
|
33
|
+
/>
|
|
34
|
+
<div className="text-xs text-text-g-contrast-medium">
|
|
35
|
+
Value: {value || "-"}
|
|
36
|
+
</div>
|
|
37
|
+
<div className="text-xs text-text-g-contrast-medium">
|
|
38
|
+
Last completed: {completed}
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
);
|
|
43
|
+
},
|
|
44
|
+
} satisfies Story;
|
|
45
|
+
|
|
46
|
+
type OtpFormValues = {
|
|
47
|
+
otp: string;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const otpSchema = yup.object({
|
|
51
|
+
otp: yup
|
|
52
|
+
.string()
|
|
53
|
+
.required("OTP is required")
|
|
54
|
+
.length(6, "OTP must be 6 digits"),
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
export const WithFormField = {
|
|
58
|
+
render: () => {
|
|
59
|
+
return (
|
|
60
|
+
<div className="w-[420px] max-w-full rounded-md bg-bg-bg2 p-4">
|
|
61
|
+
<Form<OtpFormValues>
|
|
62
|
+
defaultValues={{ otp: "" }}
|
|
63
|
+
validationSchema={otpSchema}
|
|
64
|
+
mode="onTouched"
|
|
65
|
+
className="flex flex-col gap-3"
|
|
66
|
+
onSubmit={(values) => {
|
|
67
|
+
// eslint-disable-next-line no-console
|
|
68
|
+
console.log("OTP submit:", values);
|
|
69
|
+
}}
|
|
70
|
+
>
|
|
71
|
+
{({ formState }) => (
|
|
72
|
+
<>
|
|
73
|
+
<Field<
|
|
74
|
+
OtpFormValues,
|
|
75
|
+
"otp",
|
|
76
|
+
React.ComponentProps<typeof OtpInput>
|
|
77
|
+
>
|
|
78
|
+
name="otp"
|
|
79
|
+
component={OtpInput}
|
|
80
|
+
componentProps={{
|
|
81
|
+
autoFocus: true,
|
|
82
|
+
}}
|
|
83
|
+
invalidPropName="invalid"
|
|
84
|
+
/>
|
|
85
|
+
<Button
|
|
86
|
+
type="submit"
|
|
87
|
+
disabled={!formState.isValid || formState.isSubmitting}
|
|
88
|
+
isLoading={formState.isSubmitting}
|
|
89
|
+
>
|
|
90
|
+
Verify OTP
|
|
91
|
+
</Button>
|
|
92
|
+
</>
|
|
93
|
+
)}
|
|
94
|
+
</Form>
|
|
95
|
+
</div>
|
|
96
|
+
);
|
|
97
|
+
},
|
|
98
|
+
} satisfies Story;
|
|
99
|
+
|
|
100
|
+
export const GroupWithLabelAndError = {
|
|
101
|
+
render: () => {
|
|
102
|
+
const [value, setValue] = useState("");
|
|
103
|
+
const isInvalid = value.length > 0 && value.length < 6;
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<div className="w-[420px] max-w-full rounded-md bg-bg-bg2 p-4">
|
|
107
|
+
<OtpInputGroup
|
|
108
|
+
id="otp-group"
|
|
109
|
+
label="Verification code"
|
|
110
|
+
required
|
|
111
|
+
value={value}
|
|
112
|
+
onChange={setValue}
|
|
113
|
+
helperText="Enter the 6-digit code from your authenticator app"
|
|
114
|
+
error={isInvalid}
|
|
115
|
+
errorMessage={isInvalid ? "OTP must be 6 digits" : undefined}
|
|
116
|
+
autoFocus
|
|
117
|
+
/>
|
|
118
|
+
</div>
|
|
119
|
+
);
|
|
120
|
+
},
|
|
121
|
+
} satisfies Story;
|
|
122
|
+
|
|
123
|
+
export const GroupWithFormField = {
|
|
124
|
+
render: () => {
|
|
125
|
+
return (
|
|
126
|
+
<div className="w-[420px] max-w-full rounded-md bg-bg-bg2 p-4">
|
|
127
|
+
<Form<OtpFormValues>
|
|
128
|
+
defaultValues={{ otp: "" }}
|
|
129
|
+
validationSchema={otpSchema}
|
|
130
|
+
mode="onTouched"
|
|
131
|
+
className="flex flex-col gap-3"
|
|
132
|
+
onSubmit={(values) => {
|
|
133
|
+
// eslint-disable-next-line no-console
|
|
134
|
+
console.log("OTP group submit:", values);
|
|
135
|
+
}}
|
|
136
|
+
>
|
|
137
|
+
{({ formState }) => (
|
|
138
|
+
<>
|
|
139
|
+
<Field<
|
|
140
|
+
OtpFormValues,
|
|
141
|
+
"otp",
|
|
142
|
+
React.ComponentProps<typeof OtpInputGroup>
|
|
143
|
+
>
|
|
144
|
+
name="otp"
|
|
145
|
+
component={OtpInputGroup}
|
|
146
|
+
componentProps={{
|
|
147
|
+
id: "otp-field-group",
|
|
148
|
+
label: "Verification code",
|
|
149
|
+
helperText: "Paste is supported",
|
|
150
|
+
required: true,
|
|
151
|
+
autoFocus: true,
|
|
152
|
+
}}
|
|
153
|
+
invalidPropName="error"
|
|
154
|
+
/>
|
|
155
|
+
<Button
|
|
156
|
+
type="submit"
|
|
157
|
+
disabled={!formState.isValid || formState.isSubmitting}
|
|
158
|
+
isLoading={formState.isSubmitting}
|
|
159
|
+
>
|
|
160
|
+
Verify OTP
|
|
161
|
+
</Button>
|
|
162
|
+
</>
|
|
163
|
+
)}
|
|
164
|
+
</Form>
|
|
165
|
+
</div>
|
|
166
|
+
);
|
|
167
|
+
},
|
|
168
|
+
} satisfies Story;
|