@luxfi/ui 7.0.0 → 7.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/dist/alert.cjs +162 -285
  2. package/dist/alert.js +162 -285
  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/button.cjs +373 -484
  8. package/dist/button.d.cts +10 -7
  9. package/dist/button.d.ts +10 -7
  10. package/dist/button.js +373 -484
  11. package/dist/close-button.cjs +37 -18
  12. package/dist/close-button.d.cts +1 -1
  13. package/dist/close-button.d.ts +1 -1
  14. package/dist/close-button.js +37 -18
  15. package/dist/collapsible.cjs +125 -272
  16. package/dist/collapsible.js +125 -272
  17. package/dist/dialog.cjs +60 -44
  18. package/dist/dialog.d.cts +7 -8
  19. package/dist/dialog.d.ts +7 -8
  20. package/dist/dialog.js +60 -43
  21. package/dist/drawer.cjs +37 -13
  22. package/dist/drawer.js +37 -13
  23. package/dist/heading.cjs +3 -8
  24. package/dist/heading.js +3 -8
  25. package/dist/icon-button.cjs +213 -337
  26. package/dist/icon-button.d.cts +4 -4
  27. package/dist/icon-button.d.ts +4 -4
  28. package/dist/icon-button.js +213 -338
  29. package/dist/image.cjs +125 -272
  30. package/dist/image.js +125 -272
  31. package/dist/index.cjs +604 -700
  32. package/dist/index.d.cts +0 -3
  33. package/dist/index.d.ts +0 -3
  34. package/dist/index.js +604 -698
  35. package/dist/link.cjs +125 -272
  36. package/dist/link.js +125 -272
  37. package/dist/popover.cjs +55 -40
  38. package/dist/popover.js +55 -39
  39. package/dist/select.cjs +125 -272
  40. package/dist/select.js +125 -272
  41. package/dist/skeleton.cjs +125 -272
  42. package/dist/skeleton.js +125 -272
  43. package/dist/table.cjs +127 -274
  44. package/dist/table.js +127 -274
  45. package/dist/tag.cjs +184 -306
  46. package/dist/tag.js +184 -305
  47. package/dist/tooltip.cjs +22 -21
  48. package/dist/tooltip.d.cts +2 -3
  49. package/dist/tooltip.d.ts +2 -3
  50. package/dist/tooltip.js +22 -20
  51. package/package.json +9 -7
  52. package/src/badge.tsx +20 -31
  53. package/src/button.tsx +304 -238
  54. package/src/close-button.tsx +48 -22
  55. package/src/dialog.tsx +37 -47
  56. package/src/heading.tsx +8 -19
  57. package/src/icon-button.tsx +129 -104
  58. package/src/popover.tsx +30 -44
  59. package/src/skeleton.tsx +96 -144
  60. package/src/tooltip.tsx +54 -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/tooltip.tsx CHANGED
@@ -1,4 +1,7 @@
1
- import * as RadixTooltip from '@radix-ui/react-tooltip';
1
+ import {
2
+ Tooltip as GuiTooltip,
3
+ TooltipGroup as GuiTooltipGroup,
4
+ } from '@hanzogui/tooltip';
2
5
  import { useClickAway } from '@uidotdev/usehooks';
3
6
  import * as React from 'react';
4
7
 
@@ -24,14 +27,21 @@ function useIsMobile(): boolean {
24
27
  return isMobile;
25
28
  }
26
29
 
30
+ /** Map placement string to @hanzogui Popper placement prop. */
31
+ function mapPlacement(p: string | undefined): string | undefined {
32
+ if (!p) return undefined;
33
+ // @hanzogui/popper uses same placement strings as floating-ui
34
+ return p;
35
+ }
36
+
27
37
  export interface TooltipProps {
28
38
  selected?: boolean;
29
39
  showArrow?: boolean;
30
40
  portalled?: boolean;
31
41
  portalRef?: React.RefObject<HTMLElement>;
32
42
  content: React.ReactNode;
33
- contentProps?: React.ComponentPropsWithoutRef<typeof RadixTooltip.Content>;
34
- triggerProps?: React.ComponentPropsWithoutRef<typeof RadixTooltip.Trigger>;
43
+ contentProps?: React.HTMLAttributes<HTMLDivElement>;
44
+ triggerProps?: React.HTMLAttributes<HTMLButtonElement>;
35
45
  disabled?: boolean;
36
46
  disableOnMobile?: boolean;
37
47
  children?: React.ReactNode;
@@ -63,10 +73,8 @@ export const Tooltip = React.forwardRef<HTMLDivElement, TooltipProps>(
63
73
  children,
64
74
  disabled,
65
75
  disableOnMobile,
66
- portalled = true,
67
76
  content,
68
77
  contentProps,
69
- portalRef,
70
78
  defaultOpen = false,
71
79
  triggerProps,
72
80
  closeDelay = 100,
@@ -127,58 +135,57 @@ export const Tooltip = React.forwardRef<HTMLDivElement, TooltipProps>(
127
135
  const defaultShowArrow = variant === 'popover' ? false : true;
128
136
  const showArrow = showArrowProp !== undefined ? showArrowProp : defaultShowArrow;
129
137
 
130
- const placement = positioning?.placement ?? 'top';
131
- const side = placement.split('-')[0] as 'top' | 'bottom' | 'left' | 'right';
132
- const align = placement.includes('-') ? (placement.split('-')[1] as 'start' | 'end') : undefined;
133
- const sideOffset = positioning?.offset?.mainAxis ?? 4;
134
-
135
138
  const isPopover = variant === 'popover';
136
139
 
140
+ const placement = mapPlacement(positioning?.placement) ?? 'top';
141
+ const offset = positioning?.offset?.mainAxis ?? 4;
142
+
137
143
  return (
138
- <RadixTooltip.Provider delayDuration={ openDelay } skipDelayDuration={ 0 }>
139
- <RadixTooltip.Root
144
+ <GuiTooltipGroup delay={ openDelay }>
145
+ <GuiTooltip
140
146
  open={ open }
141
147
  onOpenChange={ handleOpenChange }
142
- delayDuration={ openDelay }
148
+ delay={{ open: openDelay, close: closeDelay }}
149
+ placement={ placement as any }
150
+ offset={ offset }
151
+ unstyled
143
152
  >
144
- <RadixTooltip.Trigger
153
+ <GuiTooltip.Trigger
145
154
  ref={ open ? triggerRef : undefined }
146
155
  asChild
147
- onClick={ isMobile ? handleTriggerClick : undefined }
148
- { ...triggerProps }
156
+ {...(isMobile ? { onPress: handleTriggerClick } : {})}
157
+ { ...triggerProps as any }
149
158
  >
150
159
  { children }
151
- </RadixTooltip.Trigger>
152
- <RadixTooltip.Portal container={ portalled ? (portalRef?.current ?? undefined) : undefined }>
153
- <RadixTooltip.Content
154
- ref={ ref }
155
- side={ side }
156
- align={ align }
157
- sideOffset={ sideOffset }
158
- onClick={ interactive ? handleContentClick : undefined }
159
- className={ cn(
160
- 'z-[9999] overflow-hidden rounded-lg px-3 py-2 text-sm',
161
- 'animate-in fade-in-0 zoom-in-95',
162
- isPopover && 'bg-[var(--color-popover-bg)] text-[var(--color-text-primary)]',
163
- isPopover && 'shadow-[var(--shadow-popover)] border border-[var(--color-border-divider)] max-w-sm',
164
- !isPopover && 'bg-[var(--color-tooltip-bg)] text-[var(--color-tooltip-fg)] max-w-xs',
165
- contentProps?.className,
166
- ) }
167
- { ...(selected ? { 'data-selected': true } : {}) }
168
- { ...contentProps }
169
- >
170
- { showArrow && (
171
- <RadixTooltip.Arrow
172
- className={ cn(
173
- isPopover ? 'fill-[var(--color-popover-bg)]' : 'fill-[var(--color-tooltip-bg)]',
174
- ) }
175
- />
176
- ) }
177
- { content }
178
- </RadixTooltip.Content>
179
- </RadixTooltip.Portal>
180
- </RadixTooltip.Root>
181
- </RadixTooltip.Provider>
160
+ </GuiTooltip.Trigger>
161
+
162
+ <GuiTooltip.Content
163
+ ref={ ref }
164
+ unstyled
165
+ className={ cn(
166
+ 'z-[9999] overflow-hidden rounded-lg px-3 py-2 text-sm',
167
+ 'animate-in fade-in-0 zoom-in-95',
168
+ isPopover && 'bg-[var(--color-popover-bg)] text-[var(--color-text-primary)]',
169
+ isPopover && 'shadow-[var(--shadow-popover)] border border-[var(--color-border-divider)] max-w-sm',
170
+ !isPopover && 'bg-[var(--color-tooltip-bg)] text-[var(--color-tooltip-fg)] max-w-xs',
171
+ contentProps?.className,
172
+ ) }
173
+ onClick={ interactive ? handleContentClick : undefined }
174
+ { ...(selected ? { 'data-selected': true } : {}) }
175
+ { ...contentProps as any }
176
+ >
177
+ { showArrow && (
178
+ <GuiTooltip.Arrow
179
+ unstyled
180
+ className={ cn(
181
+ isPopover ? 'fill-[var(--color-popover-bg)]' : 'fill-[var(--color-tooltip-bg)]',
182
+ ) }
183
+ />
184
+ ) }
185
+ { content }
186
+ </GuiTooltip.Content>
187
+ </GuiTooltip>
188
+ </GuiTooltipGroup>
182
189
  );
183
190
  },
184
191
  );