@luxfi/ui 7.4.11 → 7.4.13

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 (84) hide show
  1. package/dist/alert.cjs +68 -46
  2. package/dist/alert.js +69 -46
  3. package/dist/avatar.cjs +49 -47
  4. package/dist/avatar.js +52 -49
  5. package/dist/badge.cjs +82 -49
  6. package/dist/badge.js +83 -50
  7. package/dist/checkbox.cjs +42 -75
  8. package/dist/checkbox.js +43 -76
  9. package/dist/chrome.cjs +3 -7
  10. package/dist/chrome.js +3 -7
  11. package/dist/close-button.cjs +3 -7
  12. package/dist/close-button.js +3 -7
  13. package/dist/dialog.cjs +3 -7
  14. package/dist/dialog.js +3 -7
  15. package/dist/drawer.cjs +3 -7
  16. package/dist/drawer.js +3 -7
  17. package/dist/empty-state.cjs +13 -13
  18. package/dist/empty-state.js +13 -13
  19. package/dist/expiration-selector.cjs +58 -63
  20. package/dist/expiration-selector.js +58 -63
  21. package/dist/greeks-display.cjs +48 -39
  22. package/dist/greeks-display.js +48 -39
  23. package/dist/index.cjs +1053 -888
  24. package/dist/index.js +1054 -889
  25. package/dist/input-group.cjs +36 -19
  26. package/dist/input-group.js +37 -19
  27. package/dist/option-chain.cjs +89 -67
  28. package/dist/option-chain.js +89 -67
  29. package/dist/option-position.cjs +88 -85
  30. package/dist/option-position.js +88 -85
  31. package/dist/pin-input.cjs +30 -29
  32. package/dist/pin-input.js +30 -29
  33. package/dist/pnl-diagram.cjs +20 -20
  34. package/dist/pnl-diagram.js +20 -20
  35. package/dist/popover.cjs +3 -7
  36. package/dist/popover.js +3 -7
  37. package/dist/progress-circle.cjs +40 -22
  38. package/dist/progress-circle.d.cts +5 -5
  39. package/dist/progress-circle.d.ts +5 -5
  40. package/dist/progress-circle.js +41 -22
  41. package/dist/progress.cjs +15 -22
  42. package/dist/progress.d.cts +6 -6
  43. package/dist/progress.d.ts +6 -6
  44. package/dist/progress.js +15 -22
  45. package/dist/radio.cjs +35 -68
  46. package/dist/radio.d.cts +18 -18
  47. package/dist/radio.d.ts +18 -18
  48. package/dist/radio.js +36 -69
  49. package/dist/rating.cjs +15 -17
  50. package/dist/rating.js +15 -17
  51. package/dist/slider.cjs +33 -50
  52. package/dist/slider.js +34 -51
  53. package/dist/strategy-builder.cjs +194 -115
  54. package/dist/strategy-builder.js +194 -115
  55. package/dist/switch.cjs +48 -55
  56. package/dist/switch.js +49 -56
  57. package/dist/tag.cjs +115 -69
  58. package/dist/tag.js +116 -69
  59. package/dist/tooltip.cjs +28 -17
  60. package/dist/tooltip.js +30 -18
  61. package/package.json +1 -1
  62. package/src/alert.tsx +78 -45
  63. package/src/avatar.tsx +54 -38
  64. package/src/badge.tsx +65 -44
  65. package/src/checkbox.tsx +70 -89
  66. package/src/close-button.tsx +3 -8
  67. package/src/empty-state.tsx +21 -17
  68. package/src/expiration-selector.tsx +84 -66
  69. package/src/greeks-display.tsx +59 -51
  70. package/src/input-group.tsx +38 -24
  71. package/src/option-chain.tsx +154 -104
  72. package/src/option-position.tsx +143 -114
  73. package/src/pin-input.tsx +37 -29
  74. package/src/pnl-diagram.tsx +36 -28
  75. package/src/progress-circle.tsx +52 -28
  76. package/src/progress.tsx +17 -21
  77. package/src/radio.tsx +56 -76
  78. package/src/rating.tsx +22 -20
  79. package/src/slider.tsx +43 -45
  80. package/src/strategy-builder.tsx +260 -162
  81. package/src/switch.tsx +63 -64
  82. package/src/tag.tsx +89 -51
  83. package/src/tooltip.tsx +21 -13
  84. package/tokens.css +18 -0
package/src/avatar.tsx CHANGED
@@ -1,9 +1,9 @@
1
1
  'use client';
2
2
 
3
- import { Avatar as GuiAvatar } from '@hanzo/gui';
3
+ import { Avatar as GuiAvatar, XStack } from '@hanzo/gui';
4
4
  import * as React from 'react';
5
5
 
6
- import { cn } from './utils';
6
+ import { ink } from './ink';
7
7
 
8
8
  type ImageProps = React.ImgHTMLAttributes<HTMLImageElement>;
9
9
 
@@ -12,19 +12,29 @@ type ImageProps = React.ImgHTMLAttributes<HTMLImageElement>;
12
12
  type AvatarSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl';
13
13
  type AvatarVariant = 'solid' | 'subtle' | 'outline';
14
14
 
15
- const SIZE_CLASSES: Record<AvatarSize, string> = {
16
- xs: 'h-6 w-6 text-[10px]',
17
- sm: 'h-8 w-8 text-xs',
18
- md: 'h-10 w-10 text-sm',
19
- lg: 'h-12 w-12 text-base',
20
- xl: 'h-16 w-16 text-lg',
21
- '2xl': 'h-20 w-20 text-xl',
15
+ const SIZE: Record<AvatarSize, { width: number; height: number; fontSize: number }> = {
16
+ xs: { width: 24, height: 24, fontSize: 10 },
17
+ sm: { width: 32, height: 32, fontSize: 12 },
18
+ md: { width: 40, height: 40, fontSize: 14 },
19
+ lg: { width: 48, height: 48, fontSize: 16 },
20
+ xl: { width: 64, height: 64, fontSize: 18 },
21
+ '2xl': { width: 80, height: 80, fontSize: 20 },
22
22
  };
23
23
 
24
- const VARIANT_CLASSES: Record<AvatarVariant, string> = {
25
- solid: 'bg-gray-500 text-white',
26
- subtle: 'bg-gray-200 text-gray-700 dark:bg-gray-700 dark:text-gray-200',
27
- outline: 'border-2 border-gray-300 bg-transparent text-gray-700 dark:border-gray-600 dark:text-gray-200',
24
+ // The ring colour matches the page ground rather than staying a fixed white,
25
+ // so a stack of overlapping avatars reads as cut out of the page in both
26
+ // themes — the same job `ring-white dark:ring-gray-900` did.
27
+ const RING = 'var(--color-bg-body)' as never;
28
+
29
+ const VARIANT: Record<AvatarVariant, Record<string, unknown>> = {
30
+ solid: { backgroundColor: 'var(--color-gray-500)' as never, color: 'var(--color-white)' as never },
31
+ subtle: { backgroundColor: 'var(--color-badge-gray-bg)' as never, color: 'var(--color-badge-gray-fg)' as never },
32
+ outline: {
33
+ borderWidth: 2,
34
+ borderColor: 'var(--color-input-border)' as never,
35
+ backgroundColor: 'transparent',
36
+ color: 'var(--color-text-secondary)' as never,
37
+ },
28
38
  };
29
39
 
30
40
  const DEFAULT_SIZE: AvatarSize = 'md';
@@ -69,7 +79,6 @@ export const Avatar = React.forwardRef<HTMLSpanElement, AvatarProps>(
69
79
  icon,
70
80
  fallback,
71
81
  children,
72
- className,
73
82
  size: sizeProp,
74
83
  variant: variantProp,
75
84
  borderless,
@@ -85,14 +94,16 @@ export const Avatar = React.forwardRef<HTMLSpanElement, AvatarProps>(
85
94
  return (
86
95
  <GuiAvatar
87
96
  ref={ ref as never }
88
- unstyled
89
- className={ cn(
90
- 'relative inline-flex shrink-0 items-center justify-center overflow-hidden rounded-full align-middle',
91
- SIZE_CLASSES[size],
92
- VARIANT_CLASSES[variant],
93
- !isBorderless && 'ring-2 ring-white dark:ring-gray-900',
94
- className,
95
- ) }
97
+ position="relative"
98
+ display="inline-flex"
99
+ flexShrink={ 0 }
100
+ alignItems="center"
101
+ justifyContent="center"
102
+ overflow="hidden"
103
+ borderRadius={ 9999 }
104
+ { ...SIZE[size] }
105
+ { ...VARIANT[variant] }
106
+ boxShadow={ isBorderless ? undefined : `0 0 0 2px ${ RING }` }
96
107
  { ...(rest as object) }
97
108
  >
98
109
  <AvatarFallback name={ name } icon={ icon }>
@@ -100,7 +111,10 @@ export const Avatar = React.forwardRef<HTMLSpanElement, AvatarProps>(
100
111
  </AvatarFallback>
101
112
  { src != null && (
102
113
  <GuiAvatar.Image
103
- className="h-full w-full rounded-[inherit] object-cover"
114
+ width="100%"
115
+ height="100%"
116
+ borderRadius={ 9999 }
117
+ objectFit="cover"
104
118
  src={ src }
105
119
  srcSet={ srcSet }
106
120
  loading={ loading }
@@ -122,20 +136,18 @@ interface AvatarFallbackInternalProps extends React.ComponentPropsWithoutRef<'sp
122
136
 
123
137
  const AvatarFallback = React.forwardRef<HTMLSpanElement, AvatarFallbackInternalProps>(
124
138
  function AvatarFallback(props, ref) {
125
- const { name, icon, children, className, ...rest } = props;
139
+ const { name, icon, children, ...rest } = props;
126
140
  return (
127
141
  <GuiAvatar.Fallback
128
142
  ref={ ref as never }
129
- unstyled
130
- className={ cn(
131
- 'flex h-full w-full items-center justify-center font-medium',
132
- className,
133
- ) }
143
+ display="flex"
144
+ width="100%"
145
+ height="100%"
146
+ alignItems="center"
147
+ justifyContent="center"
134
148
  { ...(rest as object) }
135
149
  >
136
- { children }
137
- { name != null && children == null && <>{ getInitials(name) }</> }
138
- { name == null && children == null && icon }
150
+ { ink(children ?? (name != null ? getInitials(name) : icon), undefined, { fontWeight: '500' }) }
139
151
  </GuiAvatar.Fallback>
140
152
  );
141
153
  },
@@ -162,18 +174,22 @@ interface AvatarGroupProps extends React.ComponentPropsWithoutRef<'div'> {
162
174
 
163
175
  export const AvatarGroup = React.forwardRef<HTMLDivElement, AvatarGroupProps>(
164
176
  function AvatarGroup(props, ref) {
165
- const { size, variant, borderless, className, ...rest } = props;
177
+ const { size, variant, borderless, children, ...rest } = props;
166
178
  const contextValue = React.useMemo(
167
179
  () => ({ size, variant, borderless }),
168
180
  [ size, variant, borderless ],
169
181
  );
170
182
  return (
171
183
  <AvatarGroupContext.Provider value={ contextValue }>
172
- <div
173
- ref={ ref }
174
- className={ cn('flex -space-x-3', className) }
175
- { ...rest }
176
- />
184
+ { /* `-space-x-3`'s sibling-margin trick, done by cloning: each avatar
185
+ after the first overlaps the one before it by 12px. */ }
186
+ <XStack ref={ ref as never } { ...(rest as object) }>
187
+ { React.Children.map(children, (child, index) => (
188
+ index === 0 || !React.isValidElement(child) ?
189
+ child :
190
+ React.cloneElement(child as React.ReactElement<{ marginLeft?: number }>, { marginLeft: -12 })
191
+ )) }
192
+ </XStack>
177
193
  </AvatarGroupContext.Provider>
178
194
  );
179
195
  },
package/src/badge.tsx CHANGED
@@ -1,7 +1,6 @@
1
+ import { Text, XStack } from '@hanzo/gui';
1
2
  import React from 'react';
2
3
 
3
- import { cn } from './utils';
4
-
5
4
  import { Skeleton } from './skeleton';
6
5
  import { Tooltip } from './tooltip';
7
6
 
@@ -21,37 +20,45 @@ type ColorPalette =
21
20
  'bright_yellow' | 'bright_teal' | 'bright_cyan' | 'bright_orange' |
22
21
  'bright_purple' | 'bright_pink';
23
22
 
24
- const BASE_CLASSES = 'inline-flex items-center rounded-sm gap-1 font-medium w-fit max-w-full whitespace-nowrap select-text';
25
-
26
- const SIZE_CLASSES = {
27
- sm: 'text-xs p-1 h-[18px] min-h-[18px]',
28
- md: 'text-sm px-1 py-0.5 min-h-6',
29
- lg: 'text-sm px-2 py-1 min-h-7 font-semibold',
30
- } as const;
31
-
32
- const COLOR_PALETTE_CLASSES: Record<ColorPalette, string> = {
33
- gray: 'bg-badge-gray-bg text-badge-gray-fg',
34
- green: 'bg-badge-green-bg text-badge-green-fg',
35
- red: 'bg-badge-red-bg text-badge-red-fg',
36
- purple: 'bg-badge-purple-bg text-badge-purple-fg',
37
- orange: 'bg-badge-orange-bg text-badge-orange-fg',
38
- blue: 'bg-badge-blue-bg text-badge-blue-fg',
39
- yellow: 'bg-badge-yellow-bg text-badge-yellow-fg',
40
- teal: 'bg-badge-teal-bg text-badge-teal-fg',
41
- cyan: 'bg-badge-cyan-bg text-badge-cyan-fg',
42
- pink: 'bg-badge-pink-bg text-badge-pink-fg',
43
- purple_alt: 'bg-badge-purple-alt-bg text-badge-purple-alt-fg',
44
- blue_alt: 'bg-badge-blue-alt-bg text-badge-blue-alt-fg',
45
- bright_gray: 'bg-badge-bright-gray-bg text-badge-bright-gray-fg',
46
- bright_green: 'bg-badge-bright-green-bg text-badge-bright-green-fg',
47
- bright_red: 'bg-badge-bright-red-bg text-badge-bright-red-fg',
48
- bright_blue: 'bg-badge-bright-blue-bg text-badge-bright-blue-fg',
49
- bright_yellow: 'bg-badge-bright-yellow-bg text-badge-bright-yellow-fg',
50
- bright_teal: 'bg-badge-bright-teal-bg text-badge-bright-teal-fg',
51
- bright_cyan: 'bg-badge-bright-cyan-bg text-badge-bright-cyan-fg',
52
- bright_orange: 'bg-badge-bright-orange-bg text-badge-bright-orange-fg',
53
- bright_purple: 'bg-badge-bright-purple-bg text-badge-bright-purple-fg',
54
- bright_pink: 'bg-badge-bright-pink-bg text-badge-bright-pink-fg',
23
+ type BadgeSize = 'sm' | 'md' | 'lg';
24
+
25
+ /** The chip itself — layout only. `color`/`fontSize` are Text props, not View props. */
26
+ const SIZE: Record<BadgeSize, Record<string, number>> = {
27
+ sm: { padding: 4, height: 18, minHeight: 18 },
28
+ md: { paddingHorizontal: 4, paddingVertical: 2, minHeight: 24 },
29
+ lg: { paddingHorizontal: 8, paddingVertical: 4, minHeight: 28 },
30
+ };
31
+
32
+ /** The label inside it — same three sizes, the typography half. */
33
+ const SIZE_TEXT: Record<BadgeSize, { fontSize: number; fontWeight: '500' | '600' }> = {
34
+ sm: { fontSize: 12, fontWeight: '500' },
35
+ md: { fontSize: 14, fontWeight: '500' },
36
+ lg: { fontSize: 14, fontWeight: '600' },
37
+ };
38
+
39
+ const COLOR_PALETTE: Record<ColorPalette, { bg: string; fg: string }> = {
40
+ gray: { bg: 'var(--color-badge-gray-bg)', fg: 'var(--color-badge-gray-fg)' },
41
+ green: { bg: 'var(--color-badge-green-bg)', fg: 'var(--color-badge-green-fg)' },
42
+ red: { bg: 'var(--color-badge-red-bg)', fg: 'var(--color-badge-red-fg)' },
43
+ purple: { bg: 'var(--color-badge-purple-bg)', fg: 'var(--color-badge-purple-fg)' },
44
+ orange: { bg: 'var(--color-badge-orange-bg)', fg: 'var(--color-badge-orange-fg)' },
45
+ blue: { bg: 'var(--color-badge-blue-bg)', fg: 'var(--color-badge-blue-fg)' },
46
+ yellow: { bg: 'var(--color-badge-yellow-bg)', fg: 'var(--color-badge-yellow-fg)' },
47
+ teal: { bg: 'var(--color-badge-teal-bg)', fg: 'var(--color-badge-teal-fg)' },
48
+ cyan: { bg: 'var(--color-badge-cyan-bg)', fg: 'var(--color-badge-cyan-fg)' },
49
+ pink: { bg: 'var(--color-badge-pink-bg)', fg: 'var(--color-badge-pink-fg)' },
50
+ purple_alt: { bg: 'var(--color-badge-purple-alt-bg)', fg: 'var(--color-badge-purple-alt-fg)' },
51
+ blue_alt: { bg: 'var(--color-badge-blue-alt-bg)', fg: 'var(--color-badge-blue-alt-fg)' },
52
+ bright_gray: { bg: 'var(--color-badge-bright-gray-bg)', fg: 'var(--color-badge-bright-gray-fg)' },
53
+ bright_green: { bg: 'var(--color-badge-bright-green-bg)', fg: 'var(--color-badge-bright-green-fg)' },
54
+ bright_red: { bg: 'var(--color-badge-bright-red-bg)', fg: 'var(--color-badge-bright-red-fg)' },
55
+ bright_blue: { bg: 'var(--color-badge-bright-blue-bg)', fg: 'var(--color-badge-bright-blue-fg)' },
56
+ bright_yellow: { bg: 'var(--color-badge-bright-yellow-bg)', fg: 'var(--color-badge-bright-yellow-fg)' },
57
+ bright_teal: { bg: 'var(--color-badge-bright-teal-bg)', fg: 'var(--color-badge-bright-teal-fg)' },
58
+ bright_cyan: { bg: 'var(--color-badge-bright-cyan-bg)', fg: 'var(--color-badge-bright-cyan-fg)' },
59
+ bright_orange: { bg: 'var(--color-badge-bright-orange-bg)', fg: 'var(--color-badge-bright-orange-fg)' },
60
+ bright_purple: { bg: 'var(--color-badge-bright-purple-bg)', fg: 'var(--color-badge-bright-purple-fg)' },
61
+ bright_pink: { bg: 'var(--color-badge-bright-pink-bg)', fg: 'var(--color-badge-bright-pink-fg)' },
55
62
  };
56
63
 
57
64
  export interface BadgeProps
@@ -106,8 +113,17 @@ export const Badge = React.forwardRef<HTMLDivElement, BadgeProps>(
106
113
  if (_mr !== undefined) shimStyle.marginRight = typeof _mr === 'number' ? `${ _mr * S }px` : _mr;
107
114
  const badgeStyle = Object.keys(shimStyle).length > 0 ? shimStyle : undefined;
108
115
 
116
+ const palette = COLOR_PALETTE[colorPalette];
117
+
109
118
  const child = children ? (
110
- <span className="overflow-hidden text-ellipsis">{ children }</span>
119
+ // `ellipsis` is gui Text's own built-in variant for single-line
120
+ // truncation — it expands to the same overflow/text-overflow/
121
+ // white-space/max-width rule Tailwind's `text-ellipsis` did, but
122
+ // through the component's own variants (guaranteed to compile),
123
+ // rather than naming each longhand as a separate style prop.
124
+ <Text ellipsis { ...SIZE_TEXT[size] } color={ palette.fg as never }>
125
+ { children }
126
+ </Text>
111
127
  ) : null;
112
128
 
113
129
  const childrenElement = truncated ? (
@@ -117,21 +133,26 @@ export const Badge = React.forwardRef<HTMLDivElement, BadgeProps>(
117
133
  ) : child;
118
134
 
119
135
  const badgeElement = (
120
- <span
121
- ref={ ref as React.Ref<HTMLSpanElement> }
122
- className={ cn(
123
- BASE_CLASSES,
124
- SIZE_CLASSES[size],
125
- COLOR_PALETTE_CLASSES[colorPalette],
126
- className,
127
- ) }
136
+ <XStack
137
+ ref={ ref as never }
138
+ render={ <span/> }
139
+ display="inline-flex"
140
+ alignItems="center"
141
+ alignSelf="flex-start"
142
+ borderRadius={ 2 }
143
+ gap={ 4 }
144
+ maxWidth="100%"
145
+ userSelect="text"
146
+ { ...SIZE[size] }
147
+ backgroundColor={ palette.bg as never }
148
+ className={ className }
128
149
  style={ badgeStyle }
129
- { ...rest }
150
+ { ...(rest as object) }
130
151
  >
131
152
  { startElement }
132
153
  { childrenElement }
133
154
  { endElement }
134
- </span>
155
+ </XStack>
135
156
  );
136
157
 
137
158
  if (loading) {
package/src/checkbox.tsx CHANGED
@@ -1,8 +1,6 @@
1
- import { Checkbox as GuiCheckbox } from '@hanzo/gui';
1
+ import { Checkbox as GuiCheckbox, Label, Text, View, VisuallyHidden } from '@hanzo/gui';
2
2
  import * as React from 'react';
3
3
 
4
- import { cn } from './utils';
5
-
6
4
  /** Tri-state, as the Chakra/Radix contract consumers already code against. */
7
5
  type CheckedState = boolean | 'indeterminate';
8
6
 
@@ -40,7 +38,6 @@ const CheckboxGroupBase = React.forwardRef<HTMLDivElement, CheckboxGroupProps>(
40
38
  onValueChange,
41
39
  orientation = 'vertical',
42
40
  name: _name,
43
- className,
44
41
  ...rest
45
42
  } = props;
46
43
 
@@ -74,18 +71,16 @@ const CheckboxGroupBase = React.forwardRef<HTMLDivElement, CheckboxGroupProps>(
74
71
 
75
72
  return (
76
73
  <CheckboxGroupContext.Provider value={ ctx }>
77
- <div
78
- ref={ ref }
74
+ <View
75
+ ref={ ref as never }
79
76
  role="group"
80
- className={ cn(
81
- 'flex',
82
- orientation === 'vertical' ? 'flex-col gap-3' : 'flex-row gap-8',
83
- className,
84
- ) }
85
- { ...rest }
77
+ display="flex"
78
+ flexDirection={ orientation === 'vertical' ? 'column' : 'row' }
79
+ gap={ orientation === 'vertical' ? 12 : 32 }
80
+ { ...(rest as object) }
86
81
  >
87
82
  { children }
88
- </div>
83
+ </View>
89
84
  </CheckboxGroupContext.Provider>
90
85
  );
91
86
  },
@@ -110,43 +105,28 @@ export interface CheckboxProps extends Omit<React.ComponentPropsWithoutRef<'labe
110
105
  required?: boolean;
111
106
  }
112
107
 
113
- const SIZE_CLASSES = {
114
- sm: {
115
- root: 'gap-1.5',
116
- control: 'h-3.5 w-3.5 rounded-sm',
117
- label: 'text-xs',
118
- icon: 'h-3 w-3',
119
- },
120
- md: {
121
- root: 'gap-2',
122
- control: 'h-4 w-4 rounded',
123
- label: 'text-sm',
124
- icon: 'h-3.5 w-3.5',
125
- },
108
+ const SIZE = {
109
+ sm: { rootGap: 6, control: 14, radius: 2, label: 12, icon: 12 },
110
+ md: { rootGap: 8, control: 16, radius: 4, label: 14, icon: 14 },
126
111
  } as const;
127
112
 
128
- const CheckIcon = ({ className }: { readonly className?: string }) => (
129
- <svg
130
- className={ className }
131
- viewBox="0 0 12 10"
132
- fill="none"
133
- xmlns="http://www.w3.org/2000/svg"
134
- >
113
+ const INK = 'var(--color-text-primary)' as never;
114
+ const ON_INK = 'var(--color-bg-body)' as never;
115
+ const RESTING_BORDER = 'var(--color-input-border)' as never;
116
+ const OUTLINE = 'var(--color-gray-500)' as never;
117
+
118
+ const CheckIcon = ({ size, color }: { readonly size: number; readonly color: string }) => (
119
+ <svg width={ size } height={ size } viewBox="0 0 12 10" fill="none" xmlns="http://www.w3.org/2000/svg">
135
120
  <path
136
121
  d="M10.59 0.59L4 7.17L1.41 4.59L0 6L4 10L12 2L10.59 0.59Z"
137
- fill="currentColor"
122
+ fill={ color }
138
123
  />
139
124
  </svg>
140
125
  );
141
126
 
142
- const IndeterminateIcon = ({ className }: { readonly className?: string }) => (
143
- <svg
144
- className={ className }
145
- viewBox="0 0 12 2"
146
- fill="none"
147
- xmlns="http://www.w3.org/2000/svg"
148
- >
149
- <path d="M0 0H12V2H0V0Z" fill="currentColor"/>
127
+ const IndeterminateIcon = ({ size, color }: { readonly size: number; readonly color: string }) => (
128
+ <svg width={ size } height={ size } viewBox="0 0 12 2" fill="none" xmlns="http://www.w3.org/2000/svg">
129
+ <path d="M0 0H12V2H0V0Z" fill={ color }/>
150
130
  </svg>
151
131
  );
152
132
 
@@ -167,7 +147,6 @@ const CheckboxBase = React.forwardRef<HTMLInputElement, CheckboxProps>(
167
147
  size = 'md',
168
148
  name,
169
149
  required,
170
- className,
171
150
  ...rest
172
151
  } = props;
173
152
 
@@ -237,25 +216,28 @@ const CheckboxBase = React.forwardRef<HTMLInputElement, CheckboxProps>(
237
216
  [ readOnly, isControlled, isInGroup, group, value, onCheckedChange, onChange ],
238
217
  );
239
218
 
240
- const sizeClasses = SIZE_CLASSES[size];
219
+ const s = SIZE[size];
220
+ const isChecked = checkedState !== false;
241
221
 
242
222
  return (
243
- <label
244
- ref={ rootRef }
245
- className={ cn(
246
- 'inline-flex items-center cursor-pointer select-none',
247
- sizeClasses.root,
248
- disabled && 'opacity-50 cursor-not-allowed',
249
- readOnly && 'cursor-default',
250
- className,
251
- ) }
252
- onChange={ onChange }
223
+ <Label
224
+ ref={ rootRef as never }
225
+ unstyled
226
+ flexDirection="row"
227
+ alignItems="center"
228
+ gap={ s.rootGap }
229
+ cursor={ disabled ? 'not-allowed' : readOnly ? 'default' : 'pointer' }
230
+ userSelect="none"
231
+ opacity={ disabled ? 0.5 : 1 }
232
+ onChange={ onChange as never }
253
233
  data-disabled={ disabled || undefined }
254
234
  data-readonly={ readOnly || undefined }
255
- { ...rest }
235
+ { ...(rest as object) }
256
236
  >
257
237
  { /* gui's Checkbox — role=checkbox + aria-checked from its headless core,
258
- a real Pressable on native. Paint stays in the Lux classes. */ }
238
+ a real Pressable on native. Checked state is already tracked above,
239
+ so the fill is a consequence of that state rather than a
240
+ `data-[state=checked]` selector. */ }
259
241
  <GuiCheckbox
260
242
  unstyled
261
243
  checked={ checkedState }
@@ -264,47 +246,46 @@ const CheckboxBase = React.forwardRef<HTMLInputElement, CheckboxProps>(
264
246
  name={ name }
265
247
  value={ value }
266
248
  required={ required }
267
- className={ cn(
268
- 'inline-flex items-center justify-center shrink-0',
269
- 'border-2 border-current/30',
270
- 'data-[state=checked]:bg-gray-800 data-[state=checked]:text-white',
271
- 'dark:data-[state=checked]:bg-white dark:data-[state=checked]:text-black',
272
- 'data-[state=indeterminate]:bg-gray-800 data-[state=indeterminate]:text-white',
273
- 'dark:data-[state=indeterminate]:bg-white dark:data-[state=indeterminate]:text-black',
274
- 'focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-500',
275
- 'transition-colors duration-150',
276
- sizeClasses.control,
277
- ) }
249
+ flexShrink={ 0 }
250
+ alignItems="center"
251
+ justifyContent="center"
252
+ width={ s.control }
253
+ height={ s.control }
254
+ borderRadius={ s.radius }
255
+ borderWidth={ 2 }
256
+ borderColor={ isChecked ? INK : RESTING_BORDER }
257
+ backgroundColor={ isChecked ? INK : 'transparent' }
258
+ transition="quicker"
259
+ focusStyle={{ outlineWidth: 2, outlineStyle: 'solid', outlineColor: OUTLINE, outlineOffset: 2 }}
278
260
  >
279
261
  { icon ?? (
280
- <GuiCheckbox.Indicator className={ cn('flex items-center justify-center', sizeClasses.icon) }>
281
- { checkedState === 'indeterminate' ? (
282
- <IndeterminateIcon className={ sizeClasses.icon }/>
283
- ) : (
284
- <CheckIcon className={ sizeClasses.icon }/>
285
- ) }
262
+ <GuiCheckbox.Indicator alignItems="center" justifyContent="center" width={ s.icon } height={ s.icon }>
263
+ { checkedState === 'indeterminate' ?
264
+ <IndeterminateIcon size={ s.icon } color={ ON_INK }/> :
265
+ <CheckIcon size={ s.icon } color={ ON_INK }/> }
286
266
  </GuiCheckbox.Indicator>
287
267
  ) }
288
268
  </GuiCheckbox>
289
269
  { /* Hidden native input for form compatibility and ref forwarding */ }
290
- <input
291
- ref={ setHiddenInputRef }
292
- type="checkbox"
293
- className="sr-only"
294
- checked={ checkedState === true }
295
- disabled={ disabled }
296
- readOnly={ readOnly }
297
- name={ name }
298
- value={ value }
299
- tabIndex={ -1 }
300
- aria-hidden
301
- onChange={ NOOP }
302
- { ...inputProps }
303
- />
270
+ <VisuallyHidden>
271
+ <input
272
+ ref={ setHiddenInputRef }
273
+ type="checkbox"
274
+ checked={ checkedState === true }
275
+ disabled={ disabled }
276
+ readOnly={ readOnly }
277
+ name={ name }
278
+ value={ value }
279
+ tabIndex={ -1 }
280
+ aria-hidden
281
+ onChange={ NOOP }
282
+ { ...inputProps }
283
+ />
284
+ </VisuallyHidden>
304
285
  { children != null && (
305
- <span className={ cn(sizeClasses.label) }>{ children }</span>
286
+ <Text fontSize={ s.label }>{ children }</Text>
306
287
  ) }
307
- </label>
288
+ </Label>
308
289
  );
309
290
  },
310
291
  );
@@ -36,12 +36,6 @@ const CloseButtonFrame = styled(View, {
36
36
  } as const,
37
37
  });
38
38
 
39
- const CLOSE_BUTTON_CLASSES = [
40
- 'text-[var(--closeButton-fg,currentColor)]',
41
- 'hover:text-[var(--hover-color,currentColor)]',
42
- 'disabled:opacity-40',
43
- ].join(' ');
44
-
45
39
  export interface CloseButtonProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'size'> {
46
40
  readonly variant?: 'plain';
47
41
  readonly size?: 'md';
@@ -56,12 +50,13 @@ export const CloseButton = React.forwardRef<
56
50
  return (
57
51
  <CloseButtonFrame
58
52
  ref={ref as any}
59
- className={[CLOSE_BUTTON_CLASSES, className].filter(Boolean).join(' ')}
53
+ className={className}
60
54
  {...(rest as any)}
61
55
  >
62
56
  {children ?? (
63
57
  <svg
64
- className="size-5"
58
+ width={20}
59
+ height={20}
65
60
  viewBox="0 0 20 20"
66
61
  fill="none"
67
62
  aria-hidden="true"
@@ -1,9 +1,8 @@
1
+ import { Text, XStack, YStack } from '@hanzo/gui';
1
2
  import * as React from 'react';
2
3
 
3
- import { cn } from './utils';
4
-
5
4
  // Inline constants
6
- const apos = '\u2019'; // right single quotation mark (typographic apostrophe)
5
+ const apos = ''; // right single quotation mark (typographic apostrophe)
7
6
 
8
7
  function upperFirst(str: string): string {
9
8
  return str.charAt(0).toUpperCase() + str.slice(1);
@@ -23,6 +22,8 @@ export interface EmptyStateProps extends React.HTMLAttributes<HTMLDivElement> {
23
22
  icon?: React.ReactNode;
24
23
  }
25
24
 
25
+ const MUTED = 'var(--color-text-secondary)' as never;
26
+
26
27
  export const EmptyState = React.forwardRef<HTMLDivElement, EmptyStateProps>(
27
28
  function EmptyState(props, ref) {
28
29
  const { title, description, term, type = 'query', icon, children, className, ...rest } = props;
@@ -70,28 +71,31 @@ export const EmptyState = React.forwardRef<HTMLDivElement, EmptyStateProps>(
70
71
  })();
71
72
 
72
73
  return (
73
- <div
74
- ref={ ref }
75
- className={ cn('flex items-center justify-center py-10', className) }
76
- { ...rest }
74
+ <XStack
75
+ ref={ ref as never }
76
+ alignItems="center"
77
+ justifyContent="center"
78
+ paddingVertical={ 40 }
79
+ className={ className }
80
+ { ...(rest as object) }
77
81
  >
78
- <div className="flex flex-col items-center gap-5">
82
+ <YStack alignItems="center" gap={ 20 }>
79
83
  { iconContent && (
80
- <div className="flex items-center justify-center">{ iconContent }</div>
84
+ <XStack alignItems="center" justifyContent="center">{ iconContent }</XStack>
81
85
  ) }
82
86
  { descriptionContent ? (
83
- <div className="flex flex-col items-center gap-2 text-center">
84
- <span className="text-lg font-semibold text-text-secondary">{ titleContent }</span>
85
- <span className="text-sm text-text-secondary">
87
+ <YStack alignItems="center" gap={ 8 }>
88
+ <Text fontSize={ 18 } fontWeight="600" textAlign="center" color={ MUTED }>{ titleContent }</Text>
89
+ <Text fontSize={ 14 } textAlign="center" color={ MUTED }>
86
90
  { descriptionContent }
87
- </span>
88
- </div>
91
+ </Text>
92
+ </YStack>
89
93
  ) : (
90
- <span className="text-lg font-semibold text-text-secondary">{ titleContent }</span>
94
+ <Text fontSize={ 18 } fontWeight="600" textAlign="center" color={ MUTED }>{ titleContent }</Text>
91
95
  ) }
92
96
  { children }
93
- </div>
94
- </div>
97
+ </YStack>
98
+ </XStack>
95
99
  );
96
100
  },
97
101
  );