@janbox/storefront-ui 2.0.29 → 2.0.30

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.
Files changed (76) hide show
  1. package/README.md +681 -0
  2. package/dist/lib/accordion/README.md +81 -0
  3. package/dist/lib/avatar/README.md +74 -0
  4. package/dist/lib/badge/README.md +58 -0
  5. package/dist/lib/box/README.md +69 -0
  6. package/dist/lib/breadcrumbs/README.md +70 -0
  7. package/dist/lib/button/README.md +115 -0
  8. package/dist/lib/cascader/README.md +128 -0
  9. package/dist/lib/checkbox/README.md +74 -0
  10. package/dist/lib/checkbox/checkbox.js +107 -12
  11. package/dist/lib/checkbox/types.d.ts +2 -2
  12. package/dist/lib/chip/README.md +72 -0
  13. package/dist/lib/collapse/README.md +78 -0
  14. package/dist/lib/container/README.md +59 -0
  15. package/dist/lib/count-up/README.md +52 -0
  16. package/dist/lib/countdown-timer/README.md +77 -0
  17. package/dist/lib/date-picker/README.md +94 -0
  18. package/dist/lib/date-picker/date-picker.js +2 -1
  19. package/dist/lib/dialog/README.md +109 -0
  20. package/dist/lib/drawer/README.md +97 -0
  21. package/dist/lib/filter-panel/README.md +146 -0
  22. package/dist/lib/flag/README.md +58 -0
  23. package/dist/lib/flexbox/README.md +59 -0
  24. package/dist/lib/floating/README.md +109 -0
  25. package/dist/lib/form-helper-text/README.md +54 -0
  26. package/dist/lib/form-label/README.md +50 -0
  27. package/dist/lib/grid/README.md +72 -0
  28. package/dist/lib/highlight-words/README.md +64 -0
  29. package/dist/lib/highlight-words/highlight-words.js +1 -2
  30. package/dist/lib/icon/README.md +69 -0
  31. package/dist/lib/icon-button/README.md +92 -0
  32. package/dist/lib/image/README.md +80 -0
  33. package/dist/lib/input/README.md +118 -0
  34. package/dist/lib/input-mask/README.md +88 -0
  35. package/dist/lib/input-number/README.md +92 -0
  36. package/dist/lib/input-range/README.md +85 -0
  37. package/dist/lib/lightbox/README.md +107 -0
  38. package/dist/lib/linear-progress/README.md +54 -0
  39. package/dist/lib/link/README.md +67 -0
  40. package/dist/lib/loading/README.md +65 -0
  41. package/dist/lib/marquee/README.md +83 -0
  42. package/dist/lib/menu/README.md +92 -0
  43. package/dist/lib/multiple-select/README.md +108 -0
  44. package/dist/lib/nav-link/README.md +61 -0
  45. package/dist/lib/notifications/README.md +103 -0
  46. package/dist/lib/otp-input/README.md +71 -0
  47. package/dist/lib/pagination/README.md +84 -0
  48. package/dist/lib/phone-input/README.md +80 -0
  49. package/dist/lib/popover/README.md +93 -0
  50. package/dist/lib/price-label/README.md +78 -0
  51. package/dist/lib/primitive/README.md +80 -0
  52. package/dist/lib/progress/README.md +50 -0
  53. package/dist/lib/radio-button/README.md +89 -0
  54. package/dist/lib/radio-button/radio-button.js +98 -7
  55. package/dist/lib/ripple-effect/README.md +66 -0
  56. package/dist/lib/select/README.md +124 -0
  57. package/dist/lib/star-rating/README.md +67 -0
  58. package/dist/lib/stepper/README.md +99 -0
  59. package/dist/lib/suspense-query/README.md +78 -0
  60. package/dist/lib/swiper/README.md +99 -0
  61. package/dist/lib/switch/README.md +73 -0
  62. package/dist/lib/switch/switch.d.ts +1 -1
  63. package/dist/lib/switch/switch.js +110 -11
  64. package/dist/lib/table/README.md +115 -0
  65. package/dist/lib/tabs/README.md +103 -0
  66. package/dist/lib/text/README.md +59 -0
  67. package/dist/lib/textarea/README.md +76 -0
  68. package/dist/lib/time-picker/README.md +100 -0
  69. package/dist/lib/tooltip/README.md +106 -0
  70. package/dist/lib/unordered-list/README.md +85 -0
  71. package/dist/lib/video/README.md +88 -0
  72. package/package.json +5 -5
  73. package/dist/lib/checkbox/checkbox.module.scss.js +0 -23
  74. package/dist/lib/radio-button/radio-button.module.scss.js +0 -17
  75. package/dist/lib/switch/switch.module.scss.js +0 -14
  76. package/dist/style.css +0 -823
@@ -0,0 +1,80 @@
1
+ # Primitive
2
+
3
+ Một polymorphic base component cho phép render HTML element động với hỗ trợ responsive styling qua `sx` prop. Đây là nền tảng để xây dựng các component khác trong design system, cho phép thay đổi loại element qua `as` prop trong khi vẫn duy trì khả năng styling nhất quán.
4
+
5
+ ## Import
6
+
7
+ ```tsx
8
+ import { Primitive } from '@janbox/storefront-ui';
9
+ ```
10
+
11
+ ## Props
12
+
13
+ | Prop | Type | Default | Description |
14
+ |------|------|---------|-------------|
15
+ | `as` | `HTMLElementType` | `div` | Loại HTML element cần render (e.g., `'div'`, `'button'`, `'span'`, `'a'`). |
16
+ | `sx` | `ToResponsiveProps<StyledCSS>` | - | Responsive style object hỗ trợ breakpoint-specific styles (`sm`, `md`, `lg`). Nhận Emotion CSS properties với responsive variants. |
17
+ | `className` | `string` | - | CSS class names bổ sung cho element. |
18
+ | `children` | `React.ReactNode` | - | Child elements render bên trong component. |
19
+
20
+ ## Examples
21
+
22
+ ### Basic usage — render mặc định là div
23
+
24
+ ```tsx
25
+ <Primitive sx={{ padding: 16, backgroundColor: 'lightblue' }}>
26
+ Default div element
27
+ </Primitive>
28
+ ```
29
+
30
+ ### Render as button với responsive styles
31
+
32
+ ```tsx
33
+ <Primitive
34
+ as="button"
35
+ sx={{
36
+ padding: 12,
37
+ fontSize: 14,
38
+ sm: { padding: 16, fontSize: 16 },
39
+ md: { padding: 20, fontSize: 18 }
40
+ }}
41
+ onClick={() => console.log('clicked')}
42
+ >
43
+ Responsive Button
44
+ </Primitive>
45
+ ```
46
+
47
+ ### Render as anchor link với custom styles
48
+
49
+ ```tsx
50
+ <Primitive
51
+ as="a"
52
+ href="https://example.com"
53
+ sx={{
54
+ color: 'blue',
55
+ textDecoration: 'none',
56
+ '&:hover': { textDecoration: 'underline' }
57
+ }}
58
+ >
59
+ Click me
60
+ </Primitive>
61
+ ```
62
+
63
+ ## Related Components
64
+
65
+ - [Box](../box/README.md)
66
+ - [Flexbox](../flexbox/README.md)
67
+ - [Grid](../grid/README.md)
68
+ - [TableRow](../tablerow/README.md)
69
+ - [ListItem](../listitem/README.md)
70
+
71
+ ## Dependencies
72
+
73
+ - `@emotion/react`
74
+ - `react`
75
+
76
+ ## See Also
77
+
78
+ - [Component Source](./index.ts)
79
+ - [Storybook Stories](./primitive.stories.tsx)
80
+ - [Main README](../../README.md)
@@ -0,0 +1,50 @@
1
+ # Progress
2
+
3
+ A progress bar component that visually represents a completion percentage. Supports both horizontal and vertical orientations with smooth animated fill transitions.
4
+
5
+ ## Import
6
+
7
+ ```tsx
8
+ import { Progress } from '@janbox/storefront-ui';
9
+ ```
10
+
11
+ ## Props
12
+
13
+ | Prop | Type | Default | Description |
14
+ |------|------|---------|-------------|
15
+ | `value*` | `number` | - | The progress percentage (0–100). Controls the width (horizontal) or height (vertical) of the filled indicator. |
16
+ | `direction` | `'horizontal' \| 'vertical'` | `'horizontal'` | Orientation of the progress bar. |
17
+ | `activeProps` | `ComponentProps<'div'>` | `{}` | Props forwarded to the inner indicator element (the filled portion). Useful for custom styling or event handlers on the fill bar. |
18
+ | `children` | `React.ReactNode` | - | Optional content rendered inside the active/fill indicator element. |
19
+
20
+ *Required props
21
+
22
+ ## Examples
23
+
24
+ ### Default horizontal at 50%
25
+
26
+ ```tsx
27
+ <Progress value={50} style={{ width: 400 }} />
28
+ ```
29
+
30
+ ### Full completion
31
+
32
+ ```tsx
33
+ <Progress value={100} style={{ width: 400 }} />
34
+ ```
35
+
36
+ ### Vertical progress bar at 60%
37
+
38
+ ```tsx
39
+ <Progress value={60} direction="vertical" style={{ width: 8, height: 200 }} />
40
+ ```
41
+
42
+ ## Dependencies
43
+
44
+ - @emotion/react
45
+
46
+ ## See Also
47
+
48
+ - [Component Source](./index.ts)
49
+ - [Storybook Stories](./progress.stories.tsx)
50
+ - [Main README](../../README.md)
@@ -0,0 +1,89 @@
1
+ # RadioButton
2
+
3
+ Component radio input được styled tùy chỉnh, render một label wrapper với checkmark visual. Hỗ trợ color variants cho brand và semantic theming, và chấp nhận tất cả các HTML input[type="radio"] attributes chuẩn.
4
+
5
+ ## Import
6
+
7
+ ```tsx
8
+ import { RadioButton } from '@janbox/storefront-ui';
9
+ ```
10
+
11
+ ## Props
12
+
13
+ | Prop | Type | Default | Description |
14
+ |------|------|---------|-------------|
15
+ | `color` | `ColorVariant` | `'primary'` | Điều khiển màu accent của radio button. Map tới design token color shades qua getColorShadesByVariant. |
16
+ | `containerProps` | `PrimitiveProps<JSX.IntrinsicElements['label']>` | - | Props được forward tới outer label element (Primitive as='label'). Hỗ trợ sx cho responsive styles và tất cả standard label HTML attributes. |
17
+ | `children` | `React.ReactNode` | - | Nội dung label được render bên cạnh radio button checkmark. |
18
+ | `className` | `string` | - | CSS class names bổ sung được apply vào inner input element. |
19
+ | `disabled` | `boolean` | `false` | Disable radio input, ngăn chặn interaction. |
20
+ | `checked` | `boolean` | - | Controlled checked state của radio input. |
21
+ | `defaultChecked` | `boolean` | - | Uncontrolled initial checked state của radio input. |
22
+ | `onChange` | `React.ChangeEventHandler<HTMLInputElement>` | - | Change event handler được gọi khi radio selection thay đổi. |
23
+ | `name` | `string` | - | Nhóm các radio buttons có cùng name để chỉ một button có thể được chọn tại một thời điểm. |
24
+ | `value` | `string \| number \| readonly string[]` | - | Value được liên kết với radio button này khi submit trong form. |
25
+ | `ref` | `React.Ref<HTMLInputElement>` | - | Ref được forward tới underlying input element. |
26
+
27
+ ## Examples
28
+
29
+ ### Default radio button
30
+
31
+ ```tsx
32
+ <RadioButton onChange={handleChange} />
33
+ ```
34
+
35
+ ### Grouped radio buttons với color và label
36
+
37
+ ```tsx
38
+ <>
39
+ <RadioButton name="delivery" value="standard" color="primary" defaultChecked onChange={handleChange}>
40
+ Giao hàng tiêu chuẩn
41
+ </RadioButton>
42
+ <RadioButton name="delivery" value="express" color="primary" onChange={handleChange}>
43
+ Giao hàng nhanh
44
+ </RadioButton>
45
+ </>
46
+ ```
47
+
48
+ ### Error state và disabled
49
+
50
+ ```tsx
51
+ <>
52
+ <RadioButton color="red" defaultChecked onChange={handleChange}>
53
+ Tùy chọn không hợp lệ
54
+ </RadioButton>
55
+ <RadioButton disabled defaultChecked onChange={handleChange}>
56
+ Tùy chọn không khả dụng
57
+ </RadioButton>
58
+ </>
59
+ ```
60
+
61
+ ### Sử dụng containerProps để customize label
62
+
63
+ ```tsx
64
+ <RadioButton
65
+ name="payment"
66
+ value="card"
67
+ color="blue"
68
+ containerProps={{
69
+ sx: { padding: '8px', border: '1px solid #ddd', borderRadius: '4px' }
70
+ }}
71
+ >
72
+ Thanh toán qua thẻ
73
+ </RadioButton>
74
+ ```
75
+
76
+ ## Related Components
77
+
78
+ - [Primitive](../primitive/README.md)
79
+ - [Checkbox](../checkbox/README.md)
80
+
81
+ ## Dependencies
82
+
83
+ - @emotion/react
84
+
85
+ ## See Also
86
+
87
+ - [Component Source](./radio-button.tsx)
88
+ - [Storybook Stories](./radio-button.stories.ts)
89
+ - [Types](./types.ts)
@@ -1,8 +1,8 @@
1
1
  import { jsxs, jsx } from "react/jsx-runtime";
2
2
  import { getRadioButtonProps } from "./helpers.js";
3
- import classes from "./radio-button.module.scss.js";
4
3
  import { getColorShadesByVariant } from "../../utils/index.js";
5
- import clsx from "clsx";
4
+ import "lodash-es";
5
+ import { getColorVar } from "../../theme/palette.js";
6
6
  import { Primitive } from "../primitive/primitive.js";
7
7
  const RadioButton = ({ ref, ..._props }) => {
8
8
  const { children, color = "primary", className, containerProps, ...props } = getRadioButtonProps(_props);
@@ -11,17 +11,108 @@ const RadioButton = ({ ref, ..._props }) => {
11
11
  Primitive,
12
12
  {
13
13
  ...containerProps,
14
- className: clsx(classes.radio_button__container, containerProps == null ? void 0 : containerProps.className),
15
14
  sx: {
16
15
  "--radio-button-color": main,
16
+ display: "flex",
17
+ width: "fit-content",
18
+ maxWidth: "100%",
19
+ alignItems: "center",
20
+ gap: "0.5rem",
21
+ cursor: "pointer",
22
+ "&:has(input:disabled)": {
23
+ cursor: "default",
24
+ pointerEvents: "none"
25
+ },
26
+ '&:hover [data-radio-button-part="main"]': {
27
+ "--radio-button-checkmark-color": "var(--radio-button-color)",
28
+ '& [data-radio-button-part="checkmark"]': {
29
+ boxShadow: "0 1px 3px 0 currentColor, 0 1px 2px -1px currentColor"
30
+ }
31
+ },
17
32
  ...containerProps == null ? void 0 : containerProps.sx
18
33
  },
19
34
  as: "label",
20
35
  children: [
21
- /* @__PURE__ */ jsxs("span", { className: classes.radio_button__main, children: [
22
- /* @__PURE__ */ jsx("input", { ...props, className: clsx(classes.radio_button__input, className), type: "radio", ref }),
23
- /* @__PURE__ */ jsx("span", { className: classes.radio_button__checkmark })
24
- ] }),
36
+ /* @__PURE__ */ jsxs(
37
+ Primitive,
38
+ {
39
+ as: "span",
40
+ "data-radio-button-part": "main",
41
+ sx: {
42
+ "--radio-button-checkmark-color": getColorVar("neutral.600"),
43
+ "--radio-button-dot-scale": 0,
44
+ position: "relative",
45
+ display: "flex",
46
+ borderRadius: 9999,
47
+ padding: "0.125rem",
48
+ width: "1.5rem",
49
+ height: "1.5rem",
50
+ flexShrink: 0,
51
+ "&:has(input:checked)": {
52
+ "--radio-button-checkmark-color": "var(--radio-button-color)",
53
+ "--radio-button-dot-scale": 1
54
+ },
55
+ "&:has(input:disabled)": {
56
+ "--radio-button-checkmark-color": getColorVar("neutral.300")
57
+ }
58
+ },
59
+ children: [
60
+ /* @__PURE__ */ jsx(
61
+ Primitive,
62
+ {
63
+ ...props,
64
+ className,
65
+ type: "radio",
66
+ ref,
67
+ as: "input",
68
+ sx: {
69
+ width: 0,
70
+ height: 0,
71
+ opacity: 0,
72
+ display: "none"
73
+ }
74
+ }
75
+ ),
76
+ /* @__PURE__ */ jsx(
77
+ Primitive,
78
+ {
79
+ as: "span",
80
+ "data-radio-button-part": "checkmark",
81
+ sx: {
82
+ color: "var(--radio-button-checkmark-color)",
83
+ width: "100%",
84
+ height: "100%",
85
+ borderWidth: 1,
86
+ borderStyle: "solid",
87
+ borderColor: "currentColor",
88
+ borderRadius: 9999,
89
+ position: "relative",
90
+ "&::after": {
91
+ content: '""',
92
+ position: "absolute",
93
+ top: 0,
94
+ right: 0,
95
+ bottom: 0,
96
+ left: 0,
97
+ borderWidth: 2,
98
+ borderStyle: "solid",
99
+ backgroundClip: "content-box",
100
+ width: "100%",
101
+ height: "100%",
102
+ borderRadius: 9999,
103
+ transform: "scale(var(--radio-button-dot-scale))",
104
+ transitionProperty: "transform",
105
+ transitionTimingFunction: "cubic-bezier(0.4, 0, 0.2, 1)",
106
+ transitionDuration: "300ms",
107
+ borderColor: "transparent",
108
+ backgroundColor: "currentColor"
109
+ }
110
+ }
111
+ }
112
+ )
113
+ ]
114
+ }
115
+ ),
25
116
  children
26
117
  ]
27
118
  }
@@ -0,0 +1,66 @@
1
+ # RippleEffect
2
+
3
+ A presentational component that renders a Material-style ripple animation on pointer interactions. It attaches itself to its parent element and is used internally by interactive components like Button and IconButton.
4
+
5
+ ## Import
6
+
7
+ ```tsx
8
+ import { RippleEffect } from '@janbox/storefront-ui';
9
+ ```
10
+
11
+ ## Props
12
+
13
+ | Prop | Type | Default | Description |
14
+ |------|------|---------|-------------|
15
+ | `enabled` | `boolean` | `true` | Whether to enable the ripple effect. When false, the component renders nothing and no event listeners are attached. |
16
+ | `color` | `string` | `'currentColor'` | CSS color value for the ripple circle. Accepts any valid CSS color string. |
17
+ | `duration` | `number` | `400` | Duration of the ripple animation in milliseconds. |
18
+ | `opacity` | `number` | `0.2` | Starting opacity of the ripple circle before it fades out. |
19
+ | `sizeMultiplier` | `number` | `1` | Multiplier applied to the computed ripple radius. Values greater than 1 make the ripple larger relative to the parent element. |
20
+
21
+ ## Examples
22
+
23
+ ### Basic usage inside a button
24
+
25
+ ```tsx
26
+ import { RippleEffect } from '@janbox/storefront-ui';
27
+
28
+ const MyButton = () => (
29
+ <button style={{ position: 'relative', overflow: 'hidden' }}>
30
+ Click me
31
+ <RippleEffect />
32
+ </button>
33
+ );
34
+ ```
35
+
36
+ ### Custom color and duration
37
+
38
+ ```tsx
39
+ import { RippleEffect } from '@janbox/storefront-ui';
40
+
41
+ const PrimaryButton = () => (
42
+ <button style={{ position: 'relative', overflow: 'hidden', backgroundColor: '#1976d2', color: '#fff' }}>
43
+ Submit
44
+ <RippleEffect color="#ffffff" opacity={0.3} duration={600} />
45
+ </button>
46
+ );
47
+ ```
48
+
49
+ ### Disabled ripple conditionally
50
+
51
+ ```tsx
52
+ import { RippleEffect } from '@janbox/storefront-ui';
53
+
54
+ const ToggleButton = ({ disabled }: { disabled: boolean }) => (
55
+ <button style={{ position: 'relative', overflow: 'hidden' }} disabled={disabled}>
56
+ Toggle
57
+ <RippleEffect enabled={!disabled} />
58
+ </button>
59
+ );
60
+ ```
61
+
62
+ ## See Also
63
+
64
+ - [Component Source](./index.ts)
65
+ - [Storybook Stories](./rippleeffect.stories.tsx)
66
+ - [Main README](../../README.md)
@@ -0,0 +1,124 @@
1
+ # Select
2
+
3
+ A dropdown select component that supports searchable filtering, virtualized option lists, controlled/uncontrolled state, and responsive sizing. It is built on top of InputMask and Floating UI, making it suitable for forms that need single-value selection from a list of options.
4
+
5
+ ## Import
6
+
7
+ ```tsx
8
+ import { Select } from '@janbox/storefront-ui';
9
+ ```
10
+
11
+ ## Props
12
+
13
+ | Prop | Type | Default | Description |
14
+ |------|------|---------|-------------|
15
+ | `options*` | `O[]` | - | Array of option objects to display in the dropdown list. |
16
+ | `value` | `V \| null` | - | Controlled selected value. |
17
+ | `defaultValue` | `V` | - | Default selected value for uncontrolled usage. |
18
+ | `onChange` | `(value: V, option: O) => void` | - | Callback fired when the user selects an option. Receives both the selected value and the full option object. |
19
+ | `placeholder` | `string` | - | Placeholder text shown when no option is selected. |
20
+ | `searchable` | `boolean` | - | Enables a search input inside the dropdown. Defaults to `true` automatically when options count exceeds `displayRowCount`. |
21
+ | `optionLabel` | `keyof O \| ((option: O) => React.ReactNode)` | `'label'` | Key or function to derive the display label from an option. |
22
+ | `optionValue` | `keyof O \| ((option: O) => V)` | `'value'` | Key or function to derive the value from an option. |
23
+ | `optionSearchLabel` | `keyof O \| Array<keyof O> \| ((option: O) => string \| string[])` | - | Key, array of keys, or function used to extract searchable text from an option. Falls back to `optionLabel` if not provided. |
24
+ | `selectedRenderer` | `(option: O) => React.ReactNode` | - | Custom render function for the selected option displayed in the trigger button. |
25
+ | `placement` | `Placement` | `'bottom-start'` | Floating UI placement for the dropdown panel. |
26
+ | `open` | `boolean` | - | Controlled open state of the dropdown. |
27
+ | `onOpenChange` | `UseFloatingOptions['onOpenChange']` | - | Callback fired when the dropdown open state changes. |
28
+ | `error` | `boolean` | - | Puts the trigger input into an error visual state. |
29
+ | `helperText` | `string` | - | Helper or validation message displayed below the select trigger. |
30
+ | `displayRowCount` | `number` | `5` | Maximum number of rows visible in the dropdown list before scrolling. Use `Infinity` to show all options. |
31
+ | `lazy` | `boolean` | `true` | When `true`, uses windowed/virtualized rendering for the option list. |
32
+ | `size` | `'xs' \| 'sm' \| 'md' \| 'lg'` | `'md'` | Size variant for the trigger input and option rows. Supports responsive breakpoint overrides via `sm`/`md`/`lg` props. |
33
+ | `disabled` | `boolean` | - | Disables the select trigger, preventing interaction. |
34
+ | `containerProps` | `PrimitiveProps<JSX.IntrinsicElements['div']>` | - | Props forwarded to the outer container `Primitive` div. |
35
+ | `listProps` | `HTMLAttributes<HTMLDivElement>` | - | Props forwarded to the `VirtualizedList` container div. |
36
+ | `inputProps` | `InputProps` | - | Props forwarded to the search `Input` rendered inside the dropdown when `searchable` is `true`. |
37
+ | `css` | `Interpolation` | - | Emotion CSS interpolation applied to the `InputMask` trigger element. |
38
+ | `sx` | `ToResponsiveProps<StyledCSS>` | - | Responsive sx prop for inline styles via Emotion (inherited from `InputMaskProps`). |
39
+
40
+ *Required props
41
+
42
+ ## Examples
43
+
44
+ ### Default
45
+
46
+ ```tsx
47
+ const options = [
48
+ { label: 'Apple', value: 'apple' },
49
+ { label: 'Banana', value: 'banana' },
50
+ { label: 'Cherry', value: 'cherry' },
51
+ ];
52
+
53
+ <Select
54
+ options={options}
55
+ placeholder="Select a fruit..."
56
+ onChange={(value, option) => console.log(value, option)}
57
+ />
58
+ ```
59
+
60
+ ### Searchable with error state
61
+
62
+ ```tsx
63
+ const options = [
64
+ { label: 'Apple', value: 'apple' },
65
+ { label: 'Banana', value: 'banana' },
66
+ { label: 'Cherry', value: 'cherry' },
67
+ ];
68
+
69
+ <Select
70
+ options={options}
71
+ placeholder="Search and select..."
72
+ searchable
73
+ error
74
+ helperText="Please select an option"
75
+ onChange={(value) => console.log(value)}
76
+ />
77
+ ```
78
+
79
+ ### Controlled with custom optionLabel and selectedRenderer
80
+
81
+ ```tsx
82
+ type Product = { id: number; name: string; price: number };
83
+
84
+ const products: Product[] = [
85
+ { id: 1, name: 'Shirt', price: 29 },
86
+ { id: 2, name: 'Pants', price: 59 },
87
+ ];
88
+
89
+ <Select<number, Product>
90
+ options={products}
91
+ optionValue="id"
92
+ optionLabel="name"
93
+ optionSearchLabel="name"
94
+ value={selectedId}
95
+ selectedRenderer={(p) => `${p.name} — $${p.price}`}
96
+ onChange={(id) => setSelectedId(id)}
97
+ size="lg"
98
+ placeholder="Pick a product"
99
+ />
100
+ ```
101
+
102
+ ## Related Components
103
+
104
+ - [InputMask](../input-mask/README.md)
105
+ - [Input](../input/README.md)
106
+ - [Floating](../floating/README.md)
107
+ - [FloatingTrigger](../floatingtrigger/README.md)
108
+ - [FloatingContent](../floatingcontent/README.md)
109
+ - [FormHelperText](../formhelpertext/README.md)
110
+ - [VirtualizedList](../virtualizedlist/README.md)
111
+ - [Primitive](../primitive/README.md)
112
+
113
+ ## Dependencies
114
+
115
+ - `@floating-ui/react`
116
+ - `@tanstack/react-virtual`
117
+ - `lodash-es`
118
+ - `@emotion/react`
119
+
120
+ ## See Also
121
+
122
+ - [Component Source](./index.ts)
123
+ - [Storybook Stories](./select.stories.ts)
124
+ - [Main README](../../README.md)
@@ -0,0 +1,67 @@
1
+ # StarRating
2
+
3
+ Component SVG một ngôi sao hiển thị rating theo tỷ lệ phần trăm bằng kỹ thuật clip-path fill. Dùng để hiển thị điểm đánh giá sản phẩm hoặc review score, hỗ trợ giá trị thập phân và color variants.
4
+
5
+ ## Import
6
+
7
+ ```tsx
8
+ import { StarRating } from '@janbox/storefront-ui';
9
+ ```
10
+
11
+ ## Props
12
+
13
+ | Prop | Type | Default | Description |
14
+ |------|------|---------|-------------|
15
+ | `rating*` | `number` | - | Giá trị rating hiện tại. Kiểm soát mức độ tô màu ngôi sao qua clip-path. Hỗ trợ giá trị thập phân (VD: 2.5). |
16
+ | `maxRating` | `number` | 5 | Giá trị rating tối đa. Dùng để tính phần trăm tô màu của ngôi sao. |
17
+ | `size` | `number` | 24 | Chiều rộng và chiều cao của SVG tính bằng pixel. ViewBox nội bộ luôn là 24x24 nên giá trị này scale output. |
18
+ | `colorVariant` | `ColorVariant` | `"secondary"` | Color variant dùng cho cả stroke và fill của ngôi sao. Ánh xạ tới design token color shades qua `getColorShadesByVariant`. |
19
+ | `className` | `string` | - | CSS class name tùy chọn áp dụng lên root SVG element. |
20
+
21
+ *Props bắt buộc
22
+
23
+ ## Examples
24
+
25
+ ### Mặc định (partial fill)
26
+
27
+ ```tsx
28
+ <StarRating rating={3} />
29
+ ```
30
+
31
+ ### Nửa ngôi sao với kích thước tùy chỉnh
32
+
33
+ ```tsx
34
+ <StarRating rating={2.5} size={32} />
35
+ ```
36
+
37
+ ### Max rating tùy chỉnh và color variant
38
+
39
+ ```tsx
40
+ <StarRating rating={7} maxRating={10} colorVariant="primary" />
41
+ ```
42
+
43
+ ### Ngôi sao đầy và rỗng
44
+
45
+ ```tsx
46
+ {/* Đầy hoàn toàn */}
47
+ <StarRating rating={5} />
48
+
49
+ {/* Rỗng hoàn toàn */}
50
+ <StarRating rating={0} />
51
+ ```
52
+
53
+ ## Related Components
54
+
55
+ - [Icon](../icon/README.md)
56
+
57
+ ## Dependencies
58
+
59
+ - react (forwardRef, useId)
60
+ - @/utils (getColorShadesByVariant)
61
+ - @/types (ColorVariant)
62
+
63
+ ## See Also
64
+
65
+ - [Component Source](./index.ts)
66
+ - [Storybook Stories](./star-rating.stories.tsx)
67
+ - [Main README](../../README.md)