@luxfi/ui 7.4.10 → 7.4.12
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.
- package/dist/accordion.cjs +107 -166
- package/dist/accordion.d.cts +18 -51
- package/dist/accordion.d.ts +18 -51
- package/dist/accordion.js +109 -166
- package/dist/alert.cjs +68 -46
- package/dist/alert.js +69 -46
- package/dist/avatar.cjs +49 -46
- package/dist/avatar.js +52 -48
- package/dist/badge.cjs +82 -49
- package/dist/badge.js +83 -50
- package/dist/checkbox.cjs +42 -75
- package/dist/checkbox.js +43 -76
- package/dist/chrome.cjs +196 -386
- package/dist/chrome.js +198 -386
- package/dist/close-button.cjs +3 -7
- package/dist/close-button.js +3 -7
- package/dist/collapsible.cjs +35 -26
- package/dist/collapsible.d.cts +2 -1
- package/dist/collapsible.d.ts +2 -1
- package/dist/collapsible.js +34 -24
- package/dist/dialog.cjs +3 -7
- package/dist/dialog.d.cts +1 -1
- package/dist/dialog.d.ts +1 -1
- package/dist/dialog.js +3 -7
- package/dist/drawer.cjs +95 -122
- package/dist/drawer.d.cts +43 -10
- package/dist/drawer.d.ts +43 -10
- package/dist/drawer.js +97 -122
- package/dist/empty-state.cjs +13 -13
- package/dist/empty-state.js +13 -13
- package/dist/expiration-selector.cjs +58 -63
- package/dist/expiration-selector.js +58 -63
- package/dist/field.cjs +60 -109
- package/dist/field.d.cts +0 -1
- package/dist/field.d.ts +0 -1
- package/dist/field.js +61 -109
- package/dist/greeks-display.cjs +48 -39
- package/dist/greeks-display.js +48 -39
- package/dist/heading.cjs +10 -17
- package/dist/heading.d.cts +0 -2
- package/dist/heading.d.ts +0 -2
- package/dist/heading.js +10 -17
- package/dist/icon-button.cjs +82 -82
- package/dist/icon-button.d.cts +16 -12
- package/dist/icon-button.d.ts +16 -12
- package/dist/icon-button.js +82 -82
- package/dist/index.cjs +1812 -2105
- package/dist/index.d.cts +1 -5
- package/dist/index.d.ts +1 -5
- package/dist/index.js +1814 -2100
- package/dist/input-group.cjs +11 -12
- package/dist/input-group.js +11 -12
- package/dist/input.cjs +27 -27
- package/dist/input.d.cts +21 -1
- package/dist/input.d.ts +21 -1
- package/dist/input.js +25 -28
- package/dist/menu.cjs +160 -271
- package/dist/menu.d.cts +41 -57
- package/dist/menu.d.ts +41 -57
- package/dist/menu.js +161 -251
- package/dist/option-chain.cjs +89 -67
- package/dist/option-chain.js +89 -67
- package/dist/option-position.cjs +88 -85
- package/dist/option-position.js +88 -85
- package/dist/pnl-diagram.cjs +20 -20
- package/dist/pnl-diagram.js +20 -20
- package/dist/popover.cjs +4 -8
- package/dist/popover.js +4 -8
- package/dist/progress.cjs +15 -22
- package/dist/progress.d.cts +6 -6
- package/dist/progress.d.ts +6 -6
- package/dist/progress.js +15 -22
- package/dist/radio.cjs +35 -67
- package/dist/radio.d.cts +18 -18
- package/dist/radio.d.ts +18 -18
- package/dist/radio.js +36 -68
- package/dist/select.cjs +40 -39
- package/dist/select.d.cts +35 -4
- package/dist/select.d.ts +35 -4
- package/dist/select.js +40 -38
- package/dist/separator.cjs +8 -33
- package/dist/separator.js +8 -33
- package/dist/slider.cjs +33 -49
- package/dist/slider.js +34 -50
- package/dist/switch.cjs +48 -55
- package/dist/switch.js +49 -56
- package/dist/tabs.cjs +108 -197
- package/dist/tabs.d.cts +16 -60
- package/dist/tabs.d.ts +16 -60
- package/dist/tabs.js +109 -196
- package/dist/tag.cjs +112 -69
- package/dist/tag.js +113 -69
- package/dist/textarea.cjs +56 -27
- package/dist/textarea.js +57 -28
- package/dist/toaster.cjs +97 -79
- package/dist/toaster.d.cts +7 -8
- package/dist/toaster.d.ts +7 -8
- package/dist/toaster.js +78 -80
- package/dist/tooltip.cjs +28 -17
- package/dist/tooltip.js +30 -18
- package/package.json +2 -16
- package/src/accordion.tsx +173 -227
- package/src/alert.tsx +78 -45
- package/src/avatar.tsx +54 -37
- package/src/badge.tsx +65 -44
- package/src/checkbox.tsx +70 -89
- package/src/close-button.tsx +3 -8
- package/src/collapsible.tsx +55 -52
- package/src/drawer.tsx +123 -115
- package/src/empty-state.tsx +21 -17
- package/src/expiration-selector.tsx +84 -66
- package/src/field.tsx +68 -124
- package/src/greeks-display.tsx +59 -51
- package/src/heading.tsx +22 -26
- package/src/icon-button.tsx +134 -141
- package/src/ink.tsx +37 -0
- package/src/input-group.tsx +59 -36
- package/src/input.tsx +41 -32
- package/src/menu.tsx +224 -397
- package/src/option-chain.tsx +154 -104
- package/src/option-position.tsx +136 -114
- package/src/pin-input.tsx +37 -29
- package/src/pnl-diagram.tsx +36 -28
- package/src/popover.tsx +1 -1
- package/src/progress.tsx +17 -21
- package/src/radio.tsx +56 -75
- package/src/select.tsx +80 -59
- package/src/separator.tsx +13 -34
- package/src/slider.tsx +43 -44
- package/src/strategy-builder.tsx +245 -156
- package/src/switch.tsx +63 -64
- package/src/tabs.tsx +166 -296
- package/src/tag.tsx +89 -51
- package/src/textarea.tsx +19 -29
- package/src/toaster.tsx +118 -86
- package/src/tooltip.tsx +21 -13
- package/tokens.css +79 -0
package/src/accordion.tsx
CHANGED
|
@@ -1,285 +1,231 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { Accordion, View, XStack } from '@hanzo/gui';
|
|
2
2
|
import * as React from 'react';
|
|
3
3
|
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
//
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
import { ink } from './ink';
|
|
5
|
+
|
|
6
|
+
// The Lux accordion, on gui's Accordion.
|
|
7
|
+
//
|
|
8
|
+
// Everything behavioural belongs to the primitive and none of it is
|
|
9
|
+
// re-implemented here: roving Arrow/Home/End focus on the root, `aria-expanded`
|
|
10
|
+
// + `aria-controls` on the trigger, `role="region"` + `aria-labelledby` on the
|
|
11
|
+
// content. This file names the four parts and dresses them.
|
|
12
|
+
//
|
|
13
|
+
// Three corrections the primitive needs, each of which is a real bug if skipped:
|
|
14
|
+
//
|
|
15
|
+
// 1. gui's `Accordion.Header` renders an `<h1>` at the display type scale. An
|
|
16
|
+
// accordion is a list of sections inside a page that already has a title, so
|
|
17
|
+
// unqualified every row publishes a competing document heading and a screen
|
|
18
|
+
// reader's heading list comes out as N level-1s. `headingLevel` re-hosts it
|
|
19
|
+
// and sets the matching `aria-level`, which the inherited `role="heading"`
|
|
20
|
+
// requires to be valid.
|
|
21
|
+
// 2. `unstyled` means gui applies nothing — which on the web leaves the USER
|
|
22
|
+
// AGENT's chrome, and a bare `<button>` is #efefef with an outset border.
|
|
23
|
+
// Unstyled has to mean "no chrome", so the reset is explicit.
|
|
24
|
+
// 3. That trigger is a bare `<button>`, and a bare button inside a form
|
|
25
|
+
// SUBMITS it. `type="button"` or opening a section posts the form.
|
|
26
|
+
//
|
|
27
|
+
// The indicator's open state comes from the trigger's function child — gui
|
|
28
|
+
// exports no per-item context, and that is the only seam. It replaces a
|
|
29
|
+
// `group-data-[state=open]/trigger:` Tailwind chain that could not have worked
|
|
30
|
+
// here anyway: gui compiles style props to atomic classes and has no
|
|
31
|
+
// parent-state selector.
|
|
32
|
+
|
|
33
|
+
const IndicatorIcon = () => (
|
|
34
|
+
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
9
35
|
<path d="M7.5 15L12.5 10L7.5 5" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
|
10
36
|
</svg>
|
|
11
37
|
);
|
|
12
38
|
|
|
39
|
+
const BAR = 'var(--color-text-primary)' as never;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The open/closed mark. The FAQ variant is a plus that becomes a minus: two
|
|
43
|
+
* bars, and the upright one turns a quarter turn onto the flat one.
|
|
44
|
+
*/
|
|
45
|
+
const Indicator = ({ open, variant }: { open: boolean; variant?: 'outline' | 'faq' }) => (
|
|
46
|
+
variant === 'faq' ? (
|
|
47
|
+
<XStack width={ 12 } height={ 12 } flexShrink={ 0 } alignItems="center" justifyContent="center" position="relative">
|
|
48
|
+
<View position="absolute" width="100%" height={ 2 } borderRadius={ 2 } backgroundColor={ BAR }/>
|
|
49
|
+
<View
|
|
50
|
+
position="absolute"
|
|
51
|
+
width={ 2 }
|
|
52
|
+
height="100%"
|
|
53
|
+
borderRadius={ 2 }
|
|
54
|
+
backgroundColor={ BAR }
|
|
55
|
+
rotate={ open ? '90deg' : '0deg' }
|
|
56
|
+
/>
|
|
57
|
+
</XStack>
|
|
58
|
+
) : (
|
|
59
|
+
<XStack flexShrink={ 0 } alignItems="center" rotate={ open ? '270deg' : '180deg' }>
|
|
60
|
+
<IndicatorIcon/>
|
|
61
|
+
</XStack>
|
|
62
|
+
)
|
|
63
|
+
);
|
|
64
|
+
|
|
13
65
|
// ---------------------------------------------------------------------------
|
|
14
|
-
//
|
|
66
|
+
// Root
|
|
15
67
|
// ---------------------------------------------------------------------------
|
|
16
68
|
|
|
17
|
-
interface AccordionRootProps
|
|
69
|
+
export interface AccordionRootProps
|
|
70
|
+
extends Omit<React.ComponentProps<typeof Accordion>, 'type' | 'value' | 'defaultValue' | 'onValueChange' | 'size'> {
|
|
18
71
|
|
|
19
|
-
/**
|
|
20
|
-
readonly multiple?: boolean;
|
|
21
|
-
|
|
22
|
-
/** Controlled open items (array of `value` strings). */
|
|
72
|
+
/** Controlled open items. */
|
|
23
73
|
readonly value?: Array<string>;
|
|
24
74
|
|
|
25
75
|
/** Uncontrolled initial open items. */
|
|
26
76
|
readonly defaultValue?: Array<string>;
|
|
27
77
|
|
|
28
|
-
/**
|
|
29
|
-
* Called when open items change.
|
|
30
|
-
* Wraps the value in `{ value }` to stay compatible with the Chakra callback shape
|
|
31
|
-
* that consumers already rely on.
|
|
32
|
-
*/
|
|
78
|
+
/** Fires with the full set of open items. */
|
|
33
79
|
readonly onValueChange?: (details: { value: Array<string> }) => void;
|
|
80
|
+
|
|
34
81
|
readonly variant?: 'outline' | 'faq';
|
|
35
82
|
readonly size?: 'sm' | 'md';
|
|
36
|
-
|
|
37
|
-
/** Accepted for compatibility; not applied visually. */
|
|
38
|
-
readonly noAnimation?: boolean;
|
|
39
|
-
|
|
40
|
-
/** Accepted for compatibility; Radix defers rendering internally. */
|
|
41
|
-
readonly lazyMount?: boolean;
|
|
42
|
-
readonly children?: React.ReactNode;
|
|
43
|
-
// Legacy Chakra style-prop shims
|
|
44
|
-
readonly position?: string;
|
|
45
|
-
readonly w?: string;
|
|
46
|
-
readonly bgColor?: string | Record<string, string>;
|
|
47
|
-
readonly borderRadius?: string;
|
|
48
83
|
}
|
|
49
84
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
[ onValueChange ],
|
|
77
|
-
);
|
|
78
|
-
|
|
79
|
-
return (
|
|
80
|
-
<AccordionPrimitive.Root
|
|
81
|
-
ref={ ref }
|
|
82
|
-
type="multiple"
|
|
83
|
-
value={ value }
|
|
84
|
-
defaultValue={ defaultValue }
|
|
85
|
-
onValueChange={ handleValueChange }
|
|
86
|
-
className={ cn(
|
|
87
|
-
'w-full',
|
|
88
|
-
variant === 'faq' && 'accordion-faq',
|
|
89
|
-
size === 'sm' && 'accordion-sm',
|
|
90
|
-
className,
|
|
91
|
-
) }
|
|
92
|
-
data-variant={ variant }
|
|
93
|
-
data-size={ size }
|
|
94
|
-
{ ...rest }
|
|
95
|
-
>
|
|
96
|
-
{ children }
|
|
97
|
-
</AccordionPrimitive.Root>
|
|
98
|
-
);
|
|
99
|
-
},
|
|
85
|
+
const Variant = React.createContext<'outline' | 'faq' | undefined>(undefined);
|
|
86
|
+
|
|
87
|
+
export const AccordionRoot = ({
|
|
88
|
+
value,
|
|
89
|
+
defaultValue,
|
|
90
|
+
onValueChange,
|
|
91
|
+
variant,
|
|
92
|
+
size,
|
|
93
|
+
children,
|
|
94
|
+
...rest
|
|
95
|
+
}: AccordionRootProps): React.ReactElement => (
|
|
96
|
+
<Variant.Provider value={ variant }>
|
|
97
|
+
<Accordion
|
|
98
|
+
unstyled
|
|
99
|
+
type="multiple"
|
|
100
|
+
width="100%"
|
|
101
|
+
value={ value }
|
|
102
|
+
defaultValue={ defaultValue }
|
|
103
|
+
onValueChange={ onValueChange ? (next: Array<string>) => onValueChange({ value: next }) : undefined }
|
|
104
|
+
data-variant={ variant }
|
|
105
|
+
data-size={ size }
|
|
106
|
+
{ ...rest }
|
|
107
|
+
>
|
|
108
|
+
{ children }
|
|
109
|
+
</Accordion>
|
|
110
|
+
</Variant.Provider>
|
|
100
111
|
);
|
|
101
112
|
|
|
102
113
|
// ---------------------------------------------------------------------------
|
|
103
|
-
//
|
|
114
|
+
// Item
|
|
104
115
|
// ---------------------------------------------------------------------------
|
|
105
116
|
|
|
106
|
-
|
|
107
|
-
// Legacy Chakra style-prop shims
|
|
108
|
-
as?: string;
|
|
109
|
-
_first?: Record<string, unknown>;
|
|
110
|
-
_last?: Record<string, unknown>;
|
|
111
|
-
display?: string;
|
|
112
|
-
}
|
|
117
|
+
export type AccordionItemProps = React.ComponentProps<typeof Accordion.Item>;
|
|
113
118
|
|
|
114
|
-
export const AccordionItem = React.
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
'border-b border-[var(--color-border-divider)]',
|
|
122
|
-
className,
|
|
123
|
-
) }
|
|
124
|
-
{ ...rest }
|
|
125
|
-
/>
|
|
126
|
-
);
|
|
127
|
-
},
|
|
119
|
+
export const AccordionItem = (props: AccordionItemProps): React.ReactElement => (
|
|
120
|
+
<Accordion.Item
|
|
121
|
+
unstyled
|
|
122
|
+
borderBottomWidth={ 1 }
|
|
123
|
+
borderColor={ 'var(--color-border-divider)' as never }
|
|
124
|
+
{ ...props }
|
|
125
|
+
/>
|
|
128
126
|
);
|
|
129
127
|
|
|
130
128
|
// ---------------------------------------------------------------------------
|
|
131
|
-
//
|
|
129
|
+
// Trigger
|
|
132
130
|
// ---------------------------------------------------------------------------
|
|
133
131
|
|
|
134
|
-
|
|
132
|
+
export type AccordionItemTriggerProps = React.ComponentProps<typeof Accordion.Trigger> & {
|
|
135
133
|
readonly indicatorPlacement?: 'start' | 'end';
|
|
136
134
|
readonly noIndicator?: boolean;
|
|
137
135
|
readonly variant?: 'outline' | 'faq';
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
const {
|
|
155
|
-
children, indicatorPlacement: indicatorPlacementProp, variant, noIndicator, className,
|
|
156
|
-
// Strip Chakra style props
|
|
157
|
-
px: _px, py: _py, _hover, wordBreak: _wordBreak, textAlign: _textAlign,
|
|
158
|
-
cursor: _cursor, display: _display, alignItems: _alignItems, columnGap: _columnGap,
|
|
159
|
-
...rest
|
|
160
|
-
} = props;
|
|
161
|
-
|
|
162
|
-
const indicatorPlacement = variant === 'faq' ? 'start' : (indicatorPlacementProp ?? 'end');
|
|
163
|
-
|
|
164
|
-
const indicator = variant === 'faq' ? (
|
|
165
|
-
<span
|
|
166
|
-
className={ cn(
|
|
167
|
-
'relative inline-block h-3 w-3 shrink-0',
|
|
168
|
-
// horizontal bar (always visible)
|
|
169
|
-
'before:absolute before:left-0 before:top-1/2 before:block before:h-[2px] before:w-full before:-translate-y-1/2 before:rounded-[2px] before:bg-current',
|
|
170
|
-
// vertical bar (rotates to 0 when open)
|
|
171
|
-
'after:absolute after:left-1/2 after:top-0 after:block after:h-full after:w-[2px]',
|
|
172
|
-
'after:-translate-x-1/2 after:rounded-[2px] after:bg-current',
|
|
173
|
-
'after:transition-transform after:duration-200 after:ease-in-out',
|
|
174
|
-
// When parent trigger has data-state="open", rotate vertical bar
|
|
175
|
-
'group-data-[state=open]/trigger:after:rotate-90',
|
|
176
|
-
) }
|
|
177
|
-
/>
|
|
178
|
-
) : (
|
|
179
|
-
<span
|
|
180
|
-
className={ cn(
|
|
181
|
-
'inline-flex shrink-0 transition-transform duration-200',
|
|
182
|
-
'rotate-180',
|
|
183
|
-
'group-data-[state=open]/trigger:rotate-[270deg]',
|
|
184
|
-
) }
|
|
185
|
-
>
|
|
186
|
-
<IndicatorIcon className="h-5 w-5"/>
|
|
187
|
-
</span>
|
|
188
|
-
);
|
|
136
|
+
readonly headingLevel?: 1 | 2 | 3 | 4 | 5 | 6;
|
|
137
|
+
|
|
138
|
+
/** The DOM button type — see (3) above. Defaults to `button`, not submit. */
|
|
139
|
+
readonly type?: 'button' | 'submit' | 'reset';
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
export const AccordionItemTrigger = ({
|
|
143
|
+
children,
|
|
144
|
+
indicatorPlacement: placementProp,
|
|
145
|
+
variant: variantProp,
|
|
146
|
+
noIndicator,
|
|
147
|
+
headingLevel = 3,
|
|
148
|
+
...rest
|
|
149
|
+
}: AccordionItemTriggerProps): React.ReactElement => {
|
|
150
|
+
const variant = variantProp ?? React.useContext(Variant);
|
|
151
|
+
const placement = variant === 'faq' ? 'start' : (placementProp ?? 'end');
|
|
189
152
|
|
|
190
153
|
return (
|
|
191
|
-
<
|
|
192
|
-
<
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
154
|
+
<Accordion.Header unstyled render={ `h${ headingLevel }` } aria-level={ headingLevel } display="flex" width="100%">
|
|
155
|
+
<Accordion.Trigger
|
|
156
|
+
unstyled
|
|
157
|
+
type="button"
|
|
158
|
+
width="100%"
|
|
159
|
+
flexDirection="row"
|
|
160
|
+
alignItems="center"
|
|
161
|
+
gap={ 8 }
|
|
162
|
+
paddingVertical={ 12 }
|
|
163
|
+
paddingHorizontal={ 0 }
|
|
164
|
+
borderWidth={ 0 }
|
|
165
|
+
backgroundColor="transparent"
|
|
166
|
+
cursor="pointer"
|
|
167
|
+
// Ink colour and its hover live in one CSS rule rather than on this
|
|
168
|
+
// frame: `color` is a Text prop, not a Stack prop, and "the label
|
|
169
|
+
// brightens while the ROW is hovered" is a pseudo-class on an ancestor
|
|
170
|
+
// — a selector, which no prop expresses. The children inherit it.
|
|
171
|
+
className="lux-accordion-trigger"
|
|
172
|
+
{ ...rest }
|
|
173
|
+
>
|
|
174
|
+
{ (({ open }: { open: boolean }) => (
|
|
175
|
+
<>
|
|
176
|
+
{ placement === 'start' && !noIndicator && <Indicator open={ open } variant={ variant }/> }
|
|
177
|
+
{ ink(children, undefined, { color: 'inherit', textAlign: 'left' }) }
|
|
178
|
+
{ placement === 'end' && !noIndicator && <Indicator open={ open } variant={ variant }/> }
|
|
179
|
+
</>
|
|
180
|
+
)) as unknown as React.ReactNode }
|
|
181
|
+
</Accordion.Trigger>
|
|
182
|
+
</Accordion.Header>
|
|
209
183
|
);
|
|
210
|
-
}
|
|
184
|
+
};
|
|
211
185
|
|
|
212
186
|
// ---------------------------------------------------------------------------
|
|
213
|
-
//
|
|
187
|
+
// Content
|
|
214
188
|
// ---------------------------------------------------------------------------
|
|
215
189
|
|
|
216
|
-
export
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
AccordionItemContentProps
|
|
230
|
-
>(function AccordionItemContent(props, ref) {
|
|
231
|
-
const { className, children, pb: _pb, pr: _pr, pl: _pl, w: _w, display: _display, flexDir: _flexDir, rowGap: _rowGap, ...rest } = props;
|
|
232
|
-
return (
|
|
233
|
-
<AccordionPrimitive.Content
|
|
234
|
-
ref={ ref }
|
|
235
|
-
className={ cn(
|
|
236
|
-
'overflow-hidden text-[var(--color-text-primary)]',
|
|
237
|
-
'data-[state=open]:animate-accordion-down',
|
|
238
|
-
'data-[state=closed]:animate-accordion-up',
|
|
239
|
-
className,
|
|
240
|
-
) }
|
|
241
|
-
{ ...rest }
|
|
242
|
-
>
|
|
243
|
-
<div className="pb-3">
|
|
244
|
-
{ children }
|
|
245
|
-
</div>
|
|
246
|
-
</AccordionPrimitive.Content>
|
|
247
|
-
);
|
|
248
|
-
});
|
|
190
|
+
export type AccordionItemContentProps = React.ComponentProps<typeof Accordion.Content>;
|
|
191
|
+
|
|
192
|
+
export const AccordionItemContent = ({ children, ...rest }: AccordionItemContentProps): React.ReactElement => (
|
|
193
|
+
<Accordion.Content
|
|
194
|
+
unstyled
|
|
195
|
+
overflow="hidden"
|
|
196
|
+
paddingBottom={ 12 }
|
|
197
|
+
backgroundColor="transparent"
|
|
198
|
+
{ ...rest }
|
|
199
|
+
>
|
|
200
|
+
{ ink(children, undefined, { color: 'var(--color-text-primary)' }) }
|
|
201
|
+
</Accordion.Content>
|
|
202
|
+
);
|
|
249
203
|
|
|
250
204
|
// ---------------------------------------------------------------------------
|
|
251
205
|
// useAccordion
|
|
252
206
|
// ---------------------------------------------------------------------------
|
|
253
207
|
|
|
208
|
+
/** Open state for an accordion whose items are addressable by URL fragment. */
|
|
254
209
|
export function useAccordion(items: Array<{ id: string }>) {
|
|
255
210
|
const [ value, setValue ] = React.useState<Array<string>>([]);
|
|
256
211
|
|
|
257
|
-
const onValueChange = React.useCallback(({ value }: { value: Array<string> }) => {
|
|
258
|
-
setValue(
|
|
212
|
+
const onValueChange = React.useCallback(({ value: next }: { value: Array<string> }) => {
|
|
213
|
+
setValue(next);
|
|
259
214
|
}, []);
|
|
260
215
|
|
|
261
216
|
const scrollToItemFromUrl = React.useCallback(() => {
|
|
262
217
|
const hash = window.location.hash.replace('#', '');
|
|
263
|
-
|
|
264
|
-
if (!hash) {
|
|
265
|
-
return;
|
|
266
|
-
}
|
|
218
|
+
if (!hash) return;
|
|
267
219
|
|
|
268
220
|
const itemToScroll = items.find((item) => item.id === hash);
|
|
269
|
-
if (itemToScroll)
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
}
|
|
274
|
-
setValue([ itemToScroll.id ]);
|
|
275
|
-
}
|
|
221
|
+
if (!itemToScroll) return;
|
|
222
|
+
|
|
223
|
+
document.getElementById(itemToScroll.id)?.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
224
|
+
setValue([ itemToScroll.id ]);
|
|
276
225
|
}, [ items ]);
|
|
277
226
|
|
|
278
|
-
return React.useMemo(
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
scrollToItemFromUrl,
|
|
283
|
-
};
|
|
284
|
-
}, [ value, onValueChange, scrollToItemFromUrl ]);
|
|
227
|
+
return React.useMemo(
|
|
228
|
+
() => ({ value, onValueChange, scrollToItemFromUrl }),
|
|
229
|
+
[ value, onValueChange, scrollToItemFromUrl ],
|
|
230
|
+
);
|
|
285
231
|
}
|
package/src/alert.tsx
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
+
import { Text, View, XStack, YStack } from '@hanzo/gui';
|
|
1
2
|
import * as React from 'react';
|
|
2
3
|
|
|
3
|
-
import { cn } from './utils';
|
|
4
|
-
|
|
5
4
|
// Inline info icon (filled circle with i)
|
|
6
|
-
const IndicatorIcon = (
|
|
7
|
-
<svg
|
|
5
|
+
const IndicatorIcon = () => (
|
|
6
|
+
<svg width={ 20 } height={ 20 } viewBox="0 0 20 20" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
|
8
7
|
<path fillRule="evenodd" clipRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm.75-11.25a.75.75 0 11-1.5 0 .75.75 0 011.5 0zM9.25 10a.75.75 0 011.5 0v3a.75.75 0 01-1.5 0v-3z"/>
|
|
9
8
|
</svg>
|
|
10
9
|
);
|
|
11
10
|
|
|
12
11
|
import { CloseButton } from './close-button';
|
|
12
|
+
import { ink } from './ink';
|
|
13
13
|
import { Skeleton } from './skeleton';
|
|
14
14
|
|
|
15
15
|
/* ------------------------------------------------------------------ */
|
|
@@ -19,27 +19,38 @@ import { Skeleton } from './skeleton';
|
|
|
19
19
|
type AlertStatus = 'info' | 'warning' | 'warning_table' | 'success' | 'error';
|
|
20
20
|
|
|
21
21
|
/* ------------------------------------------------------------------ */
|
|
22
|
-
/*
|
|
22
|
+
/* Style maps (replaced CVA) */
|
|
23
23
|
/* ------------------------------------------------------------------ */
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
25
|
+
/** The frame every alert shares, regardless of status or size. */
|
|
26
|
+
const ALERT_BASE = {
|
|
27
|
+
width: '100%',
|
|
28
|
+
position: 'relative',
|
|
29
|
+
alignItems: 'flex-start',
|
|
30
|
+
borderRadius: 4,
|
|
31
|
+
gap: 8,
|
|
32
|
+
paddingVertical: 8,
|
|
33
|
+
} as const;
|
|
34
|
+
|
|
35
|
+
/** Foreground is constant across every status — only the background moves. */
|
|
36
|
+
const ALERT_FG = 'var(--color-alert-fg)' as never;
|
|
37
|
+
|
|
38
|
+
const STATUS_BG: Record<AlertStatus, string> = {
|
|
39
|
+
info: 'var(--color-alert-bg-info)',
|
|
40
|
+
warning: 'var(--color-alert-bg-warning)',
|
|
41
|
+
warning_table: 'var(--color-alert-bg-warning-table)',
|
|
42
|
+
success: 'var(--color-alert-bg-success)',
|
|
43
|
+
error: 'var(--color-alert-bg-error)',
|
|
33
44
|
};
|
|
34
45
|
|
|
35
|
-
const
|
|
36
|
-
sm:
|
|
37
|
-
md:
|
|
46
|
+
const SIZE: Record<'sm' | 'md', { paddingHorizontal: number; fontSize: number }> = {
|
|
47
|
+
sm: { paddingHorizontal: 8, fontSize: 12 },
|
|
48
|
+
md: { paddingHorizontal: 12, fontSize: 16 },
|
|
38
49
|
};
|
|
39
50
|
|
|
40
|
-
const
|
|
41
|
-
sm:
|
|
42
|
-
md:
|
|
51
|
+
const INDICATOR_SIZE: Record<'sm' | 'md', { marginVertical: number }> = {
|
|
52
|
+
sm: { marginVertical: 0 },
|
|
53
|
+
md: { marginVertical: 2 },
|
|
43
54
|
};
|
|
44
55
|
|
|
45
56
|
/* ------------------------------------------------------------------ */
|
|
@@ -107,10 +118,9 @@ export const Alert = React.forwardRef<HTMLDivElement, AlertProps>(
|
|
|
107
118
|
const [ isOpen, setIsOpen ] = React.useState(true);
|
|
108
119
|
|
|
109
120
|
const resolvedSize = size ?? 'md';
|
|
121
|
+
const fontSize = SIZE[resolvedSize].fontSize;
|
|
110
122
|
|
|
111
|
-
const defaultIcon =
|
|
112
|
-
<IndicatorIcon className="w-5 h-5"/>
|
|
113
|
-
);
|
|
123
|
+
const defaultIcon = <IndicatorIcon/>;
|
|
114
124
|
|
|
115
125
|
const iconElement = (() => {
|
|
116
126
|
if (startElement !== undefined) {
|
|
@@ -122,12 +132,23 @@ export const Alert = React.forwardRef<HTMLDivElement, AlertProps>(
|
|
|
122
132
|
}
|
|
123
133
|
|
|
124
134
|
return (
|
|
125
|
-
<
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
135
|
+
<XStack
|
|
136
|
+
alignItems="center"
|
|
137
|
+
justifyContent="center"
|
|
138
|
+
flexShrink={ 0 }
|
|
139
|
+
width={ 20 }
|
|
140
|
+
height={ 20 }
|
|
141
|
+
{ ...INDICATOR_SIZE[resolvedSize] }
|
|
142
|
+
// The default icon and a caller's custom `icon` both use
|
|
143
|
+
// `fill="currentColor"`; `.lux-icon` (tokens.css) is the existing
|
|
144
|
+
// descendant-selector rule that sizes an arbitrary child svg to the
|
|
145
|
+
// box (20px default) instead of its own intrinsic size — no prop
|
|
146
|
+
// reaches a child element, so this is shared, not reinvented here.
|
|
147
|
+
className="lux-icon"
|
|
148
|
+
style={{ color: ALERT_FG }}
|
|
149
|
+
>
|
|
129
150
|
{ icon || defaultIcon }
|
|
130
|
-
</
|
|
151
|
+
</XStack>
|
|
131
152
|
);
|
|
132
153
|
})();
|
|
133
154
|
|
|
@@ -156,37 +177,49 @@ export const Alert = React.forwardRef<HTMLDivElement, AlertProps>(
|
|
|
156
177
|
...(descColumnGap !== undefined ? { columnGap: typeof descColumnGap === 'number' ? `${ descColumnGap * 4 }px` : descColumnGap } : {}),
|
|
157
178
|
};
|
|
158
179
|
|
|
180
|
+
// Row when inline (text sits beside the title), column otherwise.
|
|
181
|
+
const ContentFrame = inline ? XStack : YStack;
|
|
182
|
+
|
|
159
183
|
return (
|
|
160
184
|
<Skeleton loading={ loading }>
|
|
161
|
-
<
|
|
162
|
-
ref={ ref }
|
|
163
|
-
|
|
185
|
+
<XStack
|
|
186
|
+
ref={ ref as never }
|
|
187
|
+
{ ...ALERT_BASE }
|
|
188
|
+
paddingHorizontal={ SIZE[resolvedSize].paddingHorizontal }
|
|
189
|
+
backgroundColor={ STATUS_BG[status] as never }
|
|
190
|
+
className={ className }
|
|
164
191
|
style={ alertStyle }
|
|
165
|
-
{ ...alertRest }
|
|
192
|
+
{ ...(alertRest as object) }
|
|
166
193
|
>
|
|
167
194
|
{ iconElement }
|
|
168
195
|
{ children ? (
|
|
169
|
-
<
|
|
170
|
-
{ title &&
|
|
171
|
-
|
|
172
|
-
|
|
196
|
+
<ContentFrame flex={ 1 } alignItems={ inline ? 'center' : undefined }>
|
|
197
|
+
{ title && (
|
|
198
|
+
<Text fontWeight="600" fontSize={ fontSize } color={ ALERT_FG }>
|
|
199
|
+
{ title }
|
|
200
|
+
</Text>
|
|
201
|
+
) }
|
|
202
|
+
<XStack
|
|
203
|
+
flexWrap="wrap"
|
|
204
|
+
className={ descClassName }
|
|
173
205
|
style={ Object.keys(descStyle).length > 0 ? descStyle : undefined }
|
|
174
|
-
{ ...descRest }
|
|
206
|
+
{ ...(descRest as object) }
|
|
175
207
|
>
|
|
176
|
-
{ children }
|
|
177
|
-
</
|
|
178
|
-
</
|
|
208
|
+
{ ink(children, undefined, { fontSize, color: ALERT_FG }) }
|
|
209
|
+
</XStack>
|
|
210
|
+
</ContentFrame>
|
|
179
211
|
) : (
|
|
180
|
-
<
|
|
212
|
+
<Text flex={ 1 } fontWeight="600" fontSize={ fontSize } color={ ALERT_FG }>
|
|
213
|
+
{ title }
|
|
214
|
+
</Text>
|
|
181
215
|
) }
|
|
182
216
|
{ endElement }
|
|
183
217
|
{ closable && (
|
|
184
|
-
<
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
/>
|
|
218
|
+
<View position="relative" alignSelf="flex-start">
|
|
219
|
+
<CloseButton onClick={ handleClose }/>
|
|
220
|
+
</View>
|
|
188
221
|
) }
|
|
189
|
-
</
|
|
222
|
+
</XStack>
|
|
190
223
|
</Skeleton>
|
|
191
224
|
);
|
|
192
225
|
},
|