@arbor-education/design-system.components 3.1.0 → 3.2.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.
- package/.gather/skills/write-stories/README.md +8 -4
- package/.gather/skills/write-stories/SKILL.md +59 -5
- package/.github/pull_request_template.md +4 -3
- package/CHANGELOG.md +20 -0
- package/CONTRIBUTING.md +1 -0
- package/dist/components/breadcrumbs/Breadcrumbs.stories.d.ts +1 -0
- package/dist/components/breadcrumbs/Breadcrumbs.stories.d.ts.map +1 -1
- package/dist/components/breadcrumbs/Breadcrumbs.stories.js +103 -21
- package/dist/components/breadcrumbs/Breadcrumbs.stories.js.map +1 -1
- package/dist/components/card/Card.stories.d.ts.map +1 -1
- package/dist/components/card/Card.stories.js +46 -41
- package/dist/components/card/Card.stories.js.map +1 -1
- package/dist/components/editableText/EditableText.stories.d.ts.map +1 -1
- package/dist/components/editableText/EditableText.stories.js +19 -6
- package/dist/components/editableText/EditableText.stories.js.map +1 -1
- package/dist/components/formField/fieldset/Fieldset.stories.d.ts.map +1 -1
- package/dist/components/formField/fieldset/Fieldset.stories.js +5 -3
- package/dist/components/formField/fieldset/Fieldset.stories.js.map +1 -1
- package/dist/components/formField/inputs/selectDropdown/items/item/SelectDropdownItem.d.ts +2 -2
- package/dist/components/formField/inputs/selectDropdown/items/item/SelectDropdownItem.d.ts.map +1 -1
- package/dist/components/formField/inputs/selectDropdown/items/item/SelectDropdownItem.js +0 -1
- package/dist/components/formField/inputs/selectDropdown/items/item/SelectDropdownItem.js.map +1 -1
- package/dist/components/formField/inputs/time/TimeInput.d.ts +1 -1
- package/dist/components/formField/inputs/time/TimeInput.stories.d.ts +1 -1
- package/dist/components/icon/Icon.d.ts.map +1 -1
- package/dist/components/icon/Icon.js +18 -3
- package/dist/components/icon/Icon.js.map +1 -1
- package/dist/components/icon/Icon.stories.d.ts +4 -2
- package/dist/components/icon/Icon.stories.d.ts.map +1 -1
- package/dist/components/icon/Icon.stories.js +150 -102
- package/dist/components/icon/Icon.stories.js.map +1 -1
- package/dist/components/icon/Icon.test.js +146 -6
- package/dist/components/icon/Icon.test.js.map +1 -1
- package/dist/components/icon/LucideIcon.d.ts +17 -0
- package/dist/components/icon/LucideIcon.d.ts.map +1 -0
- package/dist/components/icon/LucideIcon.js +73 -0
- package/dist/components/icon/LucideIcon.js.map +1 -0
- package/dist/components/icon/allowedIcons.d.ts +44 -108
- package/dist/components/icon/allowedIcons.d.ts.map +1 -1
- package/dist/components/icon/allowedIcons.js +52 -112
- package/dist/components/icon/allowedIcons.js.map +1 -1
- package/dist/components/icon/types.d.ts +5 -4
- package/dist/components/icon/types.d.ts.map +1 -1
- package/dist/components/row/Row.stories.d.ts.map +1 -1
- package/dist/components/row/Row.stories.js +22 -13
- package/dist/components/row/Row.stories.js.map +1 -1
- package/dist/components/sidebar/Sidebar.stories.d.ts.map +1 -1
- package/dist/components/sidebar/Sidebar.stories.js +19 -24
- package/dist/components/sidebar/Sidebar.stories.js.map +1 -1
- package/dist/components/table/Table.stories.d.ts.map +1 -1
- package/dist/components/table/Table.stories.js +16 -10
- package/dist/components/table/Table.stories.js.map +1 -1
- package/dist/components/table/cellRenderers/SelectDropdownCellRenderer.stories.d.ts.map +1 -1
- package/dist/components/table/cellRenderers/SelectDropdownCellRenderer.stories.js +8 -5
- package/dist/components/table/cellRenderers/SelectDropdownCellRenderer.stories.js.map +1 -1
- package/dist/components/table/tableControls/TableControls.stories.d.ts +1 -1
- package/dist/components/table/tableControls/TableControls.stories.d.ts.map +1 -1
- package/dist/components/table/tableControls/TableControls.stories.js +26 -21
- package/dist/components/table/tableControls/TableControls.stories.js.map +1 -1
- package/dist/components/tabs/Tabs.d.ts +1 -1
- package/dist/components/tabs/Tabs.stories.d.ts +1 -1
- package/dist/components/tabs/Tabs.stories.js +1 -1
- package/dist/components/tabs/Tabs.stories.js.map +1 -1
- package/dist/components/tabs/TabsItem.d.ts +2 -2
- package/dist/components/tabs/TabsItem.d.ts.map +1 -1
- package/dist/components/tabs/TabsItem.stories.d.ts +5 -5
- package/dist/components/tabs/TabsItem.stories.d.ts.map +1 -1
- package/dist/components/tabs/TabsItem.stories.js +7 -4
- package/dist/components/tabs/TabsItem.stories.js.map +1 -1
- package/dist/components/widget/Widget.stories.d.ts +13 -0
- package/dist/components/widget/Widget.stories.d.ts.map +1 -1
- package/dist/components/widget/Widget.stories.js +45 -6
- package/dist/components/widget/Widget.stories.js.map +1 -1
- package/dist/components/widgetCalendar/WidgetCalendar.stories.d.ts +88 -16
- package/dist/components/widgetCalendar/WidgetCalendar.stories.d.ts.map +1 -1
- package/dist/components/widgetCalendar/WidgetCalendar.stories.js +443 -30
- package/dist/components/widgetCalendar/WidgetCalendar.stories.js.map +1 -1
- package/dist/index.css +1 -4
- package/dist/index.css.map +1 -1
- package/package.json +1 -1
- package/src/components/breadcrumbs/Breadcrumbs.stories.tsx +112 -21
- package/src/components/card/Card.stories.tsx +46 -41
- package/src/components/datePicker/datePicker.scss +0 -5
- package/src/components/editableText/EditableText.stories.tsx +26 -29
- package/src/components/formField/fieldset/Fieldset.stories.tsx +5 -3
- package/src/components/formField/inputs/selectDropdown/items/item/SelectDropdownItem.tsx +2 -2
- package/src/components/icon/Icon.stories.tsx +224 -119
- package/src/components/icon/Icon.test.tsx +178 -6
- package/src/components/icon/Icon.tsx +25 -3
- package/src/components/icon/LucideIcon.tsx +94 -0
- package/src/components/icon/allowedIcons.tsx +67 -207
- package/src/components/icon/lucideDynamic.d.ts +13 -0
- package/src/components/icon/types.ts +5 -4
- package/src/components/row/Row.stories.tsx +20 -14
- package/src/components/sidebar/Sidebar.stories.tsx +61 -112
- package/src/components/table/Table.stories.tsx +25 -10
- package/src/components/table/cellRenderers/SelectDropdownCellRenderer.stories.tsx +8 -5
- package/src/components/table/tableControls/TableControls.stories.tsx +26 -21
- package/src/components/tabs/Tabs.stories.tsx +1 -1
- package/src/components/tabs/TabsItem.stories.tsx +8 -4
- package/src/components/tabs/TabsItem.tsx +2 -2
- package/src/components/widget/Widget.stories.tsx +61 -6
- package/src/components/widgetCalendar/WidgetCalendar.stories.tsx +547 -74
- package/src/tokens.scss +1 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { useEffect, useReducer } from 'react';
|
|
2
|
+
import { dynamicIconImports, iconNames } from 'lucide-react/dynamic.mjs';
|
|
3
|
+
import type { DynamicIconModule, IconName } from 'lucide-react/dynamic';
|
|
4
|
+
import type { CustomIconProps } from './types.js';
|
|
5
|
+
|
|
6
|
+
/** Every icon name published by `lucide-react`, kebab-cased. */
|
|
7
|
+
export type LucideIconName = IconName;
|
|
8
|
+
|
|
9
|
+
/** Every `lucide-react` icon name, for building galleries and validating input. */
|
|
10
|
+
export const lucideIconNames: readonly LucideIconName[] = iconNames;
|
|
11
|
+
|
|
12
|
+
type IconLoader = () => Promise<DynamicIconModule>;
|
|
13
|
+
type LucideIconComponent = DynamicIconModule['default'];
|
|
14
|
+
|
|
15
|
+
const iconLoaders: Readonly<Record<LucideIconName, IconLoader>> = dynamicIconImports;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Icons resolve through `lucide-react`'s per-icon dynamic imports so that consumers only ship the
|
|
19
|
+
* icons they actually render, rather than all ~1,900 of them. Both caches are module-scoped: the
|
|
20
|
+
* first render of a given icon anywhere in the app pays the import, and every render after that —
|
|
21
|
+
* including new instances in other trees — resolves synchronously from `loadedIcons`.
|
|
22
|
+
*/
|
|
23
|
+
const loadedIcons = new Map<LucideIconName, LucideIconComponent>();
|
|
24
|
+
const inFlightLoads = new Map<LucideIconName, Promise<void>>();
|
|
25
|
+
|
|
26
|
+
const loadIcon = (name: LucideIconName): Promise<void> => {
|
|
27
|
+
const inFlight = inFlightLoads.get(name);
|
|
28
|
+
if (inFlight) {
|
|
29
|
+
return inFlight;
|
|
30
|
+
}
|
|
31
|
+
// Annotated rather than inferred: names reaching `Icon` from a backend are not always valid.
|
|
32
|
+
const loader: IconLoader | undefined = iconLoaders[name];
|
|
33
|
+
if (!loader) {
|
|
34
|
+
console.error(`[design-system] "${name}" is not a known icon name.`);
|
|
35
|
+
// Cached so an unknown name is only reported once, however many times it is rendered.
|
|
36
|
+
const noSuchIcon = Promise.resolve();
|
|
37
|
+
inFlightLoads.set(name, noSuchIcon);
|
|
38
|
+
return noSuchIcon;
|
|
39
|
+
}
|
|
40
|
+
const load = loader()
|
|
41
|
+
.then((iconModule) => {
|
|
42
|
+
loadedIcons.set(name, iconModule.default);
|
|
43
|
+
})
|
|
44
|
+
.catch((error: unknown) => {
|
|
45
|
+
// Drop the rejected promise so a later render can retry (e.g. after a transient chunk 404).
|
|
46
|
+
inFlightLoads.delete(name);
|
|
47
|
+
console.error(`[design-system] Could not load the "${name}" icon.`, error);
|
|
48
|
+
});
|
|
49
|
+
inFlightLoads.set(name, load);
|
|
50
|
+
return load;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const useLucideIcon = (name: LucideIconName): LucideIconComponent | undefined => {
|
|
54
|
+
const [, rerender] = useReducer((renderCount: number) => renderCount + 1, 0);
|
|
55
|
+
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
if (loadedIcons.has(name)) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
let isMounted = true;
|
|
61
|
+
void loadIcon(name).then(() => {
|
|
62
|
+
if (isMounted) {
|
|
63
|
+
rerender();
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
return () => {
|
|
67
|
+
isMounted = false;
|
|
68
|
+
};
|
|
69
|
+
}, [name]);
|
|
70
|
+
|
|
71
|
+
return loadedIcons.get(name);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export type LucideIconProps = CustomIconProps & {
|
|
75
|
+
name: LucideIconName;
|
|
76
|
+
fill?: string;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Renders a single `lucide-react` icon, loading it on demand. Until the icon resolves an empty SVG
|
|
81
|
+
* of the same size is rendered in its place, so the surrounding layout does not shift and the
|
|
82
|
+
* element keeps its accessible role throughout.
|
|
83
|
+
*/
|
|
84
|
+
export const LucideIcon = (props: LucideIconProps) => {
|
|
85
|
+
const { name, ...iconProps } = props;
|
|
86
|
+
const Component = useLucideIcon(name);
|
|
87
|
+
|
|
88
|
+
if (!Component) {
|
|
89
|
+
const { size, ...placeholderProps } = iconProps;
|
|
90
|
+
return <svg width={size} height={size} viewBox="0 0 24 24" {...placeholderProps} />;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return <Component {...iconProps} />;
|
|
94
|
+
};
|
|
@@ -1,217 +1,77 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
ArrowDown,
|
|
4
|
-
ArrowDownUp,
|
|
5
|
-
ArrowLeft,
|
|
6
|
-
ArrowRight,
|
|
7
|
-
ArrowRightFromLine,
|
|
8
|
-
ArrowUp,
|
|
9
|
-
ArrowUpRight,
|
|
10
|
-
BookOpen,
|
|
11
|
-
CalendarFold,
|
|
12
|
-
Camera,
|
|
13
|
-
ChartColumnIncreasing,
|
|
14
|
-
ChartSpline,
|
|
15
|
-
Check,
|
|
16
|
-
ChevronDown,
|
|
17
|
-
ChevronLeft,
|
|
18
|
-
ChevronRight,
|
|
19
|
-
ChevronsLeft,
|
|
20
|
-
ChevronsRight,
|
|
21
|
-
ChevronUp,
|
|
22
|
-
CircleAlert,
|
|
23
|
-
CircleCheck,
|
|
24
|
-
CircleCheckBig,
|
|
25
|
-
CirclePercent,
|
|
26
|
-
CirclePlus,
|
|
27
|
-
CircleQuestionMark,
|
|
28
|
-
CircleX,
|
|
29
|
-
Clipboard,
|
|
30
|
-
ClipboardList,
|
|
31
|
-
Clock3,
|
|
32
|
-
CloudUpload,
|
|
33
|
-
Copy,
|
|
34
|
-
CornerDownLeft,
|
|
35
|
-
Dot,
|
|
36
|
-
Download,
|
|
37
|
-
Ellipsis,
|
|
38
|
-
EllipsisVertical,
|
|
39
|
-
Expand,
|
|
40
|
-
ExternalLink,
|
|
41
|
-
Eye,
|
|
42
|
-
EyeOff,
|
|
43
|
-
File,
|
|
44
|
-
Files,
|
|
45
|
-
Flag,
|
|
46
|
-
Funnel,
|
|
47
|
-
GraduationCap,
|
|
48
|
-
Grid3x3,
|
|
49
|
-
GripVertical,
|
|
50
|
-
History,
|
|
51
|
-
House,
|
|
52
|
-
Info,
|
|
53
|
-
IterationCcw,
|
|
54
|
-
LayoutList,
|
|
55
|
-
Lightbulb,
|
|
56
|
-
Link,
|
|
57
|
-
List,
|
|
58
|
-
ListFilterPlus,
|
|
59
|
-
ListTree,
|
|
60
|
-
LoaderCircle,
|
|
61
|
-
Lock,
|
|
62
|
-
LockOpen,
|
|
63
|
-
LogOut, type LucideProps,
|
|
64
|
-
Mail,
|
|
65
|
-
Menu,
|
|
66
|
-
MessageSquareMore,
|
|
67
|
-
Mic,
|
|
68
|
-
Minimize2,
|
|
69
|
-
Minus,
|
|
70
|
-
Monitor,
|
|
71
|
-
PaintBucket,
|
|
72
|
-
Paperclip,
|
|
73
|
-
PartyPopper,
|
|
74
|
-
Pencil,
|
|
75
|
-
Percent,
|
|
76
|
-
Phone,
|
|
77
|
-
Pin,
|
|
78
|
-
Plus,
|
|
79
|
-
Projector,
|
|
80
|
-
Redo2,
|
|
81
|
-
Save,
|
|
82
|
-
Search,
|
|
83
|
-
Send,
|
|
84
|
-
Settings,
|
|
85
|
-
Share,
|
|
86
|
-
Sheet,
|
|
87
|
-
SlidersHorizontal,
|
|
88
|
-
Smartphone,
|
|
89
|
-
Sparkles,
|
|
90
|
-
Star, SwitchCamera, Table, Trash2, TrendingDown, TrendingUp, TriangleAlert, Undo2, Upload, User,
|
|
91
|
-
Users,
|
|
92
|
-
UsersRound, X, Zap,
|
|
93
|
-
} from 'lucide-react';
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import { LucideIcon, lucideIconNames, type LucideIconName } from './LucideIcon.js';
|
|
94
3
|
import { AskArbor } from './customIcons/AskArbor.js';
|
|
95
4
|
import { CheckSolid } from './customIcons/CheckSolid.js';
|
|
96
5
|
import { Google } from './customIcons/Google.js';
|
|
97
6
|
import { XSolid } from './customIcons/XSolid.js';
|
|
98
7
|
import type { CustomIconProps } from './types.js';
|
|
99
8
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
'
|
|
111
|
-
'
|
|
112
|
-
'
|
|
113
|
-
'
|
|
114
|
-
'
|
|
115
|
-
'
|
|
116
|
-
'
|
|
117
|
-
'
|
|
118
|
-
'
|
|
119
|
-
'
|
|
120
|
-
'
|
|
121
|
-
'
|
|
122
|
-
'
|
|
123
|
-
'
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
'cloud-upload': CloudUpload,
|
|
132
|
-
'copy': Copy,
|
|
133
|
-
'corner-down-left': CornerDownLeft,
|
|
134
|
-
'date': CalendarFold,
|
|
135
|
-
'dot': Dot,
|
|
136
|
-
'download': Download,
|
|
137
|
-
'ellipsis-vertical': EllipsisVertical,
|
|
138
|
-
'ellipsis': Ellipsis,
|
|
139
|
-
'expand': Expand,
|
|
140
|
-
'external-link': ExternalLink,
|
|
141
|
-
'eye-off': EyeOff,
|
|
142
|
-
'eye': Eye,
|
|
143
|
-
'favourite-outline': Star,
|
|
144
|
-
'file': File,
|
|
145
|
-
'files': Files,
|
|
146
|
-
'flag': Flag,
|
|
147
|
-
'funnel': Funnel,
|
|
148
|
-
'grab': GripVertical,
|
|
149
|
-
'graduation-cap': GraduationCap,
|
|
150
|
-
'grid-3x3': Grid3x3,
|
|
151
|
-
'group': UsersRound,
|
|
152
|
-
'guardians': Users,
|
|
153
|
-
'history': History,
|
|
154
|
-
'house': House,
|
|
155
|
-
'info': Info,
|
|
156
|
-
'iteration-ccw': IterationCcw,
|
|
157
|
-
'layout-list': LayoutList,
|
|
158
|
-
'lightbulb': Lightbulb,
|
|
159
|
-
'link': Link,
|
|
160
|
-
'list-filter-plus': ListFilterPlus,
|
|
161
|
-
'list': List,
|
|
162
|
-
'list-tree': ListTree,
|
|
163
|
-
'loader': LoaderCircle,
|
|
164
|
-
'lock-open': LockOpen,
|
|
165
|
-
'lock': Lock,
|
|
166
|
-
'log-out': LogOut,
|
|
167
|
-
'mail': Mail,
|
|
168
|
-
'menu': Menu,
|
|
169
|
-
'message-square-more': MessageSquareMore,
|
|
170
|
-
'mic': Mic,
|
|
171
|
-
'minus': Minus,
|
|
172
|
-
'monitor': Monitor,
|
|
173
|
-
'paint-bucket': PaintBucket,
|
|
174
|
-
'paperclip': Paperclip,
|
|
175
|
-
'party-popper': PartyPopper,
|
|
176
|
-
'pencil': Pencil,
|
|
177
|
-
'percent': Percent,
|
|
178
|
-
'phone': Phone,
|
|
179
|
-
'pin': Pin,
|
|
180
|
-
'plus': Plus,
|
|
181
|
-
'projector': Projector,
|
|
182
|
-
'redo': Redo2,
|
|
183
|
-
'save': Save,
|
|
184
|
-
'search': Search,
|
|
185
|
-
'send': Send,
|
|
186
|
-
'settings': Settings,
|
|
187
|
-
'share': Share,
|
|
188
|
-
'sheet': Sheet,
|
|
189
|
-
'shrink': Minimize2,
|
|
190
|
-
'sliders-horizontal': SlidersHorizontal,
|
|
191
|
-
'smartphone': Smartphone,
|
|
192
|
-
'sorting': ArrowDownUp,
|
|
193
|
-
'sparkles': Sparkles,
|
|
194
|
-
'staff': GraduationCap,
|
|
195
|
-
'switch-camera': SwitchCamera,
|
|
196
|
-
'table': Table,
|
|
197
|
-
'trash': Trash2,
|
|
198
|
-
'trending-down': TrendingDown,
|
|
199
|
-
'trending-up': TrendingUp,
|
|
200
|
-
'triangle-alert': TriangleAlert,
|
|
201
|
-
'undo': Undo2,
|
|
202
|
-
'upload': Upload,
|
|
203
|
-
'user': User,
|
|
204
|
-
'x': X,
|
|
205
|
-
'zap': Zap,
|
|
206
|
-
// custom icons
|
|
9
|
+
/**
|
|
10
|
+
* Arbor names that point at a differently-named Lucide icon. These take precedence over the Lucide
|
|
11
|
+
* name they shadow, so `trash` stays the lidded can (`trash-2`) rather than Lucide's plain `trash`.
|
|
12
|
+
* Seven of these — `grab`, `group`, `loader`, `redo`, `shrink`, `trash` and `undo` — shadow a real
|
|
13
|
+
* Lucide icon with a different glyph, which is deliberate: renaming them would break every existing
|
|
14
|
+
* consumer. Reach for the target name directly if you want Lucide's version.
|
|
15
|
+
*
|
|
16
|
+
* The `satisfies` clause makes every target a compile-time-checked Lucide icon name.
|
|
17
|
+
*/
|
|
18
|
+
export const iconAliases = {
|
|
19
|
+
'3-dot': 'ellipsis-vertical',
|
|
20
|
+
'circle-help': 'circle-question-mark',
|
|
21
|
+
'date': 'calendar-fold',
|
|
22
|
+
'favourite-outline': 'star',
|
|
23
|
+
'grab': 'grip-vertical',
|
|
24
|
+
'group': 'users-round',
|
|
25
|
+
'guardians': 'users',
|
|
26
|
+
'loader': 'loader-circle',
|
|
27
|
+
'redo': 'redo-2',
|
|
28
|
+
'shrink': 'minimize-2',
|
|
29
|
+
'sorting': 'arrow-down-up',
|
|
30
|
+
'staff': 'graduation-cap',
|
|
31
|
+
'trash': 'trash-2',
|
|
32
|
+
'undo': 'undo-2',
|
|
33
|
+
} as const satisfies Readonly<Record<string, LucideIconName>>;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Icons that are not plain Lucide glyphs: Arbor-specific artwork, and the solid variants built for
|
|
37
|
+
* particular semantic emphasis states.
|
|
38
|
+
*/
|
|
39
|
+
export const customIcons = {
|
|
207
40
|
'ask-arbor': AskArbor,
|
|
208
41
|
'google': Google,
|
|
209
|
-
|
|
210
|
-
'
|
|
211
|
-
|
|
212
|
-
<Star {...props} fill={props.color} />
|
|
42
|
+
'check-solid': CheckSolid,
|
|
43
|
+
'favourite-filled': (props: CustomIconProps) => (
|
|
44
|
+
<LucideIcon name="star" {...props} fill={props.color} />
|
|
213
45
|
),
|
|
214
|
-
'x-solid':
|
|
215
|
-
} as const
|
|
46
|
+
'x-solid': XSolid,
|
|
47
|
+
} as const satisfies Readonly<Record<string, (props: CustomIconProps) => ReactNode>>;
|
|
48
|
+
|
|
49
|
+
export type IconName
|
|
50
|
+
= | LucideIconName
|
|
51
|
+
| keyof typeof iconAliases
|
|
52
|
+
| keyof typeof customIcons;
|
|
53
|
+
|
|
54
|
+
// `Object.keys` widens to `string[]`, so the key unions are reasserted here.
|
|
55
|
+
const aliasNames = Object.keys(iconAliases) as ReadonlyArray<keyof typeof iconAliases>;
|
|
56
|
+
const customIconNames = Object.keys(customIcons) as ReadonlyArray<keyof typeof customIcons>;
|
|
57
|
+
|
|
58
|
+
/** Every name `Icon` accepts: the whole Lucide set, plus Arbor's aliases and custom icons. */
|
|
59
|
+
export const iconNames: readonly IconName[] = [
|
|
60
|
+
...lucideIconNames,
|
|
61
|
+
...aliasNames,
|
|
62
|
+
...customIconNames,
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* The `Icon` name allow-list, as a plain object so that membership can be tested with
|
|
67
|
+
* `hasOwnProperty`. Prefer {@link isIconName}; the values carry no meaning and exist only to make
|
|
68
|
+
* this a record. Icon components are no longer held here — they are loaded on demand, so nothing
|
|
69
|
+
* outside a render has a component to hand out.
|
|
70
|
+
*/
|
|
71
|
+
export const allowedIcons = Object.freeze(
|
|
72
|
+
Object.fromEntries(iconNames.map(name => [name, true])),
|
|
73
|
+
) as Readonly<Record<IconName, true>>;
|
|
216
74
|
|
|
217
|
-
|
|
75
|
+
/** Narrows an arbitrary string to an `IconName`, for validating names that come from a backend. */
|
|
76
|
+
export const isIconName = (name: string): name is IconName =>
|
|
77
|
+
Object.prototype.hasOwnProperty.call(allowedIcons, name);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `lucide-react` ships its dynamic-import entry point as `dynamic.mjs` and declares no `exports`
|
|
3
|
+
* map, so the extensionless `lucide-react/dynamic` specifier only resolves in bundlers that list
|
|
4
|
+
* `.mjs` in `resolve.extensions`. `arbor-fe-library`'s webpack config does not, so we import the
|
|
5
|
+
* file by its exact name instead. TypeScript then looks for a sibling `dynamic.d.mts`, which the
|
|
6
|
+
* package does not publish — this declaration points it at the types that do ship.
|
|
7
|
+
*
|
|
8
|
+
* Type-only imports still use `lucide-react/dynamic`, which resolves under both `bundler` and
|
|
9
|
+
* classic `node` module resolution and is erased before any bundler sees it.
|
|
10
|
+
*/
|
|
11
|
+
declare module 'lucide-react/dynamic.mjs' {
|
|
12
|
+
export * from 'lucide-react/dynamic';
|
|
13
|
+
}
|
|
@@ -2,8 +2,9 @@ export const iconSizes = [12, 16, 24] as const;
|
|
|
2
2
|
export type IconSize = typeof iconSizes[number];
|
|
3
3
|
|
|
4
4
|
export type CustomIconProps = {
|
|
5
|
-
size: IconSize;
|
|
6
|
-
color?: string;
|
|
7
|
-
role?: string;
|
|
8
|
-
className?: string;
|
|
5
|
+
'size': IconSize;
|
|
6
|
+
'color'?: string;
|
|
7
|
+
'role'?: string;
|
|
8
|
+
'className'?: string;
|
|
9
|
+
'aria-hidden'?: boolean;
|
|
9
10
|
};
|
|
@@ -320,13 +320,11 @@ const MixedClickableAndStaticTemplate = () => (
|
|
|
320
320
|
<Row
|
|
321
321
|
label="SEN status"
|
|
322
322
|
value="SEN Support"
|
|
323
|
-
note="Review due May 2026"
|
|
324
323
|
onClick={() => {}}
|
|
325
324
|
/>
|
|
326
325
|
<Row
|
|
327
326
|
label="Attendance (YTD)"
|
|
328
327
|
value="88.3%"
|
|
329
|
-
note="Below 90% threshold"
|
|
330
328
|
onClick={() => {}}
|
|
331
329
|
/>
|
|
332
330
|
</Section>
|
|
@@ -778,13 +776,11 @@ function PupilDetailSection() {
|
|
|
778
776
|
<Row
|
|
779
777
|
label="SEN status"
|
|
780
778
|
value="SEN Support"
|
|
781
|
-
note="Review due May 2026"
|
|
782
779
|
onClick={() => navigateToSENRecord()}
|
|
783
780
|
/>
|
|
784
781
|
<Row
|
|
785
782
|
label="Attendance (YTD)"
|
|
786
783
|
value="88.3%"
|
|
787
|
-
note="Below 90% threshold"
|
|
788
784
|
onClick={() => navigateToAttendance()}
|
|
789
785
|
/>
|
|
790
786
|
</Section>
|
|
@@ -832,7 +828,7 @@ export default PersonalDetailsSection;
|
|
|
832
828
|
},
|
|
833
829
|
render: InSectionTemplate,
|
|
834
830
|
},
|
|
835
|
-
'The canonical real-world usage: `Row` components as children of a `Section`. The `Section` provides the container heading and background; each `Row` handles the label/value layout. Most product uses of `Row` are inside a `Section
|
|
831
|
+
'The canonical real-world usage: `Row` components as children of a `Section`. The `Section` provides the container heading and background; each `Row` handles the label/value layout. Most product uses of `Row` are inside a `Section` — one row demonstrates a linked value via `href` to show that pattern working inside a realistic panel too.',
|
|
836
832
|
);
|
|
837
833
|
|
|
838
834
|
export const LongContent: Story = withDescription(
|
|
@@ -857,7 +853,6 @@ function LongContentExample() {
|
|
|
857
853
|
label="Pupil premium eligibility reason"
|
|
858
854
|
value="Free school meals — ever 6"
|
|
859
855
|
note="Verified by LA"
|
|
860
|
-
onClick={() => navigateToEligibilityDetail()}
|
|
861
856
|
/>
|
|
862
857
|
</div>
|
|
863
858
|
);
|
|
@@ -878,7 +873,6 @@ export default LongContentExample;
|
|
|
878
873
|
label="Pupil premium eligibility reason"
|
|
879
874
|
value="Free school meals — ever 6"
|
|
880
875
|
note="Verified by LA"
|
|
881
|
-
onClick={fn()}
|
|
882
876
|
/>
|
|
883
877
|
</div>
|
|
884
878
|
),
|
|
@@ -1102,16 +1096,28 @@ export const WithTooltip: Story = withDescription(
|
|
|
1102
1096
|
code: `
|
|
1103
1097
|
import { Row } from '@arbor-education/design-system.components';
|
|
1104
1098
|
|
|
1105
|
-
function
|
|
1099
|
+
function ReadOnlyRows() {
|
|
1106
1100
|
return (
|
|
1107
|
-
<
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1101
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--spacing-small)' }}>
|
|
1102
|
+
<Row
|
|
1103
|
+
label="UPN"
|
|
1104
|
+
value="A823456789012"
|
|
1105
|
+
tooltipText="Unique Pupil Number — assigned by the DfE and cannot be changed here"
|
|
1106
|
+
/>
|
|
1107
|
+
<Row
|
|
1108
|
+
label="Admission number"
|
|
1109
|
+
value="001847"
|
|
1110
|
+
tooltipText="Set at admission — contact the office to request a change"
|
|
1111
|
+
/>
|
|
1112
|
+
{/* No tooltipText — hovering this row shows nothing */}
|
|
1113
|
+
<Row
|
|
1114
|
+
label="Year group"
|
|
1115
|
+
value="Year 9"
|
|
1116
|
+
/>
|
|
1117
|
+
</div>
|
|
1112
1118
|
);
|
|
1113
1119
|
}
|
|
1114
|
-
export default
|
|
1120
|
+
export default ReadOnlyRows;
|
|
1115
1121
|
`.trim(),
|
|
1116
1122
|
},
|
|
1117
1123
|
},
|