@luxfi/ui 5.6.0 → 6.0.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.
Files changed (125) hide show
  1. package/README.md +109 -0
  2. package/package.json +81 -278
  3. package/dist/accordion.cjs +0 -213
  4. package/dist/accordion.js +0 -186
  5. package/dist/alert.cjs +0 -553
  6. package/dist/alert.js +0 -531
  7. package/dist/avatar.cjs +0 -149
  8. package/dist/avatar.js +0 -125
  9. package/dist/badge.cjs +0 -611
  10. package/dist/badge.js +0 -589
  11. package/dist/button.cjs +0 -689
  12. package/dist/button.js +0 -664
  13. package/dist/checkbox.cjs +0 -265
  14. package/dist/checkbox.js +0 -241
  15. package/dist/close-button.cjs +0 -73
  16. package/dist/close-button.js +0 -51
  17. package/dist/collapsible.cjs +0 -702
  18. package/dist/collapsible.js +0 -679
  19. package/dist/color-mode.cjs +0 -96
  20. package/dist/color-mode.js +0 -72
  21. package/dist/dialog.cjs +0 -279
  22. package/dist/dialog.js +0 -246
  23. package/dist/drawer.cjs +0 -207
  24. package/dist/drawer.js +0 -175
  25. package/dist/empty-state.cjs +0 -93
  26. package/dist/empty-state.js +0 -71
  27. package/dist/field.cjs +0 -183
  28. package/dist/field.js +0 -160
  29. package/dist/heading.cjs +0 -46
  30. package/dist/heading.js +0 -40
  31. package/dist/icon-button.cjs +0 -491
  32. package/dist/icon-button.js +0 -470
  33. package/dist/image.cjs +0 -572
  34. package/dist/image.js +0 -551
  35. package/dist/index.cjs +0 -5779
  36. package/dist/index.js +0 -5619
  37. package/dist/input-group.cjs +0 -155
  38. package/dist/input-group.js +0 -133
  39. package/dist/input.cjs +0 -65
  40. package/dist/input.js +0 -59
  41. package/dist/link.cjs +0 -630
  42. package/dist/link.js +0 -606
  43. package/dist/menu.cjs +0 -305
  44. package/dist/menu.js +0 -269
  45. package/dist/pin-input.cjs +0 -182
  46. package/dist/pin-input.js +0 -160
  47. package/dist/popover.cjs +0 -327
  48. package/dist/popover.js +0 -294
  49. package/dist/progress-circle.cjs +0 -152
  50. package/dist/progress-circle.js +0 -128
  51. package/dist/progress.cjs +0 -117
  52. package/dist/progress.js +0 -94
  53. package/dist/provider.cjs +0 -62
  54. package/dist/provider.js +0 -40
  55. package/dist/radio.cjs +0 -177
  56. package/dist/radio.js +0 -153
  57. package/dist/rating.cjs +0 -80
  58. package/dist/rating.js +0 -58
  59. package/dist/select.cjs +0 -791
  60. package/dist/select.js +0 -757
  61. package/dist/separator.cjs +0 -57
  62. package/dist/separator.js +0 -51
  63. package/dist/skeleton.cjs +0 -370
  64. package/dist/skeleton.js +0 -346
  65. package/dist/slider.cjs +0 -138
  66. package/dist/slider.js +0 -115
  67. package/dist/switch.cjs +0 -163
  68. package/dist/switch.js +0 -140
  69. package/dist/table.cjs +0 -1044
  70. package/dist/table.js +0 -1013
  71. package/dist/tabs.cjs +0 -240
  72. package/dist/tabs.js +0 -213
  73. package/dist/tag.cjs +0 -651
  74. package/dist/tag.js +0 -628
  75. package/dist/textarea.cjs +0 -65
  76. package/dist/textarea.js +0 -59
  77. package/dist/toaster.cjs +0 -99
  78. package/dist/toaster.js +0 -96
  79. package/dist/tooltip.cjs +0 -171
  80. package/dist/tooltip.js +0 -148
  81. package/dist/utils.cjs +0 -11
  82. package/dist/utils.js +0 -9
  83. package/src/accordion.tsx +0 -285
  84. package/src/alert.tsx +0 -221
  85. package/src/avatar.tsx +0 -174
  86. package/src/badge.tsx +0 -158
  87. package/src/button.tsx +0 -411
  88. package/src/checkbox.tsx +0 -307
  89. package/src/close-button.tsx +0 -51
  90. package/src/collapsible.tsx +0 -126
  91. package/src/color-mode.tsx +0 -125
  92. package/src/dialog.tsx +0 -356
  93. package/src/drawer.tsx +0 -186
  94. package/src/empty-state.tsx +0 -97
  95. package/src/field.tsx +0 -202
  96. package/src/heading.tsx +0 -55
  97. package/src/icon-button.tsx +0 -192
  98. package/src/image.tsx +0 -280
  99. package/src/index.ts +0 -192
  100. package/src/input-group.tsx +0 -159
  101. package/src/input.tsx +0 -60
  102. package/src/link.tsx +0 -326
  103. package/src/menu.tsx +0 -471
  104. package/src/pin-input.tsx +0 -187
  105. package/src/popover.tsx +0 -400
  106. package/src/progress-circle.tsx +0 -180
  107. package/src/progress.tsx +0 -109
  108. package/src/provider.tsx +0 -12
  109. package/src/radio.tsx +0 -175
  110. package/src/rating.tsx +0 -79
  111. package/src/select.tsx +0 -696
  112. package/src/separator.tsx +0 -59
  113. package/src/skeleton.tsx +0 -302
  114. package/src/slider.tsx +0 -152
  115. package/src/switch.tsx +0 -158
  116. package/src/table.tsx +0 -621
  117. package/src/tabs.tsx +0 -354
  118. package/src/tag.tsx +0 -159
  119. package/src/textarea.tsx +0 -60
  120. package/src/toaster.tsx +0 -117
  121. package/src/tokens.css +0 -438
  122. package/src/tooltip.tsx +0 -184
  123. package/src/utils/cn.ts +0 -7
  124. package/src/utils.ts +0 -6
  125. package/tokens.css +0 -438
package/src/separator.tsx DELETED
@@ -1,59 +0,0 @@
1
- import React from 'react';
2
-
3
- import { cn } from './utils';
4
-
5
- type Orientation = 'horizontal' | 'vertical';
6
- type Variant = 'solid' | 'dashed' | 'dotted';
7
- type Size = 'xs' | 'sm' | 'md' | 'lg';
8
-
9
- export interface SeparatorProps extends React.HTMLAttributes<HTMLHRElement> {
10
- readonly orientation?: Orientation;
11
- readonly variant?: Variant;
12
- readonly size?: Size;
13
- }
14
-
15
- const VARIANT_CLASSES: Record<Variant, string> = {
16
- solid: 'border-solid',
17
- dashed: 'border-dashed',
18
- dotted: 'border-dotted',
19
- };
20
-
21
- const HORIZONTAL_SIZE_CLASSES: Record<Size, string> = {
22
- xs: 'border-t-[0.5px]',
23
- sm: 'border-t',
24
- md: 'border-t-2',
25
- lg: 'border-t-[3px]',
26
- };
27
-
28
- const VERTICAL_SIZE_CLASSES: Record<Size, string> = {
29
- xs: 'border-l-[0.5px]',
30
- sm: 'border-l',
31
- md: 'border-l-2',
32
- lg: 'border-l-[3px]',
33
- };
34
-
35
- export const Separator = React.forwardRef<HTMLHRElement, SeparatorProps>(
36
- function Separator(
37
- { orientation = 'horizontal', variant = 'solid', size = 'sm', className, ...rest },
38
- ref,
39
- ) {
40
- const isVertical = orientation === 'vertical';
41
-
42
- return (
43
- <hr
44
- ref={ ref }
45
- role="separator"
46
- aria-orientation={ orientation }
47
- className={ cn(
48
- 'border-[var(--color-border-divider)]',
49
- 'border-0',
50
- VARIANT_CLASSES[variant],
51
- isVertical ? VERTICAL_SIZE_CLASSES[size] : HORIZONTAL_SIZE_CLASSES[size],
52
- isVertical ? 'self-stretch h-auto w-0' : 'w-full',
53
- className,
54
- ) }
55
- { ...rest }
56
- />
57
- );
58
- },
59
- );
package/src/skeleton.tsx DELETED
@@ -1,302 +0,0 @@
1
- import * as React from 'react';
2
-
3
- import { cn } from './utils';
4
-
5
- // ---------------------------------------------------------------------------
6
- // Skeleton
7
- // ---------------------------------------------------------------------------
8
-
9
- export interface SkeletonProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'color'> {
10
- readonly loading: boolean | undefined;
11
-
12
- /** When true, the Skeleton wraps its single child element instead of adding a wrapper div. */
13
- readonly asChild?: boolean;
14
-
15
- // Legacy Chakra style-prop shims — converted to className / inline style.
16
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
17
- readonly w?: any;
18
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
19
- readonly h?: any;
20
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
21
- readonly minW?: any;
22
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
23
- readonly maxW?: any;
24
- readonly display?: string;
25
- readonly flexGrow?: number;
26
- readonly flexShrink?: number;
27
- readonly flexBasis?: string;
28
- readonly fontWeight?: number | string;
29
- readonly textStyle?: string;
30
- readonly borderRadius?: string;
31
- readonly alignSelf?: string;
32
- readonly alignItems?: string;
33
- readonly justifyContent?: string;
34
- readonly color?: string;
35
- readonly mt?: number | string;
36
- readonly mb?: number | string;
37
- readonly ml?: number | string;
38
- readonly mr?: number | string;
39
- readonly height?: string;
40
- readonly overflow?: string;
41
- readonly whiteSpace?: string;
42
- readonly textOverflow?: string;
43
- readonly textTransform?: string;
44
- readonly gap?: number | string;
45
- readonly gridTemplateColumns?: string;
46
- readonly minWidth?: string;
47
- readonly boxSize?: number | string;
48
- readonly py?: number | string;
49
- readonly px?: number | string;
50
- readonly p?: number | string;
51
- readonly hideBelow?: string;
52
- readonly as?: React.ElementType;
53
- readonly fontSize?: string;
54
- readonly flexWrap?: React.CSSProperties['flexWrap'];
55
- readonly wordBreak?: React.CSSProperties['wordBreak'];
56
- readonly lineHeight?: string;
57
- readonly marginRight?: string;
58
- readonly position?: React.CSSProperties['position'];
59
- readonly background?: string;
60
- }
61
-
62
- const SPACING_SCALE = 4;
63
-
64
- function toStylePx(value: number | string | undefined): string | undefined {
65
- if (value === undefined) return undefined;
66
- if (typeof value === 'string') return value;
67
- return `${ value * SPACING_SCALE }px`;
68
- }
69
-
70
- function extractSkeletonStyleProps(props: Record<string, unknown>): {
71
- style: React.CSSProperties;
72
- rest: Record<string, unknown>;
73
- } {
74
- const style: React.CSSProperties = {};
75
- const rest: Record<string, unknown> = {};
76
-
77
- function resolveDimension(value: unknown): string | undefined {
78
- if (value === undefined || value === null) return undefined;
79
- if (typeof value === 'number') return `${ value * SPACING_SCALE }px`;
80
- if (typeof value === 'string') return value;
81
- if (typeof value === 'object') {
82
- const obj = value as Record<string, string>;
83
- return obj.base ?? obj.lg ?? obj.xl ?? Object.values(obj)[0];
84
- }
85
- return undefined;
86
- }
87
-
88
- for (const [ key, value ] of Object.entries(props)) {
89
- if (value === undefined) continue;
90
- switch (key) {
91
- case 'w': {
92
- const v = resolveDimension(value); if (v) style.width = v; break;
93
- }
94
- case 'h': {
95
- const v = resolveDimension(value); if (v) style.height = v; break;
96
- }
97
- case 'minW': {
98
- const v = resolveDimension(value); if (v) style.minWidth = v; break;
99
- }
100
- case 'maxW': {
101
- const v = resolveDimension(value); if (v) style.maxWidth = v; break;
102
- }
103
- case 'height': style.height = value as string; break;
104
- case 'display': style.display = value as string; break;
105
- case 'flexGrow': style.flexGrow = value as number; break;
106
- case 'flexShrink': style.flexShrink = value as number; break;
107
- case 'flexBasis': style.flexBasis = value as string; break;
108
- case 'fontWeight': style.fontWeight = value as number | string; break;
109
- case 'borderRadius': style.borderRadius = value as string; break;
110
- case 'alignSelf': style.alignSelf = value as string; break;
111
- case 'alignItems': style.alignItems = value as string; break;
112
- case 'justifyContent': style.justifyContent = value as string; break;
113
- case 'color': style.color = value as string; break;
114
- case 'mt': style.marginTop = toStylePx(value as number | string); break;
115
- case 'mb': style.marginBottom = toStylePx(value as number | string); break;
116
- case 'ml': style.marginLeft = toStylePx(value as number | string); break;
117
- case 'mr': style.marginRight = toStylePx(value as number | string); break;
118
- case 'overflow': style.overflow = value as string; break;
119
- case 'whiteSpace': style.whiteSpace = value as string; break;
120
- case 'textOverflow': style.textOverflow = value as string; break;
121
- case 'textTransform': style.textTransform = value as string; break;
122
- case 'gap': style.gap = typeof value === 'number' ? `${ value * SPACING_SCALE }px` : value as string; break;
123
- case 'gridTemplateColumns': style.gridTemplateColumns = value as string; break;
124
- case 'minWidth': style.minWidth = value as string; break;
125
- case 'boxSize': {
126
- const s = typeof value === 'number' ? `${ value * SPACING_SCALE }px` : value as string; style.width = s; style.height = s; break;
127
- }
128
- case 'py': {
129
- const v = toStylePx(value as number | string); style.paddingTop = v; style.paddingBottom = v; break;
130
- }
131
- case 'px': {
132
- const v = toStylePx(value as number | string); style.paddingLeft = v; style.paddingRight = v; break;
133
- }
134
- case 'p': {
135
- const v = toStylePx(value as number | string); style.padding = v; break;
136
- }
137
- case 'hideBelow': break; // handled via className
138
- case 'textStyle': break; // drop textStyle, not directly applicable
139
- case 'fontSize': style.fontSize = value as string; break;
140
- case 'flexWrap': style.flexWrap = value as React.CSSProperties['flexWrap']; break;
141
- case 'wordBreak': style.wordBreak = value as React.CSSProperties['wordBreak']; break;
142
- case 'lineHeight': style.lineHeight = value as string; break;
143
- case 'marginRight': style.marginRight = value as string; break;
144
- case 'position': style.position = value as React.CSSProperties['position']; break;
145
- case 'background': style.background = value as string; break;
146
- default: rest[key] = value; break;
147
- }
148
- }
149
-
150
- return { style, rest };
151
- }
152
-
153
- export const Skeleton = React.forwardRef<HTMLDivElement, SkeletonProps>(
154
- function Skeleton(props, ref) {
155
- const {
156
- loading = false,
157
- asChild,
158
- className,
159
- children,
160
- style: styleProp,
161
- // Destructure style-prop shims so they don't leak into DOM
162
- w: _w, h: _h, minW: _minW, maxW: _maxW, display: _display,
163
- flexGrow: _flexGrow, flexShrink: _flexShrink, flexBasis: _flexBasis,
164
- fontWeight: _fontWeight, textStyle: _textStyle, borderRadius: _borderRadius,
165
- alignSelf: _alignSelf, alignItems: _alignItems, justifyContent: _justifyContent,
166
- color: _color, mt: _mt, mb: _mb, ml: _ml, mr: _mr, height: _height,
167
- overflow: _overflow, whiteSpace: _whiteSpace, textOverflow: _textOverflow,
168
- textTransform: _textTransform, gap: _gap, gridTemplateColumns: _gridTemplateColumns,
169
- minWidth: _minWidth, boxSize: _boxSize, py: _py, px: _px, p: _p,
170
- hideBelow: _hideBelow, fontSize: _fontSize, flexWrap: _flexWrap, wordBreak: _wordBreak, lineHeight: _lineHeight,
171
- marginRight: _marginRight, position: _position, background: _background,
172
- as: Component = 'div',
173
- ...htmlRest
174
- } = props;
175
-
176
- // Collect legacy style props into a CSSProperties object
177
- const { style: shimStyle } = extractSkeletonStyleProps({
178
- w: _w, h: _h, minW: _minW, maxW: _maxW, display: _display,
179
- flexGrow: _flexGrow, flexShrink: _flexShrink, flexBasis: _flexBasis,
180
- fontWeight: _fontWeight, textStyle: _textStyle, borderRadius: _borderRadius,
181
- alignSelf: _alignSelf, alignItems: _alignItems, justifyContent: _justifyContent,
182
- color: _color, mt: _mt, mb: _mb, ml: _ml, mr: _mr, height: _height,
183
- overflow: _overflow, whiteSpace: _whiteSpace, textOverflow: _textOverflow,
184
- textTransform: _textTransform, gap: _gap, gridTemplateColumns: _gridTemplateColumns,
185
- minWidth: _minWidth, boxSize: _boxSize, py: _py, px: _px, p: _p,
186
- hideBelow: _hideBelow, fontSize: _fontSize, flexWrap: _flexWrap, wordBreak: _wordBreak, lineHeight: _lineHeight,
187
- marginRight: _marginRight, position: _position, background: _background,
188
- });
189
- const mergedStyle = Object.keys(shimStyle).length > 0 || styleProp ?
190
- { ...shimStyle, ...styleProp } :
191
- undefined;
192
-
193
- const HIDE_BELOW_MAP: Record<string, string> = { lg: 'lg:hidden', md: 'md:hidden', sm: 'sm:hidden' };
194
- const hideBelowClass = _hideBelow ? HIDE_BELOW_MAP[_hideBelow] : undefined;
195
- const finalClassName = hideBelowClass ? cn(className, hideBelowClass) : className;
196
-
197
- if (!loading) {
198
- // When asChild is true, render children directly without a wrapper div
199
- if (asChild && React.isValidElement(children)) {
200
- return children;
201
- }
202
- return (
203
- <Component ref={ ref } className={ finalClassName } style={ mergedStyle } { ...htmlRest }>
204
- { children }
205
- </Component>
206
- );
207
- }
208
-
209
- // When asChild is true and loading, wrap the child in skeleton styles
210
- if (asChild && React.isValidElement(children)) {
211
- return (
212
- <Component
213
- ref={ ref }
214
- data-loading
215
- className={ cn(
216
- 'animate-skeleton-shimmer rounded-sm',
217
- 'bg-[linear-gradient(90deg,var(--color-skeleton-start)_0%,var(--color-skeleton-end)_50%,var(--color-skeleton-start)_100%)]',
218
- 'bg-[length:200%_100%]',
219
- 'text-transparent [&_*]:invisible',
220
- finalClassName,
221
- ) }
222
- style={ mergedStyle }
223
- { ...htmlRest }
224
- >
225
- { children }
226
- </Component>
227
- );
228
- }
229
-
230
- return (
231
- <Component
232
- ref={ ref }
233
- data-loading
234
- className={ cn(
235
- 'animate-skeleton-shimmer rounded-sm',
236
- 'bg-[linear-gradient(90deg,var(--color-skeleton-start)_0%,var(--color-skeleton-end)_50%,var(--color-skeleton-start)_100%)]',
237
- 'bg-[length:200%_100%]',
238
- children ? 'text-transparent [&_*]:invisible' : 'min-h-5',
239
- finalClassName,
240
- ) }
241
- style={ mergedStyle }
242
- { ...htmlRest }
243
- >
244
- { children }
245
- </Component>
246
- );
247
- },
248
- );
249
-
250
- // ---------------------------------------------------------------------------
251
- // SkeletonCircle
252
- // ---------------------------------------------------------------------------
253
-
254
- export interface SkeletonCircleProps extends React.HTMLAttributes<HTMLDivElement> {
255
- readonly size?: string | number;
256
- readonly loading?: boolean;
257
- }
258
-
259
- export const SkeletonCircle = React.forwardRef<HTMLDivElement, SkeletonCircleProps>(
260
- function SkeletonCircle(props, ref) {
261
- const { size = 40, loading = true, className, ...rest } = props;
262
-
263
- const dimension = typeof size === 'number' ? `${ size }px` : size;
264
-
265
- return (
266
- <Skeleton
267
- ref={ ref }
268
- loading={ loading }
269
- className={ cn('rounded-full shrink-0', className) }
270
- style={{ width: dimension, height: dimension, ...rest.style }}
271
- { ...rest }
272
- />
273
- );
274
- },
275
- );
276
-
277
- // ---------------------------------------------------------------------------
278
- // SkeletonText
279
- // ---------------------------------------------------------------------------
280
-
281
- export interface SkeletonTextProps extends React.HTMLAttributes<HTMLDivElement> {
282
- readonly noOfLines?: number;
283
- readonly loading?: boolean;
284
- }
285
-
286
- export const SkeletonText = React.forwardRef<HTMLDivElement, SkeletonTextProps>(
287
- function SkeletonText(props, ref) {
288
- const { noOfLines = 3, loading = true, className, ...rest } = props;
289
-
290
- return (
291
- <div ref={ ref } className={ cn('flex w-full flex-col gap-2', className) } { ...rest }>
292
- { Array.from({ length: noOfLines }).map((_, index) => (
293
- <Skeleton
294
- key={ index }
295
- loading={ loading }
296
- className={ cn('h-4', index === noOfLines - 1 && 'max-w-[80%]') }
297
- />
298
- )) }
299
- </div>
300
- );
301
- },
302
- );
package/src/slider.tsx DELETED
@@ -1,152 +0,0 @@
1
- import * as RadixSlider from '@radix-ui/react-slider';
2
- import * as React from 'react';
3
-
4
- import { cn } from './utils';
5
-
6
- export interface SliderProps {
7
- readonly marks?: Array<number | { readonly value: number; readonly label: React.ReactNode }>;
8
- readonly label?: React.ReactNode;
9
- readonly showValue?: boolean;
10
- readonly value?: Array<number>;
11
- readonly defaultValue?: Array<number>;
12
- readonly min?: number;
13
- readonly max?: number;
14
- readonly step?: number;
15
- readonly disabled?: boolean;
16
- readonly orientation?: 'horizontal' | 'vertical';
17
- readonly name?: string;
18
- readonly onValueChange?: (value: Array<number>) => void;
19
- readonly onValueCommit?: (value: Array<number>) => void;
20
- readonly className?: string;
21
- }
22
-
23
- const THUMB_SIZE = 20;
24
- const TRACK_HEIGHT = 6;
25
-
26
- export const Slider = React.forwardRef<HTMLSpanElement, SliderProps>(
27
- function Slider(props, ref) {
28
- const {
29
- marks: marksProp,
30
- label,
31
- showValue,
32
- value,
33
- defaultValue,
34
- min = 0,
35
- max = 100,
36
- step,
37
- disabled,
38
- orientation,
39
- name,
40
- onValueChange,
41
- onValueCommit,
42
- className,
43
- } = props;
44
-
45
- const resolvedValue = defaultValue ?? value;
46
-
47
- const marks = marksProp?.map((mark) => {
48
- if (typeof mark === 'number') return { value: mark, label: undefined };
49
- return mark;
50
- });
51
-
52
- const hasMarkLabel = Boolean(marks?.some((mark) => mark.label));
53
-
54
- return (
55
- <div className={ cn('flex flex-col gap-1', className) }>
56
- { label && !showValue && (
57
- <label className="text-sm font-medium text-text-secondary">
58
- { label }
59
- </label>
60
- ) }
61
- { label && showValue && (
62
- <div className="flex items-center justify-between">
63
- <label className="text-sm font-medium text-text-secondary">
64
- { label }
65
- </label>
66
- <span className="text-sm text-text-secondary">
67
- { (value ?? defaultValue ?? [])?.join(', ') }
68
- </span>
69
- </div>
70
- ) }
71
-
72
- <RadixSlider.Root
73
- ref={ ref }
74
- className={ cn(
75
- 'relative flex w-full touch-none select-none items-center',
76
- hasMarkLabel && 'mb-6',
77
- ) }
78
- value={ value }
79
- defaultValue={ defaultValue }
80
- min={ min }
81
- max={ max }
82
- step={ step }
83
- disabled={ disabled }
84
- orientation={ orientation }
85
- name={ name }
86
- onValueChange={ onValueChange }
87
- onValueCommit={ onValueCommit }
88
- >
89
- <RadixSlider.Track
90
- className={ cn(
91
- 'relative grow rounded-full bg-border-divider',
92
- `h-[${ TRACK_HEIGHT }px]`,
93
- ) }
94
- >
95
- <RadixSlider.Range className="absolute h-full rounded-full bg-link-primary"/>
96
- </RadixSlider.Track>
97
-
98
- { resolvedValue?.map((_, index) => (
99
- <RadixSlider.Thumb
100
- key={ index }
101
- className={ cn(
102
- `block h-[${ THUMB_SIZE }px] w-[${ THUMB_SIZE }px]`,
103
- 'rounded-full border-2 border-link-primary bg-white',
104
- 'shadow-sm transition-colors',
105
- 'hover:border-link-primary-hover',
106
- 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-link-primary focus-visible:ring-offset-2',
107
- 'disabled:pointer-events-none disabled:opacity-50',
108
- ) }
109
- />
110
- )) }
111
- </RadixSlider.Root>
112
-
113
- <SliderMarks marks={ marks } min={ min } max={ max }/>
114
- </div>
115
- );
116
- },
117
- );
118
-
119
- interface SliderMarksProps {
120
- readonly marks?: Array<{ readonly value: number; readonly label?: React.ReactNode }>;
121
- readonly min: number;
122
- readonly max: number;
123
- }
124
-
125
- function SliderMarks(props: SliderMarksProps): React.ReactNode {
126
- const { marks, min, max } = props;
127
- if (!marks?.length) return null;
128
-
129
- const range = max - min;
130
-
131
- return (
132
- <div className="relative w-full h-4">
133
- { marks.map((mark, index) => {
134
- const percent = ((mark.value - min) / range) * 100;
135
- return (
136
- <div
137
- key={ index }
138
- className="absolute flex flex-col items-center -translate-x-1/2"
139
- style={{ left: `${ percent }%` }}
140
- >
141
- <div className="h-1.5 w-0.5 bg-border-divider"/>
142
- { mark.label != null && (
143
- <span className="mt-0.5 text-xs text-text-secondary whitespace-nowrap">
144
- { mark.label }
145
- </span>
146
- ) }
147
- </div>
148
- );
149
- }) }
150
- </div>
151
- );
152
- }
package/src/switch.tsx DELETED
@@ -1,158 +0,0 @@
1
- import * as RadixSwitch from '@radix-ui/react-switch';
2
- // chakra() HOC removed — pure Radix + Tailwind
3
- import * as React from 'react';
4
-
5
- import { cn } from './utils';
6
-
7
- const NOOP = () => { /* noop */ };
8
-
9
- // ─── Size mappings ────────────────────────────────────────────────────
10
- const SIZE_CLASSES = {
11
- sm: {
12
- root: 'h-4 w-7',
13
- thumb: 'h-3 w-3 data-[state=checked]:translate-x-3',
14
- label: 'text-xs',
15
- },
16
- md: {
17
- root: 'h-5 w-9',
18
- thumb: 'h-4 w-4 data-[state=checked]:translate-x-4',
19
- label: 'text-sm',
20
- },
21
- lg: {
22
- root: 'h-6 w-11',
23
- thumb: 'h-5 w-5 data-[state=checked]:translate-x-5',
24
- label: 'text-base',
25
- },
26
- } as const;
27
-
28
- // ─── Props ────────────────────────────────────────────────────────────
29
- export interface SwitchProps extends Omit<React.ComponentPropsWithoutRef<'label'>, 'onChange' | 'dir'> {
30
- inputProps?: React.InputHTMLAttributes<HTMLInputElement>;
31
- labelProps?: React.ComponentPropsWithoutRef<'span'>;
32
- rootRef?: React.Ref<HTMLLabelElement>;
33
- trackLabel?: { on: React.ReactNode; off: React.ReactNode };
34
- thumbLabel?: { on: React.ReactNode; off: React.ReactNode };
35
- checked?: boolean;
36
- defaultChecked?: boolean;
37
- onCheckedChange?: (details: { checked: boolean }) => void;
38
- onChange?: React.FormEventHandler<HTMLLabelElement>;
39
- disabled?: boolean;
40
- size?: 'sm' | 'md' | 'lg';
41
- direction?: 'ltr' | 'rtl';
42
- }
43
-
44
- const SwitchBase = React.forwardRef<HTMLInputElement, SwitchProps>(
45
- function Switch(props, ref) {
46
- const {
47
- inputProps,
48
- children,
49
- rootRef,
50
- labelProps,
51
- trackLabel,
52
- thumbLabel,
53
- checked: checkedProp,
54
- defaultChecked,
55
- onCheckedChange,
56
- onChange,
57
- disabled = false,
58
- size = 'md',
59
- direction,
60
- className,
61
- ...rest
62
- } = props;
63
-
64
- const [ internalChecked, setInternalChecked ] = React.useState(defaultChecked ?? false);
65
-
66
- const isControlled = checkedProp !== undefined;
67
- const checkedState = isControlled ? checkedProp : internalChecked;
68
-
69
- const handleCheckedChange = React.useCallback(
70
- (nextChecked: boolean) => {
71
- if (!isControlled) {
72
- setInternalChecked(nextChecked);
73
- }
74
-
75
- if (onCheckedChange) {
76
- // Support both Chakra-style ({ checked }) and Radix-style (checked) signatures.
77
- // We call with a single object arg; if the consumer ignores it, that's fine.
78
- (onCheckedChange as (details: { checked: boolean }) => void)({ checked: nextChecked });
79
- }
80
- },
81
- [ isControlled, onCheckedChange ],
82
- );
83
-
84
- const sizeClasses = SIZE_CLASSES[size];
85
- const isRtl = direction === 'rtl';
86
-
87
- return (
88
- <label
89
- ref={ rootRef }
90
- className={ cn(
91
- 'inline-flex items-center cursor-pointer select-none gap-2',
92
- isRtl && 'flex-row-reverse',
93
- disabled && 'opacity-50 cursor-not-allowed',
94
- className,
95
- ) }
96
- onChange={ onChange }
97
- data-disabled={ disabled || undefined }
98
- { ...rest }
99
- >
100
- <RadixSwitch.Root
101
- checked={ checkedState }
102
- onCheckedChange={ handleCheckedChange }
103
- disabled={ disabled }
104
- className={ cn(
105
- 'relative inline-flex shrink-0 items-center rounded-full',
106
- 'bg-gray-300 dark:bg-gray-600',
107
- 'data-[state=checked]:bg-gray-800 dark:bg-white',
108
- 'focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-500',
109
- 'transition-colors duration-200',
110
- sizeClasses.root,
111
- ) }
112
- >
113
- { trackLabel && (
114
- <span className="absolute inset-0 flex items-center justify-center text-[8px] font-bold text-white pointer-events-none">
115
- { checkedState ? trackLabel.on : trackLabel.off }
116
- </span>
117
- ) }
118
- <RadixSwitch.Thumb
119
- className={ cn(
120
- 'block rounded-full bg-white shadow-sm',
121
- 'transition-transform duration-200',
122
- 'translate-x-0.5',
123
- sizeClasses.thumb,
124
- ) }
125
- >
126
- { thumbLabel && (
127
- <span className="flex items-center justify-center h-full w-full text-[8px]">
128
- { checkedState ? thumbLabel.on : thumbLabel.off }
129
- </span>
130
- ) }
131
- </RadixSwitch.Thumb>
132
- </RadixSwitch.Root>
133
- { /* Hidden native input for form compat and ref forwarding */ }
134
- <input
135
- ref={ ref }
136
- type="checkbox"
137
- className="sr-only"
138
- checked={ checkedState }
139
- disabled={ disabled }
140
- tabIndex={ -1 }
141
- aria-hidden
142
- onChange={ NOOP }
143
- { ...inputProps }
144
- />
145
- { children != null && (
146
- <span
147
- { ...labelProps }
148
- className={ cn(sizeClasses.label, labelProps?.className) }
149
- >
150
- { children }
151
- </span>
152
- ) }
153
- </label>
154
- );
155
- },
156
- );
157
-
158
- export const Switch = SwitchBase;