@luxfi/ui 7.0.0 → 7.2.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 (70) hide show
  1. package/dist/alert.cjs +184 -335
  2. package/dist/alert.js +184 -335
  3. package/dist/badge.cjs +162 -321
  4. package/dist/badge.d.cts +3 -7
  5. package/dist/badge.d.ts +3 -7
  6. package/dist/badge.js +162 -320
  7. package/dist/bank.d.cts +1 -1
  8. package/dist/bank.d.ts +1 -1
  9. package/dist/button.cjs +373 -484
  10. package/dist/button.d.cts +10 -7
  11. package/dist/button.d.ts +10 -7
  12. package/dist/button.js +373 -484
  13. package/dist/close-button.cjs +37 -18
  14. package/dist/close-button.d.cts +1 -1
  15. package/dist/close-button.d.ts +1 -1
  16. package/dist/close-button.js +37 -18
  17. package/dist/collapsible.cjs +125 -272
  18. package/dist/collapsible.js +125 -272
  19. package/dist/dialog.cjs +60 -44
  20. package/dist/dialog.d.cts +7 -8
  21. package/dist/dialog.d.ts +7 -8
  22. package/dist/dialog.js +60 -43
  23. package/dist/drawer.cjs +37 -13
  24. package/dist/drawer.js +37 -13
  25. package/dist/heading.cjs +3 -8
  26. package/dist/heading.js +3 -8
  27. package/dist/icon-button.cjs +213 -337
  28. package/dist/icon-button.d.cts +4 -4
  29. package/dist/icon-button.d.ts +4 -4
  30. package/dist/icon-button.js +213 -338
  31. package/dist/image.cjs +125 -272
  32. package/dist/image.js +125 -272
  33. package/dist/index.cjs +695 -858
  34. package/dist/index.d.cts +0 -3
  35. package/dist/index.d.ts +0 -3
  36. package/dist/index.js +695 -856
  37. package/dist/input.cjs +25 -36
  38. package/dist/input.js +25 -36
  39. package/dist/link.cjs +125 -272
  40. package/dist/link.js +125 -272
  41. package/dist/popover.cjs +55 -40
  42. package/dist/popover.js +55 -39
  43. package/dist/select.cjs +125 -272
  44. package/dist/select.js +125 -272
  45. package/dist/skeleton.cjs +125 -272
  46. package/dist/skeleton.js +125 -272
  47. package/dist/table.cjs +127 -274
  48. package/dist/table.js +127 -274
  49. package/dist/tag.cjs +199 -341
  50. package/dist/tag.js +199 -340
  51. package/dist/textarea.cjs +25 -36
  52. package/dist/textarea.js +25 -36
  53. package/dist/tooltip.cjs +22 -21
  54. package/dist/tooltip.d.cts +2 -3
  55. package/dist/tooltip.d.ts +2 -3
  56. package/dist/tooltip.js +22 -20
  57. package/package.json +9 -8
  58. package/src/alert.tsx +23 -51
  59. package/src/badge.tsx +20 -31
  60. package/src/button.tsx +304 -238
  61. package/src/close-button.tsx +48 -22
  62. package/src/dialog.tsx +37 -47
  63. package/src/heading.tsx +8 -19
  64. package/src/icon-button.tsx +129 -104
  65. package/src/input.tsx +27 -36
  66. package/src/popover.tsx +30 -44
  67. package/src/skeleton.tsx +96 -144
  68. package/src/tag.tsx +17 -35
  69. package/src/textarea.tsx +27 -36
  70. package/src/tooltip.tsx +54 -47
package/src/input.tsx CHANGED
@@ -1,41 +1,32 @@
1
- import { cva } from 'class-variance-authority';
2
1
  import React from 'react';
3
2
 
4
3
  import { cn } from './utils';
5
4
 
6
- const inputVariants = cva(
7
- [
8
- 'w-full appearance-none outline-none transition-colors',
9
- 'bg-[var(--color-input-bg)] text-[var(--color-input-fg)] border-[var(--color-input-border)]',
10
- 'placeholder:text-[var(--color-input-placeholder,theme(colors.gray.400))]',
11
- 'hover:border-[var(--color-input-border-hover,theme(colors.gray.400))]',
12
- 'focus:border-[var(--color-input-border-focus,theme(colors.blue.500))]',
13
- 'focus:placeholder:text-[var(--color-input-placeholder-focus,theme(colors.gray.300))]',
14
- 'disabled:opacity-40 disabled:cursor-not-allowed',
15
- 'read-only:opacity-70',
16
- 'data-[invalid]:border-[var(--color-input-border-invalid,theme(colors.red.500))]',
17
- ].join(' '),
18
- {
19
- variants: {
20
- size: {
21
- xs: 'h-6 px-2 text-xs rounded',
22
- sm: 'h-8 px-3 text-sm rounded-md',
23
- md: 'h-10 px-4 text-base rounded-md',
24
- lg: 'h-12 px-4 text-lg rounded-lg',
25
- '2xl': 'h-14 px-4 text-xl rounded-lg',
26
- },
27
- variant: {
28
- outline: 'border border-solid',
29
- filled: 'border-0 bg-[var(--color-input-filled-bg,theme(colors.gray.100))]',
30
- unstyled: 'border-0 bg-transparent p-0 h-auto',
31
- },
32
- },
33
- defaultVariants: {
34
- size: 'md',
35
- variant: 'outline',
36
- },
37
- },
38
- );
5
+ const INPUT_BASE = [
6
+ 'w-full appearance-none outline-none transition-colors',
7
+ 'bg-[var(--color-input-bg)] text-[var(--color-input-fg)] border-[var(--color-input-border)]',
8
+ 'placeholder:text-[var(--color-input-placeholder,theme(colors.gray.400))]',
9
+ 'hover:border-[var(--color-input-border-hover,theme(colors.gray.400))]',
10
+ 'focus:border-[var(--color-input-border-focus,theme(colors.blue.500))]',
11
+ 'focus:placeholder:text-[var(--color-input-placeholder-focus,theme(colors.gray.300))]',
12
+ 'disabled:opacity-40 disabled:cursor-not-allowed',
13
+ 'read-only:opacity-70',
14
+ 'data-[invalid]:border-[var(--color-input-border-invalid,theme(colors.red.500))]',
15
+ ].join(' ');
16
+
17
+ const INPUT_SIZE_CLASSES: Record<string, string> = {
18
+ xs: 'h-6 px-2 text-xs rounded',
19
+ sm: 'h-8 px-3 text-sm rounded-md',
20
+ md: 'h-10 px-4 text-base rounded-md',
21
+ lg: 'h-12 px-4 text-lg rounded-lg',
22
+ '2xl': 'h-14 px-4 text-xl rounded-lg',
23
+ };
24
+
25
+ const INPUT_VARIANT_CLASSES: Record<string, string> = {
26
+ outline: 'border border-solid',
27
+ filled: 'border-0 bg-[var(--color-input-filled-bg,theme(colors.gray.100))]',
28
+ unstyled: 'border-0 bg-transparent p-0 h-auto',
29
+ };
39
30
 
40
31
  type InputSize = 'xs' | 'sm' | 'md' | 'lg' | '2xl';
41
32
  type InputVariant = 'outline' | 'filled' | 'unstyled';
@@ -46,11 +37,11 @@ export interface InputProps extends Omit<React.ComponentPropsWithRef<'input'>, '
46
37
  }
47
38
 
48
39
  export const Input = React.forwardRef<HTMLInputElement, InputProps>(
49
- ({ size, variant, className, ...rest }, ref) => {
40
+ ({ size = 'md', variant = 'outline', className, ...rest }, ref) => {
50
41
  return (
51
42
  <input
52
43
  ref={ ref }
53
- className={ cn(inputVariants({ size, variant }), className) }
44
+ className={ cn(INPUT_BASE, INPUT_SIZE_CLASSES[size], INPUT_VARIANT_CLASSES[variant], className) }
54
45
  { ...rest }
55
46
  />
56
47
  );
package/src/popover.tsx CHANGED
@@ -1,11 +1,11 @@
1
- import * as RadixPopover from '@radix-ui/react-popover';
1
+ import { Popover as GuiPopover } from '@hanzogui/popover';
2
2
  import * as React from 'react';
3
3
 
4
4
  import { cn } from './utils';
5
5
 
6
6
  import { CloseButton } from './close-button';
7
7
 
8
- // --- Utility: map Chakra-style placement string to Radix side + align ---
8
+ // --- Utility: map Chakra-style placement string to @hanzogui placement ---
9
9
 
10
10
  type Side = 'top' | 'right' | 'bottom' | 'left';
11
11
  type Align = 'start' | 'center' | 'end';
@@ -61,7 +61,7 @@ export interface PopoverRootProps {
61
61
  }
62
62
 
63
63
  // Stash positioning info in context so PopoverContent can read it.
64
- interface PopoverPositioning {
64
+ interface PopoverPositioningCtx {
65
65
  readonly side: Side;
66
66
  readonly align: Align;
67
67
  readonly sideOffset: number;
@@ -71,7 +71,7 @@ interface PopoverPositioning {
71
71
  readonly closeOnInteractOutside: boolean;
72
72
  }
73
73
 
74
- const PositioningContext = React.createContext<PopoverPositioning>({
74
+ const PositioningContext = React.createContext<PopoverPositioningCtx>({
75
75
  side: 'bottom',
76
76
  align: 'start',
77
77
  sideOffset: 4,
@@ -90,8 +90,7 @@ export const PopoverRoot = (props: PopoverRootProps): React.ReactElement => {
90
90
  positioning,
91
91
  autoFocus = false,
92
92
  closeOnInteractOutside = true,
93
- modal = false,
94
- // lazyMount and unmountOnExit are handled via forceMount on Portal/Content
93
+ // lazyMount and unmountOnExit are handled via keepChildrenMounted
95
94
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
96
95
  lazyMount: _lazyMount,
97
96
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -111,7 +110,7 @@ export const PopoverRoot = (props: PopoverRootProps): React.ReactElement => {
111
110
 
112
111
  const { side, align } = parsePlacement(mergedPositioning.placement);
113
112
 
114
- const positioningValue = React.useMemo<PopoverPositioning>(() => ({
113
+ const positioningValue = React.useMemo<PopoverPositioningCtx>(() => ({
115
114
  side,
116
115
  align,
117
116
  sideOffset: mergedPositioning.offset?.mainAxis ?? 4,
@@ -125,21 +124,26 @@ export const PopoverRoot = (props: PopoverRootProps): React.ReactElement => {
125
124
  mergedPositioning.overflowPadding, autoFocus, closeOnInteractOutside,
126
125
  ]);
127
126
 
128
- // Bridge Chakra-style onOpenChange ({ open }) to Radix (open)
127
+ // Bridge Chakra-style onOpenChange ({ open }) to @hanzogui (open)
129
128
  const handleOpenChange = React.useCallback((isOpen: boolean) => {
130
129
  onOpenChange?.({ open: isOpen });
131
130
  }, [ onOpenChange ]);
132
131
 
132
+ const placement = mergedPositioning.placement ?? 'bottom-start';
133
+ const offset = mergedPositioning.offset?.mainAxis ?? 4;
134
+
133
135
  return (
134
136
  <PositioningContext.Provider value={ positioningValue }>
135
- <RadixPopover.Root
137
+ <GuiPopover
136
138
  open={ open }
137
139
  defaultOpen={ defaultOpen }
138
140
  onOpenChange={ handleOpenChange }
139
- modal={ modal }
141
+ placement={ placement as any }
142
+ offset={ offset }
143
+ disableFocus={ !autoFocus }
140
144
  >
141
145
  { children }
142
- </RadixPopover.Root>
146
+ </GuiPopover>
143
147
  </PositioningContext.Provider>
144
148
  );
145
149
  };
@@ -155,7 +159,7 @@ export const PopoverTrigger = React.forwardRef<
155
159
  PopoverTriggerProps
156
160
  >(function PopoverTrigger(props, ref) {
157
161
  const { asChild = true, ...rest } = props;
158
- return <RadixPopover.Trigger asChild={ asChild } ref={ ref } { ...rest }/>;
162
+ return <GuiPopover.Trigger asChild={ asChild ? 'except-style' : undefined } ref={ ref } { ...rest as any }/>;
159
163
  });
160
164
 
161
165
  // --- PopoverContent ---
@@ -174,7 +178,7 @@ export const PopoverContent = React.forwardRef<
174
178
  HTMLDivElement,
175
179
  PopoverContentProps
176
180
  >(function PopoverContent(props, ref) {
177
- const { portalled = true, portalRef, className, w, minW, maxW, paddingTop, style: styleProp, ...rest } = props;
181
+ const { portalled = true, portalRef: _portalRef, className, w, minW, maxW, paddingTop, style: styleProp, ...rest } = props;
178
182
  const resolvedW = typeof w === 'object' ? (w as Record<string, string>).base ?? (w as Record<string, string>).lg : w;
179
183
  const contentStyle: React.CSSProperties = {
180
184
  ...styleProp,
@@ -183,21 +187,11 @@ export const PopoverContent = React.forwardRef<
183
187
  ...(maxW ? { maxWidth: maxW } : {}),
184
188
  ...(paddingTop !== undefined ? { paddingTop: typeof paddingTop === 'number' ? `${ paddingTop * 4 }px` : paddingTop } : {}),
185
189
  };
186
- const positioning = React.useContext(PositioningContext);
187
-
188
- const preventFocus = React.useCallback((e: Event) => e.preventDefault(), []);
189
- const preventInteract = React.useCallback((e: Event) => e.preventDefault(), []);
190
190
 
191
- const content = (
192
- <RadixPopover.Content
191
+ return (
192
+ <GuiPopover.Content
193
193
  ref={ ref }
194
- side={ positioning.side }
195
- align={ positioning.align }
196
- sideOffset={ positioning.sideOffset }
197
- alignOffset={ positioning.alignOffset }
198
- collisionPadding={ positioning.collisionPadding }
199
- onOpenAutoFocus={ positioning.autoFocus ? undefined : preventFocus }
200
- onInteractOutside={ positioning.closeOnInteractOutside ? undefined : preventInteract }
194
+ unstyled
201
195
  className={ cn(
202
196
  'z-50 rounded-lg border border-[var(--color-popover-border,var(--color-border-divider))]',
203
197
  'bg-[var(--color-popover-bg,var(--color-dialog-bg))]',
@@ -210,19 +204,9 @@ export const PopoverContent = React.forwardRef<
210
204
  className,
211
205
  ) }
212
206
  style={ Object.keys(contentStyle).length > 0 ? contentStyle : undefined }
213
- { ...rest }
207
+ { ...rest as any }
214
208
  />
215
209
  );
216
-
217
- if (!portalled) {
218
- return content;
219
- }
220
-
221
- return (
222
- <RadixPopover.Portal container={ portalRef?.current ?? undefined }>
223
- { content }
224
- </RadixPopover.Portal>
225
- );
226
210
  });
227
211
 
228
212
  // --- PopoverArrow ---
@@ -235,10 +219,11 @@ export const PopoverArrow = React.forwardRef<
235
219
  >(function PopoverArrow(props, ref) {
236
220
  const { className, ...rest } = props;
237
221
  return (
238
- <RadixPopover.Arrow
222
+ <GuiPopover.Arrow
239
223
  ref={ ref }
224
+ unstyled
240
225
  className={ cn('fill-[var(--color-popover-bg,var(--color-dialog-bg))]', className) }
241
- { ...rest }
226
+ { ...rest as any }
242
227
  />
243
228
  );
244
229
  });
@@ -253,14 +238,15 @@ export const PopoverCloseTrigger = React.forwardRef<
253
238
  >(function PopoverCloseTrigger(props, ref) {
254
239
  const { className, ...rest } = props;
255
240
  return (
256
- <RadixPopover.Close
241
+ <GuiPopover.Close
242
+ unstyled
257
243
  className={ cn('absolute top-1 right-1', className) }
258
- { ...rest }
244
+ { ...rest as any }
259
245
  asChild
260
246
  ref={ ref }
261
247
  >
262
248
  <CloseButton/>
263
- </RadixPopover.Close>
249
+ </GuiPopover.Close>
264
250
  );
265
251
  });
266
252
 
@@ -281,9 +267,9 @@ export const PopoverCloseTriggerWrapper = React.forwardRef<
281
267
  }
282
268
 
283
269
  return (
284
- <RadixPopover.Close ref={ ref } { ...rest } asChild>
270
+ <GuiPopover.Close ref={ ref } { ...rest as any } asChild>
285
271
  { children }
286
- </RadixPopover.Close>
272
+ </GuiPopover.Close>
287
273
  );
288
274
  });
289
275
 
package/src/skeleton.tsx CHANGED
@@ -12,7 +12,7 @@ export interface SkeletonProps extends Omit<React.HTMLAttributes<HTMLDivElement>
12
12
  /** When true, the Skeleton wraps its single child element instead of adding a wrapper div. */
13
13
  readonly asChild?: boolean;
14
14
 
15
- // Legacy Chakra style-prop shims — converted to className / inline style.
15
+ // Legacy Chakra style-prop shims — converted to inline style.
16
16
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
17
17
  readonly w?: any;
18
18
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -59,166 +59,124 @@ export interface SkeletonProps extends Omit<React.HTMLAttributes<HTMLDivElement>
59
59
  readonly background?: string;
60
60
  }
61
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`;
62
+ // Style-prop shim names that must NOT leak into the DOM.
63
+ const SHIM_PROPS = [
64
+ 'w', 'h', 'minW', 'maxW', 'display', 'flexGrow', 'flexShrink', 'flexBasis',
65
+ 'fontWeight', 'textStyle', 'borderRadius', 'alignSelf', 'alignItems',
66
+ 'justifyContent', 'color', 'mt', 'mb', 'ml', 'mr', 'height', 'overflow',
67
+ 'whiteSpace', 'textOverflow', 'textTransform', 'gap', 'gridTemplateColumns',
68
+ 'minWidth', 'boxSize', 'py', 'px', 'p', 'hideBelow', 'fontSize', 'flexWrap',
69
+ 'wordBreak', 'lineHeight', 'marginRight', 'position', 'background',
70
+ ] as const;
71
+
72
+ const S = 4; // Chakra spacing scale
73
+
74
+ /** Resolve a dimension value (number → px via spacing scale, responsive object → first value). */
75
+ function dim(v: unknown): string | undefined {
76
+ if (v == null) return undefined;
77
+ if (typeof v === 'number') return `${ v * S }px`;
78
+ if (typeof v === 'string') return v;
79
+ if (typeof v === 'object') {
80
+ const o = v as Record<string, string>;
81
+ return o.base ?? o.lg ?? o.xl ?? Object.values(o)[0];
82
+ }
83
+ return undefined;
68
84
  }
69
85
 
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
- }
86
+ /** Convert a spacing number|string via the 4px scale. */
87
+ function sp(v: number | string | undefined): string | undefined {
88
+ if (v === undefined) return undefined;
89
+ return typeof v === 'number' ? `${ v * S }px` : v;
90
+ }
87
91
 
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
- }
92
+ /** Build a CSSProperties object from legacy Chakra style-prop shims. */
93
+ function shimToStyle(props: SkeletonProps): React.CSSProperties {
94
+ const s: React.CSSProperties = {};
95
+ if (props.w !== undefined) s.width = dim(props.w);
96
+ if (props.h !== undefined) s.height = dim(props.h);
97
+ if (props.minW !== undefined) s.minWidth = dim(props.minW);
98
+ if (props.maxW !== undefined) s.maxWidth = dim(props.maxW);
99
+ if (props.display !== undefined) s.display = props.display;
100
+ if (props.flexGrow !== undefined) s.flexGrow = props.flexGrow;
101
+ if (props.flexShrink !== undefined) s.flexShrink = props.flexShrink;
102
+ if (props.flexBasis !== undefined) s.flexBasis = props.flexBasis;
103
+ if (props.fontWeight !== undefined) s.fontWeight = props.fontWeight;
104
+ if (props.borderRadius !== undefined) s.borderRadius = props.borderRadius;
105
+ if (props.alignSelf !== undefined) s.alignSelf = props.alignSelf;
106
+ if (props.alignItems !== undefined) s.alignItems = props.alignItems;
107
+ if (props.justifyContent !== undefined) s.justifyContent = props.justifyContent;
108
+ if (props.color !== undefined) s.color = props.color;
109
+ if (props.mt !== undefined) s.marginTop = sp(props.mt);
110
+ if (props.mb !== undefined) s.marginBottom = sp(props.mb);
111
+ if (props.ml !== undefined) s.marginLeft = sp(props.ml);
112
+ if (props.mr !== undefined) s.marginRight = sp(props.mr);
113
+ if (props.height !== undefined) s.height = props.height;
114
+ if (props.overflow !== undefined) s.overflow = props.overflow;
115
+ if (props.whiteSpace !== undefined) s.whiteSpace = props.whiteSpace;
116
+ if (props.textOverflow !== undefined) s.textOverflow = props.textOverflow;
117
+ if (props.textTransform !== undefined) s.textTransform = props.textTransform;
118
+ if (props.gap !== undefined) s.gap = sp(props.gap);
119
+ if (props.gridTemplateColumns !== undefined) s.gridTemplateColumns = props.gridTemplateColumns;
120
+ if (props.minWidth !== undefined) s.minWidth = props.minWidth;
121
+ if (props.boxSize !== undefined) {
122
+ const v = typeof props.boxSize === 'number' ? `${ props.boxSize * S }px` : props.boxSize;
123
+ s.width = v; s.height = v;
148
124
  }
125
+ if (props.py !== undefined) { const v = sp(props.py); s.paddingTop = v; s.paddingBottom = v; }
126
+ if (props.px !== undefined) { const v = sp(props.px); s.paddingLeft = v; s.paddingRight = v; }
127
+ if (props.p !== undefined) s.padding = sp(props.p);
128
+ if (props.fontSize !== undefined) s.fontSize = props.fontSize;
129
+ if (props.flexWrap !== undefined) s.flexWrap = props.flexWrap;
130
+ if (props.wordBreak !== undefined) s.wordBreak = props.wordBreak;
131
+ if (props.lineHeight !== undefined) s.lineHeight = props.lineHeight;
132
+ if (props.marginRight !== undefined) s.marginRight = props.marginRight;
133
+ if (props.position !== undefined) s.position = props.position;
134
+ if (props.background !== undefined) s.background = props.background;
135
+ return s;
136
+ }
149
137
 
150
- return { style, rest };
138
+ /** Strip shim keys from props so they don't leak into the DOM. */
139
+ function stripShims<T extends Record<string, unknown>>(props: T): Omit<T, (typeof SHIM_PROPS)[number]> {
140
+ const out = { ...props };
141
+ for (const k of SHIM_PROPS) delete out[k];
142
+ return out as Omit<T, (typeof SHIM_PROPS)[number]>;
151
143
  }
152
144
 
145
+ const SKELETON_CLASSES = [
146
+ 'animate-skeleton-shimmer rounded-sm',
147
+ 'bg-[linear-gradient(90deg,var(--color-skeleton-start)_0%,var(--color-skeleton-end)_50%,var(--color-skeleton-start)_100%)]',
148
+ 'bg-[length:200%_100%]',
149
+ ].join(' ');
150
+
151
+ const HIDE_BELOW_MAP: Record<string, string> = { lg: 'lg:hidden', md: 'md:hidden', sm: 'sm:hidden' };
152
+
153
153
  export const Skeleton = React.forwardRef<HTMLDivElement, SkeletonProps>(
154
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;
155
+ const { loading = false, asChild, className, children, style: styleProp, as: Component = 'div', ...allRest } = props;
175
156
 
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;
157
+ const shimStyle = shimToStyle(props);
158
+ const mergedStyle = Object.keys(shimStyle).length > 0 || styleProp ? { ...shimStyle, ...styleProp } : undefined;
159
+ const hideBelowClass = props.hideBelow ? HIDE_BELOW_MAP[props.hideBelow] : undefined;
160
+ const cls = hideBelowClass ? cn(className, hideBelowClass) : className;
192
161
 
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;
162
+ // Strip shim props from what reaches the DOM.
163
+ const htmlRest = stripShims(allRest);
196
164
 
197
165
  if (!loading) {
198
- // When asChild is true, render children directly without a wrapper div
199
- if (asChild && React.isValidElement(children)) {
200
- return children;
201
- }
166
+ if (asChild && React.isValidElement(children)) return children;
202
167
  return (
203
- <Component ref={ ref } className={ finalClassName } style={ mergedStyle } { ...htmlRest }>
168
+ <Component ref={ ref } className={ cls } style={ mergedStyle } { ...htmlRest }>
204
169
  { children }
205
170
  </Component>
206
171
  );
207
172
  }
208
173
 
209
- // When asChild is true and loading, wrap the child in skeleton styles
210
174
  if (asChild && React.isValidElement(children)) {
211
175
  return (
212
176
  <Component
213
177
  ref={ ref }
214
178
  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
- ) }
179
+ className={ cn(SKELETON_CLASSES, 'text-transparent [&_*]:invisible', cls) }
222
180
  style={ mergedStyle }
223
181
  { ...htmlRest }
224
182
  >
@@ -231,13 +189,7 @@ export const Skeleton = React.forwardRef<HTMLDivElement, SkeletonProps>(
231
189
  <Component
232
190
  ref={ ref }
233
191
  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
- ) }
192
+ className={ cn(SKELETON_CLASSES, children ? 'text-transparent [&_*]:invisible' : 'min-h-5', cls) }
241
193
  style={ mergedStyle }
242
194
  { ...htmlRest }
243
195
  >
package/src/tag.tsx CHANGED
@@ -1,4 +1,3 @@
1
- import { cva } from 'class-variance-authority';
2
1
  import * as React from 'react';
3
2
 
4
3
  import { cn } from './utils';
@@ -22,39 +21,20 @@ function TruncatedTextTooltip({ label, children }: { readonly label: React.React
22
21
  type TagVariant = 'subtle' | 'clickable' | 'filter' | 'select';
23
22
  type TagSize = 'sm' | 'md' | 'lg';
24
23
 
25
- const tagVariants = cva(
26
- [
27
- 'inline-flex items-center align-top max-w-full select-text rounded-sm',
28
- 'focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-500',
29
- ].join(' '),
30
- {
31
- variants: {
32
- variant: {
33
- subtle: 'bg-[var(--color-tag-subtle-bg)] text-[var(--color-tag-subtle-fg)]',
34
- clickable: [
35
- 'cursor-pointer',
36
- 'bg-[var(--color-tag-clickable-bg)] text-[var(--color-tag-clickable-fg)]',
37
- 'hover:opacity-76',
38
- ].join(' '),
39
- filter: 'bg-[var(--color-tag-filter-bg)]',
40
- select: [
41
- 'cursor-pointer',
42
- 'bg-[var(--color-tag-select-bg)] text-[var(--color-tag-select-fg)]',
43
- 'hover:text-[var(--color-hover)] hover:opacity-76',
44
- ].join(' '),
45
- },
46
- size: {
47
- sm: 'px-1 py-0.5 min-h-5 gap-1 text-xs',
48
- md: 'px-1 py-0.5 min-h-6 gap-1 text-sm',
49
- lg: 'px-1.5 py-1.5 min-h-8 min-w-8 gap-1 text-sm',
50
- },
51
- },
52
- defaultVariants: {
53
- variant: 'subtle',
54
- size: 'md',
55
- },
56
- },
57
- );
24
+ const TAG_BASE = 'inline-flex items-center align-top max-w-full select-text rounded-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-500';
25
+
26
+ const TAG_VARIANT_CLASSES: Record<TagVariant, string> = {
27
+ subtle: 'bg-[var(--color-tag-subtle-bg)] text-[var(--color-tag-subtle-fg)]',
28
+ clickable: 'cursor-pointer bg-[var(--color-tag-clickable-bg)] text-[var(--color-tag-clickable-fg)] hover:opacity-76',
29
+ filter: 'bg-[var(--color-tag-filter-bg)]',
30
+ select: 'cursor-pointer bg-[var(--color-tag-select-bg)] text-[var(--color-tag-select-fg)] hover:text-[var(--color-hover)] hover:opacity-76',
31
+ };
32
+
33
+ const TAG_SIZE_CLASSES: Record<TagSize, string> = {
34
+ sm: 'px-1 py-0.5 min-h-5 gap-1 text-xs',
35
+ md: 'px-1 py-0.5 min-h-6 gap-1 text-sm',
36
+ lg: 'px-1.5 py-1.5 min-h-8 min-w-8 gap-1 text-sm',
37
+ };
58
38
 
59
39
  const TAG_SELECTED_CLASSES = [
60
40
  'bg-[var(--color-tag-select-selected-bg)]',
@@ -121,7 +101,9 @@ export const Tag = React.forwardRef<HTMLSpanElement, TagProps>(
121
101
  <span
122
102
  ref={ ref }
123
103
  className={ cn(
124
- tagVariants({ variant, size }),
104
+ TAG_BASE,
105
+ TAG_VARIANT_CLASSES[variant ?? 'subtle'],
106
+ TAG_SIZE_CLASSES[size ?? 'md'],
125
107
  selected && !loading && TAG_SELECTED_CLASSES,
126
108
  disabled && TAG_DISABLED_CLASSES,
127
109
  className,