@attrus-ds/core 0.2.0 → 0.3.1

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 (80) hide show
  1. package/LICENSE +27 -27
  2. package/dist/assets/flags/Brazil-rounded.svg +15 -15
  3. package/dist/assets/flags/Brazil-square.svg +16 -16
  4. package/dist/assets/flags/Chile-rounded.svg +10 -10
  5. package/dist/assets/flags/Chile-square.svg +10 -10
  6. package/dist/assets/flags/Colombia-rounded.svg +9 -9
  7. package/dist/assets/flags/Colombia-square.svg +9 -9
  8. package/dist/assets/flags/EUA-rounded.svg +10 -10
  9. package/dist/assets/flags/EUA-square.svg +10 -10
  10. package/dist/assets/flags/EUR-rounded.svg +8 -8
  11. package/dist/assets/flags/EUR-square.svg +8 -8
  12. package/dist/assets/flags/Mexico-rounded.svg +18 -18
  13. package/dist/assets/flags/Mexico-square.svg +18 -18
  14. package/dist/assets/icon-attrus-dark.svg +2 -2
  15. package/dist/assets/icon-attrus-light.svg +2 -2
  16. package/dist/assets/logo-attrus-dark.svg +7 -7
  17. package/dist/assets/logo-attrus-light.svg +7 -7
  18. package/dist/css/components/buttons.css +301 -301
  19. package/dist/css/components/callout.css +195 -195
  20. package/dist/css/components/cards.css +272 -272
  21. package/dist/css/components/combobox.css +319 -319
  22. package/dist/css/components/empty-states.css +61 -61
  23. package/dist/css/components/inputs.css +975 -975
  24. package/dist/css/components/number-input.css +138 -138
  25. package/dist/css/components/overlays.css +374 -374
  26. package/dist/css/components/primitives.css +314 -314
  27. package/dist/css/components/sidebar.css +179 -179
  28. package/dist/css/components/smart-search.css +91 -91
  29. package/dist/css/components/status-pills.css +207 -207
  30. package/dist/css/components/stepper.css +309 -309
  31. package/dist/css/components/table.css +602 -602
  32. package/dist/css/components/tabs.css +319 -319
  33. package/dist/css/components/toast.css +243 -243
  34. package/dist/css/components/tooltip.css +231 -231
  35. package/dist/css/components/topbar.css +148 -148
  36. package/dist/css/tokens.css +666 -666
  37. package/dist/react/AppSidebar/AppSidebar.d.ts +50 -50
  38. package/dist/react/AppSidebar/AppSidebar.tsx +197 -197
  39. package/dist/react/AppTopbar/AppTopbar.d.ts +58 -58
  40. package/dist/react/AppTopbar/AppTopbar.tsx +203 -203
  41. package/dist/react/Atoms/Atoms.d.ts +149 -149
  42. package/dist/react/Atoms/Atoms.tsx +407 -407
  43. package/dist/react/Button/Button.d.ts +25 -25
  44. package/dist/react/Button/Button.tsx +83 -83
  45. package/dist/react/Callout/Callout.d.ts +29 -29
  46. package/dist/react/Callout/Callout.tsx +98 -98
  47. package/dist/react/Card/Card.d.ts +38 -38
  48. package/dist/react/Card/Card.tsx +93 -93
  49. package/dist/react/Combobox/Combobox.d.ts +52 -52
  50. package/dist/react/Combobox/Combobox.tsx +254 -254
  51. package/dist/react/DataTable/DataTable.d.ts +81 -81
  52. package/dist/react/DataTable/DataTable.tsx +308 -308
  53. package/dist/react/Drawer/Drawer.d.ts +35 -35
  54. package/dist/react/Drawer/Drawer.tsx +226 -226
  55. package/dist/react/Input/Input.d.ts +155 -155
  56. package/dist/react/Input/Input.tsx +478 -478
  57. package/dist/react/Modal/Modal.d.ts +27 -27
  58. package/dist/react/Modal/Modal.tsx +173 -173
  59. package/dist/react/NumberInput/NumberInput.d.ts +25 -25
  60. package/dist/react/NumberInput/NumberInput.tsx +117 -117
  61. package/dist/react/Pill/Pill.d.ts +31 -31
  62. package/dist/react/Pill/Pill.tsx +104 -104
  63. package/dist/react/SearchPill/SearchPill.d.ts +14 -14
  64. package/dist/react/SearchPill/SearchPill.tsx +40 -40
  65. package/dist/react/SmartSearch/SmartSearch.d.ts +65 -65
  66. package/dist/react/SmartSearch/SmartSearch.tsx +271 -271
  67. package/dist/react/Stepper/Stepper.d.ts +25 -25
  68. package/dist/react/Stepper/Stepper.tsx +94 -94
  69. package/dist/react/Tabs/Tabs.d.ts +32 -32
  70. package/dist/react/Tabs/Tabs.tsx +147 -147
  71. package/dist/react/Toast/Toast.d.ts +30 -30
  72. package/dist/react/Toast/Toast.tsx +94 -94
  73. package/dist/react/Tooltip/Tooltip.d.ts +23 -23
  74. package/dist/react/Tooltip/Tooltip.tsx +80 -80
  75. package/dist/react/ZeroState/ZeroState.d.ts +17 -17
  76. package/dist/react/ZeroState/ZeroState.tsx +45 -45
  77. package/dist/react/index.ts +35 -35
  78. package/dist/tokens/attrus-tokens.flat.json +1234 -1234
  79. package/dist/tokens/attrus-tokens.json +1089 -1089
  80. package/package.json +64 -64
@@ -1,271 +1,271 @@
1
- import * as React from 'react';
2
-
3
- /**
4
- * ATTRUS SmartSearch — typed shell over the canonical `.ss` system
5
- * (preview/components/smart-search.css). The global ⌘K command palette.
6
- *
7
- * The 8 canonical states (combobox.html §03) COMPOSE from props — the
8
- * component renders whatever slices it receives, in the doc's order:
9
- * input → chips → sections/list → hint → typeButtons → empty/loading/
10
- * error illo → footer (kbd hints).
11
- *
12
- * inactive → no popover content (flat input)
13
- * active → chips + sections (recents/suggestions)
14
- * word → resultLabel + sections
15
- * similar → hint + sections
16
- * id → hint + typeButtons
17
- * partial id → hint only
18
- * no results → empty
19
- * loading → loading
20
- * error → error (retry)
21
- *
22
- * Keyboard: ↑/↓ move `.is-active` across items, Enter selects, Esc → onClose.
23
- */
24
-
25
- export interface SSItem {
26
- key: string;
27
- /** Leading 28px tile glyph. */
28
- icon?: React.ReactNode;
29
- title: React.ReactNode;
30
- /** Muted second line. */
31
- sub?: React.ReactNode;
32
- /** Right-aligned mono hint (id, shortcut). */
33
- trailing?: React.ReactNode;
34
- }
35
-
36
- export interface SSSection {
37
- /** Uppercase section label (e.g. "Counterparties"). */
38
- label?: React.ReactNode;
39
- items: SSItem[];
40
- }
41
-
42
- export interface SSTypeButton {
43
- key: string;
44
- icon?: React.ReactNode;
45
- label: React.ReactNode;
46
- /** Muted right hint (e.g. "transaction"). */
47
- hint?: React.ReactNode;
48
- }
49
-
50
- export interface SmartSearchProps {
51
- value: string;
52
- onChange?: (value: string) => void;
53
- placeholder?: string;
54
- /** Leading input glyph (search icon). */
55
- icon?: React.ReactNode;
56
- /** Shows the × clear button when value is non-empty. */
57
- onClear?: () => void;
58
- /** Esc inside the input. */
59
- onClose?: () => void;
60
- /** Suggestion chips row (active state). */
61
- chips?: Array<{ key: string; icon?: React.ReactNode; label: React.ReactNode }>;
62
- onChipClick?: (key: string) => void;
63
- /** Grouped result rows. */
64
- sections?: SSSection[];
65
- onSelect?: (key: string) => void;
66
- /** "RESULTS (N)" line above the list. */
67
- resultLabel?: React.ReactNode;
68
- /** Tip card (similar match, ID format hint). */
69
- hint?: React.ReactNode;
70
- /** ID-disambiguation buttons. */
71
- typeButtons?: SSTypeButton[];
72
- onTypeSelect?: (key: string) => void;
73
- /** No-results state. */
74
- empty?: { title: React.ReactNode; sub?: React.ReactNode; illo?: React.ReactNode };
75
- /** Skeleton rows while fetching. */
76
- loading?: boolean;
77
- /** Fetch failed — retry. */
78
- error?: { title: React.ReactNode; sub?: React.ReactNode; retryLabel?: React.ReactNode; onRetry?: () => void; illo?: React.ReactNode };
79
- /** Kbd hint footer (↑↓ navigate · ↵ open · esc close). Default true when the popover has content. */
80
- footer?: boolean;
81
- className?: string;
82
- style?: React.CSSProperties;
83
- 'aria-label'?: string;
84
- }
85
-
86
- const SearchGlyph: React.FC = () => (
87
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
88
- <circle cx="11" cy="11" r="8" /><path d="m21 21-4.3-4.3" />
89
- </svg>
90
- );
91
-
92
- export const SmartSearch: React.FC<SmartSearchProps> = ({
93
- value,
94
- onChange,
95
- placeholder = 'Search or jump to…',
96
- icon,
97
- onClear,
98
- onClose,
99
- chips,
100
- onChipClick,
101
- sections,
102
- onSelect,
103
- resultLabel,
104
- hint,
105
- typeButtons,
106
- onTypeSelect,
107
- empty,
108
- loading = false,
109
- error,
110
- footer,
111
- className,
112
- style,
113
- ...rest
114
- }) => {
115
- const [active, setActive] = React.useState<string | null>(null);
116
- const flat = React.useMemo(() => (sections || []).reduce<SSItem[]>((a, s) => a.concat(s.items), []), [sections]);
117
-
118
- const hasBody = Boolean(
119
- (chips && chips.length) || (sections && sections.length) || hint != null ||
120
- (typeButtons && typeButtons.length) || empty != null || loading || error != null
121
- );
122
- const showFooter = footer != null ? footer : hasBody;
123
-
124
- const onKeyDown = (e: React.KeyboardEvent) => {
125
- if (e.key === 'Escape') { if (onClose) onClose(); return; }
126
- if (!flat.length) return;
127
- const idx = flat.findIndex((i) => i.key === active);
128
- if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
129
- e.preventDefault();
130
- const next = e.key === 'ArrowDown'
131
- ? flat[Math.min(idx + 1, flat.length - 1)]
132
- : flat[Math.max(idx - 1, 0)];
133
- setActive(next.key);
134
- } else if (e.key === 'Enter') {
135
- const hit = flat.find((i) => i.key === active);
136
- if (hit && onSelect) { e.preventDefault(); onSelect(hit.key); }
137
- }
138
- };
139
-
140
- const renderItem = (it: SSItem) => (
141
- <li
142
- key={it.key}
143
- className={['ss-list-item', it.key === active ? 'is-active' : ''].filter(Boolean).join(' ')}
144
- onMouseEnter={() => setActive(it.key)}
145
- onClick={() => onSelect && onSelect(it.key)}
146
- >
147
- {it.icon != null ? <span className="icon-leading">{it.icon}</span> : null}
148
- <span className="text">
149
- <span className="label">{it.title}</span>
150
- {it.sub != null ? <span className="meta">{it.sub}</span> : null}
151
- </span>
152
- {it.trailing != null ? (
153
- <span style={{ marginLeft: 'auto', flexShrink: 0, fontFamily: 'var(--font-mono)', fontSize: 'var(--fs-xxs)', color: 'var(--color-foreground-muted)' }}>{it.trailing}</span>
154
- ) : null}
155
- </li>
156
- );
157
-
158
- return (
159
- <div className={['ss', className || ''].filter(Boolean).join(' ')} style={style} role="search" aria-label={rest['aria-label'] || 'Smart search'}>
160
- <div className={['ss-input', hasBody ? '' : 'ss-input-flat'].filter(Boolean).join(' ')}>
161
- {icon != null ? icon : <SearchGlyph />}
162
- <input
163
- value={value}
164
- placeholder={placeholder}
165
- onChange={(e) => onChange && onChange(e.target.value)}
166
- onKeyDown={onKeyDown}
167
- role="combobox"
168
- aria-expanded={hasBody}
169
- />
170
- {onClear && value ? (
171
- <button type="button" className="ss-clear" aria-label="Clear" onClick={onClear}>
172
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2.25} strokeLinecap="round" aria-hidden="true"><line x1="18" y1="6" x2="6" y2="18" /><line x1="6" y1="6" x2="18" y2="18" /></svg>
173
- </button>
174
- ) : null}
175
- </div>
176
-
177
- {chips && chips.length ? (
178
- <div className="ss-section">
179
- <div className="ss-chips">
180
- {chips.map((c) => (
181
- <button key={c.key} type="button" className="ss-chip" onClick={() => onChipClick && onChipClick(c.key)}>
182
- {c.icon}
183
- {c.label}
184
- </button>
185
- ))}
186
- </div>
187
- </div>
188
- ) : null}
189
-
190
- {resultLabel != null ? (
191
- <div className="ss-section ss-result-label" style={{ borderBottom: 0, paddingBottom: 0 }}><span className="l">{resultLabel}</span></div>
192
- ) : null}
193
-
194
- {sections && sections.length ? sections.map((s, i) => (
195
- <div key={i} className="ss-section">
196
- {s.label != null ? <div className="ss-section-label">{s.label}</div> : null}
197
- <ul className="ss-list">{s.items.map(renderItem)}</ul>
198
- </div>
199
- )) : null}
200
-
201
- {hint != null ? (
202
- <div className="ss-section"><div className="ss-hint">{hint}</div></div>
203
- ) : null}
204
-
205
- {typeButtons && typeButtons.length ? (
206
- <div className="ss-section">
207
- <div className="ss-typebtns">
208
- {typeButtons.map((b) => (
209
- <button key={b.key} type="button" className="ss-typebtn" onClick={() => onTypeSelect && onTypeSelect(b.key)}>
210
- {b.icon}
211
- <span className="grow">{b.label}</span>
212
- {b.hint != null ? <span style={{ fontFamily: 'var(--font-mono)', fontSize: 'var(--fs-xxs)', color: 'var(--color-foreground-muted)' }}>{b.hint}</span> : null}
213
- </button>
214
- ))}
215
- </div>
216
- </div>
217
- ) : null}
218
-
219
- {empty != null && !loading && !error ? (
220
- <div className="ss-section">
221
- <div className="ss-illo">
222
- {empty.illo}
223
- <span className="title">{empty.title}</span>
224
- {empty.sub != null ? <span className="body">{empty.sub}</span> : null}
225
- </div>
226
- </div>
227
- ) : null}
228
-
229
- {loading ? (
230
- <div className="ss-skeleton" aria-busy="true" aria-hidden="true">
231
- {[0, 1, 2].map((i) => (
232
- <div key={i} className="ss-skel-row">
233
- <span className="ss-skel-circle ss-skel-shimmer" />
234
- <span className="ss-skel-bars">
235
- <span className="ss-skel-bar long ss-skel-shimmer" />
236
- <span className="ss-skel-bar short ss-skel-shimmer" />
237
- </span>
238
- </div>
239
- ))}
240
- </div>
241
- ) : null}
242
-
243
- {error != null ? (
244
- <div className="ss-section">
245
- <div className="ss-illo" role="alert">
246
- {error.illo}
247
- <span className="title">{error.title}</span>
248
- {error.sub != null ? <span className="body">{error.sub}</span> : null}
249
- {error.onRetry ? (
250
- <button type="button" className="ss-typebtn" style={{ marginTop: 'var(--space-2)' }} onClick={error.onRetry}>
251
- {error.retryLabel != null ? error.retryLabel : 'Try again'}
252
- </button>
253
- ) : null}
254
- </div>
255
- </div>
256
- ) : null}
257
-
258
- {showFooter ? (
259
- <div className="ss-foot">
260
- <span className="kbd-group"><kbd className="ss-kbd">↑</kbd><kbd className="ss-kbd">↓</kbd> navigate</span>
261
- <span className="sep" />
262
- <span className="kbd-group"><kbd className="ss-kbd">↵</kbd> open</span>
263
- <span className="sep" />
264
- <span className="kbd-group"><kbd className="ss-kbd">esc</kbd> close</span>
265
- </div>
266
- ) : null}
267
- </div>
268
- );
269
- };
270
-
271
- export default SmartSearch;
1
+ import * as React from 'react';
2
+
3
+ /**
4
+ * ATTRUS SmartSearch — typed shell over the canonical `.ss` system
5
+ * (preview/components/smart-search.css). The global ⌘K command palette.
6
+ *
7
+ * The 8 canonical states (combobox.html §03) COMPOSE from props — the
8
+ * component renders whatever slices it receives, in the doc's order:
9
+ * input → chips → sections/list → hint → typeButtons → empty/loading/
10
+ * error illo → footer (kbd hints).
11
+ *
12
+ * inactive → no popover content (flat input)
13
+ * active → chips + sections (recents/suggestions)
14
+ * word → resultLabel + sections
15
+ * similar → hint + sections
16
+ * id → hint + typeButtons
17
+ * partial id → hint only
18
+ * no results → empty
19
+ * loading → loading
20
+ * error → error (retry)
21
+ *
22
+ * Keyboard: ↑/↓ move `.is-active` across items, Enter selects, Esc → onClose.
23
+ */
24
+
25
+ export interface SSItem {
26
+ key: string;
27
+ /** Leading 28px tile glyph. */
28
+ icon?: React.ReactNode;
29
+ title: React.ReactNode;
30
+ /** Muted second line. */
31
+ sub?: React.ReactNode;
32
+ /** Right-aligned mono hint (id, shortcut). */
33
+ trailing?: React.ReactNode;
34
+ }
35
+
36
+ export interface SSSection {
37
+ /** Uppercase section label (e.g. "Counterparties"). */
38
+ label?: React.ReactNode;
39
+ items: SSItem[];
40
+ }
41
+
42
+ export interface SSTypeButton {
43
+ key: string;
44
+ icon?: React.ReactNode;
45
+ label: React.ReactNode;
46
+ /** Muted right hint (e.g. "transaction"). */
47
+ hint?: React.ReactNode;
48
+ }
49
+
50
+ export interface SmartSearchProps {
51
+ value: string;
52
+ onChange?: (value: string) => void;
53
+ placeholder?: string;
54
+ /** Leading input glyph (search icon). */
55
+ icon?: React.ReactNode;
56
+ /** Shows the × clear button when value is non-empty. */
57
+ onClear?: () => void;
58
+ /** Esc inside the input. */
59
+ onClose?: () => void;
60
+ /** Suggestion chips row (active state). */
61
+ chips?: Array<{ key: string; icon?: React.ReactNode; label: React.ReactNode }>;
62
+ onChipClick?: (key: string) => void;
63
+ /** Grouped result rows. */
64
+ sections?: SSSection[];
65
+ onSelect?: (key: string) => void;
66
+ /** "RESULTS (N)" line above the list. */
67
+ resultLabel?: React.ReactNode;
68
+ /** Tip card (similar match, ID format hint). */
69
+ hint?: React.ReactNode;
70
+ /** ID-disambiguation buttons. */
71
+ typeButtons?: SSTypeButton[];
72
+ onTypeSelect?: (key: string) => void;
73
+ /** No-results state. */
74
+ empty?: { title: React.ReactNode; sub?: React.ReactNode; illo?: React.ReactNode };
75
+ /** Skeleton rows while fetching. */
76
+ loading?: boolean;
77
+ /** Fetch failed — retry. */
78
+ error?: { title: React.ReactNode; sub?: React.ReactNode; retryLabel?: React.ReactNode; onRetry?: () => void; illo?: React.ReactNode };
79
+ /** Kbd hint footer (↑↓ navigate · ↵ open · esc close). Default true when the popover has content. */
80
+ footer?: boolean;
81
+ className?: string;
82
+ style?: React.CSSProperties;
83
+ 'aria-label'?: string;
84
+ }
85
+
86
+ const SearchGlyph: React.FC = () => (
87
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
88
+ <circle cx="11" cy="11" r="8" /><path d="m21 21-4.3-4.3" />
89
+ </svg>
90
+ );
91
+
92
+ export const SmartSearch: React.FC<SmartSearchProps> = ({
93
+ value,
94
+ onChange,
95
+ placeholder = 'Search or jump to…',
96
+ icon,
97
+ onClear,
98
+ onClose,
99
+ chips,
100
+ onChipClick,
101
+ sections,
102
+ onSelect,
103
+ resultLabel,
104
+ hint,
105
+ typeButtons,
106
+ onTypeSelect,
107
+ empty,
108
+ loading = false,
109
+ error,
110
+ footer,
111
+ className,
112
+ style,
113
+ ...rest
114
+ }) => {
115
+ const [active, setActive] = React.useState<string | null>(null);
116
+ const flat = React.useMemo(() => (sections || []).reduce<SSItem[]>((a, s) => a.concat(s.items), []), [sections]);
117
+
118
+ const hasBody = Boolean(
119
+ (chips && chips.length) || (sections && sections.length) || hint != null ||
120
+ (typeButtons && typeButtons.length) || empty != null || loading || error != null
121
+ );
122
+ const showFooter = footer != null ? footer : hasBody;
123
+
124
+ const onKeyDown = (e: React.KeyboardEvent) => {
125
+ if (e.key === 'Escape') { if (onClose) onClose(); return; }
126
+ if (!flat.length) return;
127
+ const idx = flat.findIndex((i) => i.key === active);
128
+ if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
129
+ e.preventDefault();
130
+ const next = e.key === 'ArrowDown'
131
+ ? flat[Math.min(idx + 1, flat.length - 1)]
132
+ : flat[Math.max(idx - 1, 0)];
133
+ setActive(next.key);
134
+ } else if (e.key === 'Enter') {
135
+ const hit = flat.find((i) => i.key === active);
136
+ if (hit && onSelect) { e.preventDefault(); onSelect(hit.key); }
137
+ }
138
+ };
139
+
140
+ const renderItem = (it: SSItem) => (
141
+ <li
142
+ key={it.key}
143
+ className={['ss-list-item', it.key === active ? 'is-active' : ''].filter(Boolean).join(' ')}
144
+ onMouseEnter={() => setActive(it.key)}
145
+ onClick={() => onSelect && onSelect(it.key)}
146
+ >
147
+ {it.icon != null ? <span className="icon-leading">{it.icon}</span> : null}
148
+ <span className="text">
149
+ <span className="label">{it.title}</span>
150
+ {it.sub != null ? <span className="meta">{it.sub}</span> : null}
151
+ </span>
152
+ {it.trailing != null ? (
153
+ <span style={{ marginLeft: 'auto', flexShrink: 0, fontFamily: 'var(--font-mono)', fontSize: 'var(--fs-xxs)', color: 'var(--color-foreground-muted)' }}>{it.trailing}</span>
154
+ ) : null}
155
+ </li>
156
+ );
157
+
158
+ return (
159
+ <div className={['ss', className || ''].filter(Boolean).join(' ')} style={style} role="search" aria-label={rest['aria-label'] || 'Smart search'}>
160
+ <div className={['ss-input', hasBody ? '' : 'ss-input-flat'].filter(Boolean).join(' ')}>
161
+ {icon != null ? icon : <SearchGlyph />}
162
+ <input
163
+ value={value}
164
+ placeholder={placeholder}
165
+ onChange={(e) => onChange && onChange(e.target.value)}
166
+ onKeyDown={onKeyDown}
167
+ role="combobox"
168
+ aria-expanded={hasBody}
169
+ />
170
+ {onClear && value ? (
171
+ <button type="button" className="ss-clear" aria-label="Clear" onClick={onClear}>
172
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2.25} strokeLinecap="round" aria-hidden="true"><line x1="18" y1="6" x2="6" y2="18" /><line x1="6" y1="6" x2="18" y2="18" /></svg>
173
+ </button>
174
+ ) : null}
175
+ </div>
176
+
177
+ {chips && chips.length ? (
178
+ <div className="ss-section">
179
+ <div className="ss-chips">
180
+ {chips.map((c) => (
181
+ <button key={c.key} type="button" className="ss-chip" onClick={() => onChipClick && onChipClick(c.key)}>
182
+ {c.icon}
183
+ {c.label}
184
+ </button>
185
+ ))}
186
+ </div>
187
+ </div>
188
+ ) : null}
189
+
190
+ {resultLabel != null ? (
191
+ <div className="ss-section ss-result-label" style={{ borderBottom: 0, paddingBottom: 0 }}><span className="l">{resultLabel}</span></div>
192
+ ) : null}
193
+
194
+ {sections && sections.length ? sections.map((s, i) => (
195
+ <div key={i} className="ss-section">
196
+ {s.label != null ? <div className="ss-section-label">{s.label}</div> : null}
197
+ <ul className="ss-list">{s.items.map(renderItem)}</ul>
198
+ </div>
199
+ )) : null}
200
+
201
+ {hint != null ? (
202
+ <div className="ss-section"><div className="ss-hint">{hint}</div></div>
203
+ ) : null}
204
+
205
+ {typeButtons && typeButtons.length ? (
206
+ <div className="ss-section">
207
+ <div className="ss-typebtns">
208
+ {typeButtons.map((b) => (
209
+ <button key={b.key} type="button" className="ss-typebtn" onClick={() => onTypeSelect && onTypeSelect(b.key)}>
210
+ {b.icon}
211
+ <span className="grow">{b.label}</span>
212
+ {b.hint != null ? <span style={{ fontFamily: 'var(--font-mono)', fontSize: 'var(--fs-xxs)', color: 'var(--color-foreground-muted)' }}>{b.hint}</span> : null}
213
+ </button>
214
+ ))}
215
+ </div>
216
+ </div>
217
+ ) : null}
218
+
219
+ {empty != null && !loading && !error ? (
220
+ <div className="ss-section">
221
+ <div className="ss-illo">
222
+ {empty.illo}
223
+ <span className="title">{empty.title}</span>
224
+ {empty.sub != null ? <span className="body">{empty.sub}</span> : null}
225
+ </div>
226
+ </div>
227
+ ) : null}
228
+
229
+ {loading ? (
230
+ <div className="ss-skeleton" aria-busy="true" aria-hidden="true">
231
+ {[0, 1, 2].map((i) => (
232
+ <div key={i} className="ss-skel-row">
233
+ <span className="ss-skel-circle ss-skel-shimmer" />
234
+ <span className="ss-skel-bars">
235
+ <span className="ss-skel-bar long ss-skel-shimmer" />
236
+ <span className="ss-skel-bar short ss-skel-shimmer" />
237
+ </span>
238
+ </div>
239
+ ))}
240
+ </div>
241
+ ) : null}
242
+
243
+ {error != null ? (
244
+ <div className="ss-section">
245
+ <div className="ss-illo" role="alert">
246
+ {error.illo}
247
+ <span className="title">{error.title}</span>
248
+ {error.sub != null ? <span className="body">{error.sub}</span> : null}
249
+ {error.onRetry ? (
250
+ <button type="button" className="ss-typebtn" style={{ marginTop: 'var(--space-2)' }} onClick={error.onRetry}>
251
+ {error.retryLabel != null ? error.retryLabel : 'Try again'}
252
+ </button>
253
+ ) : null}
254
+ </div>
255
+ </div>
256
+ ) : null}
257
+
258
+ {showFooter ? (
259
+ <div className="ss-foot">
260
+ <span className="kbd-group"><kbd className="ss-kbd">↑</kbd><kbd className="ss-kbd">↓</kbd> navigate</span>
261
+ <span className="sep" />
262
+ <span className="kbd-group"><kbd className="ss-kbd">↵</kbd> open</span>
263
+ <span className="sep" />
264
+ <span className="kbd-group"><kbd className="ss-kbd">esc</kbd> close</span>
265
+ </div>
266
+ ) : null}
267
+ </div>
268
+ );
269
+ };
270
+
271
+ export default SmartSearch;
@@ -1,25 +1,25 @@
1
- import * as React from 'react';
2
-
3
- export type StepState = 'todo' | 'current' | 'done' | 'error' | 'skipped';
4
-
5
- export interface StepItem {
6
- key: string;
7
- label: React.ReactNode;
8
- help?: React.ReactNode;
9
- state?: StepState;
10
- }
11
-
12
- export interface StepperProps extends React.OlHTMLAttributes<HTMLOListElement> {
13
- steps: StepItem[];
14
- vertical?: boolean;
15
- compact?: boolean;
16
- /** Minimal capsule+label variant (.is-rule) for tight headers. */
17
- rule?: boolean;
18
- /** ≤640px: hide labels, keep markers (.is-collapse-sm). */
19
- collapseOnMobile?: boolean;
20
- /** Step click (e.g. jump back to a done step). */
21
- onStepClick?: (key: string) => void;
22
- }
23
-
24
- export declare const Stepper: React.FC<StepperProps>;
25
- export default Stepper;
1
+ import * as React from 'react';
2
+
3
+ export type StepState = 'todo' | 'current' | 'done' | 'error' | 'skipped';
4
+
5
+ export interface StepItem {
6
+ key: string;
7
+ label: React.ReactNode;
8
+ help?: React.ReactNode;
9
+ state?: StepState;
10
+ }
11
+
12
+ export interface StepperProps extends React.OlHTMLAttributes<HTMLOListElement> {
13
+ steps: StepItem[];
14
+ vertical?: boolean;
15
+ compact?: boolean;
16
+ /** Minimal capsule+label variant (.is-rule) for tight headers. */
17
+ rule?: boolean;
18
+ /** ≤640px: hide labels, keep markers (.is-collapse-sm). */
19
+ collapseOnMobile?: boolean;
20
+ /** Step click (e.g. jump back to a done step). */
21
+ onStepClick?: (key: string) => void;
22
+ }
23
+
24
+ export declare const Stepper: React.FC<StepperProps>;
25
+ export default Stepper;