@arbor-education/design-system.components 3.2.0 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.gather/skills/write-stories/README.md +8 -4
- package/.gather/skills/write-stories/SKILL.md +59 -5
- package/CHANGELOG.md +24 -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/filterBar/FilterBar.d.ts +2 -2
- package/dist/components/filterBar/FilterBar.d.ts.map +1 -1
- package/dist/components/filterBar/FilterBar.js +7 -3
- package/dist/components/filterBar/FilterBar.js.map +1 -1
- package/dist/components/filterBar/FilterBar.stories.d.ts +16 -0
- package/dist/components/filterBar/FilterBar.stories.d.ts.map +1 -1
- package/dist/components/filterBar/FilterBar.stories.js +101 -7
- package/dist/components/filterBar/FilterBar.stories.js.map +1 -1
- package/dist/components/filterBar/FilterBar.test.js +30 -0
- package/dist/components/filterBar/FilterBar.test.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/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.d.ts.map +1 -1
- package/dist/components/table/Table.js +2 -1
- package/dist/components/table/Table.js.map +1 -1
- package/dist/components/table/Table.stories.d.ts +7 -0
- package/dist/components/table/Table.stories.d.ts.map +1 -1
- package/dist/components/table/Table.stories.js +60 -10
- package/dist/components/table/Table.stories.js.map +1 -1
- package/dist/components/table/Table.test.js +23 -0
- package/dist/components/table/Table.test.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.d.ts +2 -0
- package/dist/components/table/tableControls/TableControls.d.ts.map +1 -1
- package/dist/components/table/tableControls/TableControls.js +2 -2
- package/dist/components/table/tableControls/TableControls.js.map +1 -1
- package/dist/components/table/tableControls/TableControls.stories.d.ts +26 -1
- package/dist/components/table/tableControls/TableControls.stories.d.ts.map +1 -1
- package/dist/components/table/tableControls/TableControls.stories.js +50 -21
- package/dist/components/table/tableControls/TableControls.stories.js.map +1 -1
- package/dist/components/table/tableControls/TableControls.test.js +16 -0
- package/dist/components/table/tableControls/TableControls.test.js.map +1 -1
- package/dist/components/table/useTableSettings.d.ts +2 -0
- package/dist/components/table/useTableSettings.d.ts.map +1 -1
- package/dist/components/table/useTableSettings.js +12 -7
- package/dist/components/table/useTableSettings.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/filterBar/FilterBar.stories.tsx +126 -4
- package/src/components/filterBar/FilterBar.test.tsx +51 -0
- package/src/components/filterBar/FilterBar.tsx +18 -12
- package/src/components/formField/fieldset/Fieldset.stories.tsx +5 -3
- 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 +82 -10
- package/src/components/table/Table.test.tsx +42 -0
- package/src/components/table/Table.tsx +2 -0
- package/src/components/table/cellRenderers/SelectDropdownCellRenderer.stories.tsx +8 -5
- package/src/components/table/tableControls/TableControls.stories.tsx +54 -21
- package/src/components/table/tableControls/TableControls.test.tsx +20 -0
- package/src/components/table/tableControls/TableControls.tsx +6 -0
- package/src/components/table/useTableSettings.ts +20 -6
- package/src/components/widget/Widget.stories.tsx +61 -6
- package/src/components/widgetCalendar/WidgetCalendar.stories.tsx +547 -74
- package/src/tokens.scss +1 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { StoryObj } from '@storybook/react-vite';
|
|
2
|
+
import { WidgetCalendar } from './WidgetCalendar.js';
|
|
2
3
|
declare function WidgetCalendarDocsPage(): import("react").JSX.Element;
|
|
3
4
|
declare const meta: {
|
|
4
5
|
title: string;
|
|
@@ -33,28 +34,99 @@ declare const meta: {
|
|
|
33
34
|
};
|
|
34
35
|
};
|
|
35
36
|
tags: string[];
|
|
36
|
-
|
|
37
|
-
heading:
|
|
38
|
-
|
|
37
|
+
argTypes: {
|
|
38
|
+
heading: {
|
|
39
|
+
control: "text";
|
|
40
|
+
description: string;
|
|
41
|
+
table: {
|
|
42
|
+
type: {
|
|
43
|
+
summary: string;
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
tooltip: {
|
|
48
|
+
control: "text";
|
|
49
|
+
description: string;
|
|
50
|
+
table: {
|
|
51
|
+
type: {
|
|
52
|
+
summary: string;
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
headingHref: {
|
|
57
|
+
control: "text";
|
|
58
|
+
description: string;
|
|
59
|
+
table: {
|
|
60
|
+
type: {
|
|
61
|
+
summary: string;
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
};
|
|
39
65
|
items: {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
66
|
+
control: false;
|
|
67
|
+
description: string;
|
|
68
|
+
table: {
|
|
69
|
+
type: {
|
|
70
|
+
summary: string;
|
|
71
|
+
};
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
isFloating: {
|
|
75
|
+
control: "boolean";
|
|
76
|
+
description: string;
|
|
77
|
+
table: {
|
|
78
|
+
type: {
|
|
79
|
+
summary: string;
|
|
80
|
+
};
|
|
81
|
+
defaultValue: {
|
|
82
|
+
summary: string;
|
|
83
|
+
};
|
|
84
|
+
};
|
|
85
|
+
};
|
|
86
|
+
onItemClick: {
|
|
87
|
+
control: false;
|
|
88
|
+
description: string;
|
|
89
|
+
table: {
|
|
90
|
+
type: {
|
|
91
|
+
summary: string;
|
|
92
|
+
};
|
|
93
|
+
};
|
|
94
|
+
};
|
|
95
|
+
emptyState: {
|
|
96
|
+
control: false;
|
|
97
|
+
description: string;
|
|
98
|
+
table: {
|
|
99
|
+
type: {
|
|
100
|
+
summary: string;
|
|
101
|
+
};
|
|
102
|
+
defaultValue: {
|
|
103
|
+
summary: string;
|
|
104
|
+
};
|
|
105
|
+
};
|
|
106
|
+
};
|
|
107
|
+
className: {
|
|
108
|
+
control: false;
|
|
109
|
+
description: string;
|
|
110
|
+
table: {
|
|
111
|
+
type: {
|
|
112
|
+
summary: string;
|
|
113
|
+
};
|
|
114
|
+
};
|
|
115
|
+
};
|
|
52
116
|
};
|
|
53
117
|
};
|
|
54
118
|
export default meta;
|
|
55
|
-
type Story = StoryObj<typeof
|
|
119
|
+
type Story = StoryObj<typeof WidgetCalendar>;
|
|
56
120
|
export declare const Default: Story;
|
|
121
|
+
export declare const Interactive: Story;
|
|
122
|
+
export declare const PrimaryEventType: Story;
|
|
123
|
+
export declare const SecondaryEventType: Story;
|
|
124
|
+
export declare const NonInteractiveItems: Story;
|
|
125
|
+
export declare const WithoutLocation: Story;
|
|
126
|
+
export declare const CustomSecondaryContent: Story;
|
|
127
|
+
export declare const WithTooltip: Story;
|
|
57
128
|
export declare const Empty: Story;
|
|
129
|
+
export declare const CustomEmptyState: Story;
|
|
58
130
|
export declare const FixedHeightOverflow: Story;
|
|
59
131
|
export declare const Composable: Story;
|
|
60
132
|
//# sourceMappingURL=WidgetCalendar.stories.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WidgetCalendar.stories.d.ts","sourceRoot":"","sources":["../../../src/components/widgetCalendar/WidgetCalendar.stories.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"WidgetCalendar.stories.d.ts","sourceRoot":"","sources":["../../../src/components/widgetCalendar/WidgetCalendar.stories.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAQ,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAa5D,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AA0JrD,iBAAS,sBAAsB,gCAmB9B;AAMD,QAAA,MAAM,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiF6B,CAAC;AAExC,eAAe,IAAI,CAAC;AAGpB,KAAK,KAAK,GAAG,QAAQ,CAAC,OAAO,cAAc,CAAC,CAAC;AA0G7C,eAAO,MAAM,OAAO,EAAE,KAgBrB,CAAC;AAEF,eAAO,MAAM,WAAW,EAAE,KAsCzB,CAAC;AAEF,eAAO,MAAM,gBAAgB,EAAE,KA6B9B,CAAC;AAEF,eAAO,MAAM,kBAAkB,EAAE,KA6BhC,CAAC;AAEF,eAAO,MAAM,mBAAmB,EAAE,KA6BjC,CAAC;AAEF,eAAO,MAAM,eAAe,EAAE,KAwB7B,CAAC;AAEF,eAAO,MAAM,sBAAsB,EAAE,KA0BpC,CAAC;AAEF,eAAO,MAAM,WAAW,EAAE,KAoBzB,CAAC;AAEF,eAAO,MAAM,KAAK,EAAE,KAUnB,CAAC;AAEF,eAAO,MAAM,gBAAgB,EAAE,KAiB9B,CAAC;AAEF,eAAO,MAAM,mBAAmB,EAAE,KAmBjC,CAAC;AAEF,eAAO,MAAM,UAAU,EAAE,KAkDxB,CAAC"}
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Controls, Heading as DocHeading, Primary as DocPrimary,
|
|
2
|
+
import { Controls, Heading as DocHeading, Markdown, Primary as DocPrimary, Stories, Subtitle, Title, } from '@storybook/addon-docs/blocks';
|
|
3
|
+
import { useState } from 'react';
|
|
3
4
|
import { Widget } from '../widget/Widget.js';
|
|
4
5
|
import { fn } from 'storybook/test';
|
|
5
6
|
import { WidgetCalendar } from './WidgetCalendar.js';
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
// Fixture data
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
6
10
|
const classEventTime = {
|
|
7
11
|
eventSecondaryTerm: 'Time and location',
|
|
8
12
|
eventDateTime: '2026-06-08T08:45/2026-06-08T09:00',
|
|
@@ -76,24 +80,181 @@ const overflowItems = [
|
|
|
76
80
|
...initialItems,
|
|
77
81
|
...initialItems.map((item, index) => ({ ...item, id: `${item.id}-overflow-${index}` })),
|
|
78
82
|
];
|
|
83
|
+
// ---------------------------------------------------------------------------
|
|
84
|
+
// Docs page content
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
79
86
|
const DESCRIPTION_INTRO = [
|
|
80
|
-
'`WidgetCalendar` is a pre-composed dashboard widget for
|
|
81
|
-
'It wraps [`Widget`](?path=/docs/components-widget--docs) with event
|
|
82
|
-
'
|
|
83
|
-
|
|
87
|
+
'`WidgetCalendar` is a pre-composed dashboard widget for date/time-anchored calendar events.',
|
|
88
|
+
'It wraps [`Widget`](?path=/docs/components-widget--docs) with an event list, primary/secondary type indicators, and',
|
|
89
|
+
'optional heading navigation. Each item renders a semantic `<time>` element from an ISO `eventDateTime` and a',
|
|
90
|
+
'human-readable `eventTimeLabel`.',
|
|
91
|
+
].join('\n');
|
|
92
|
+
const PROPS_INTRO = 'The preview below is wired to the **Controls** panel — tweak any prop to see the story update in real time.';
|
|
93
|
+
const USAGE_GUIDANCE = [
|
|
94
|
+
'### When to use',
|
|
95
|
+
'',
|
|
96
|
+
'- Date/time-anchored list items — events, appointments, or timetable entries — that need a semantic `<time>` element',
|
|
97
|
+
'- Optional location context alongside the time (classroom, hall, room number)',
|
|
98
|
+
'- A dashboard widget where rows may optionally navigate somewhere on click',
|
|
99
|
+
'',
|
|
100
|
+
'---',
|
|
101
|
+
'',
|
|
102
|
+
'### When NOT to use',
|
|
103
|
+
'',
|
|
104
|
+
'| Situation | Use instead |',
|
|
105
|
+
'|---|---|',
|
|
106
|
+
'| Favourite/shortcut links with no date fields, needing a remove action | [`WidgetFavourites`](?path=/docs/components-widget-widgetfavourites--docs) (link + remove pattern) |',
|
|
107
|
+
'| Read/unread notification feed with a mark-all-as-read action | [`WidgetNotifications`](?path=/docs/components-widget-widgetnotifications--docs) |',
|
|
108
|
+
].join('\n');
|
|
109
|
+
const DEVELOPER_NOTES = [
|
|
110
|
+
'### Critical usage patterns',
|
|
111
|
+
'',
|
|
112
|
+
'**`onItemClick` signature differs between `WidgetCalendar` and `WidgetCalendar.Item`.** The parent\'s',
|
|
113
|
+
'`onItemClick` is `(id: string) => void` — it receives the clicked item\'s id. `WidgetCalendar.Item`\'s own',
|
|
114
|
+
'`onItemClick` prop takes **no arguments** (`() => void`). When composing manually (see the Composable',
|
|
115
|
+
'story), you must close over the id yourself: `onItemClick={() => handleClick(item.id)}`.',
|
|
116
|
+
'',
|
|
117
|
+
'**`buttonA11yText` fallback only fires for plain strings.** When `onItemClick` makes a row interactive,',
|
|
118
|
+
'the accessible label defaults to `Open ${eventPrimaryDefinition}` — but only when `eventPrimaryDefinition`',
|
|
119
|
+
'is a plain string. Pass a JSX node as the primary definition and the fallback silently degrades to the',
|
|
120
|
+
'generic `\'Open event\'`. Always supply `buttonA11yText` explicitly when `eventPrimaryDefinition` is rich content.',
|
|
121
|
+
'',
|
|
122
|
+
'**`eventSecondaryDefinition` is an all-or-nothing override.** When provided, it entirely replaces the',
|
|
123
|
+
'default time + location composition — `eventLocation` becomes dead data with no warning. Never pass both',
|
|
124
|
+
'in the same item.',
|
|
125
|
+
'',
|
|
126
|
+
'**The type-bar colour is semantic, not decorative.** `eventType="primary"` renders the brand-green bar',
|
|
127
|
+
'(`--widget-calendar-type-primary-color`), `"secondary"` renders neutral grey',
|
|
128
|
+
'(`--widget-calendar-type-secondary-color`). Choose deliberately — don\'t treat it as a purely visual toggle.',
|
|
129
|
+
'',
|
|
130
|
+
'**No loading, disabled, or error state exists on this component.** Handle those states in the consumer',
|
|
131
|
+
'(e.g. render a Skeleton in place of `WidgetCalendar` while data loads).',
|
|
132
|
+
'',
|
|
133
|
+
'---',
|
|
134
|
+
'',
|
|
135
|
+
'### Accessibility',
|
|
136
|
+
'',
|
|
137
|
+
'- Each item renders a `<dl>` with `sr-only` `<dt>` terms and visible `<dd>` definitions, pairing both the',
|
|
138
|
+
' primary and secondary content for screen readers.',
|
|
139
|
+
'- `eventDateTime` must be a valid ISO datetime string — it is passed directly to the `<time dateTime>` attribute.',
|
|
140
|
+
'- Interactive rows (`onItemClick` provided) get `role="button"`, `tabIndex={0}`, and respond to both',
|
|
141
|
+
' Enter and Space, matching the standard button-role keyboard contract.',
|
|
142
|
+
'- Rows without `onItemClick` render as plain, non-interactive `<li>` elements — no button semantics are added.',
|
|
143
|
+
].join('\n');
|
|
144
|
+
const RELATED_COMPONENTS = [
|
|
145
|
+
'## Related components',
|
|
146
|
+
'',
|
|
147
|
+
'[Widget](?path=/docs/components-widget--docs) · [WidgetFavourites](?path=/docs/components-widget-widgetfavourites--docs) · [WidgetNotifications](?path=/docs/components-widget-widgetnotifications--docs)',
|
|
148
|
+
].join('\n');
|
|
84
149
|
function WidgetCalendarDocsPage() {
|
|
85
|
-
return (_jsxs(_Fragment, { children: [_jsx(Title, {}), _jsx(Subtitle, {}), _jsx(Markdown, { children: DESCRIPTION_INTRO }), _jsx(DocHeading, { children: "Interactive example" }), _jsx(DocPrimary, {}), _jsx(Controls, {}), _jsx(DocHeading, { children: "Examples" }), _jsx(Stories, { title: "" })] }));
|
|
150
|
+
return (_jsxs(_Fragment, { children: [_jsx(Title, {}), _jsx(Subtitle, {}), _jsx(Markdown, { children: DESCRIPTION_INTRO }), _jsx(DocHeading, { children: "Interactive example" }), _jsx(Markdown, { children: PROPS_INTRO }), _jsx(DocPrimary, {}), _jsx(Controls, {}), _jsx(DocHeading, { children: "Usage guidance" }), _jsx(Markdown, { children: USAGE_GUIDANCE }), _jsx(DocHeading, { children: "Developer notes" }), _jsx(Markdown, { children: DEVELOPER_NOTES }), _jsx(DocHeading, { children: "Examples" }), _jsx(Stories, { title: "" }), _jsx(Markdown, { children: RELATED_COMPONENTS })] }));
|
|
86
151
|
}
|
|
152
|
+
// ---------------------------------------------------------------------------
|
|
153
|
+
// Meta
|
|
154
|
+
// ---------------------------------------------------------------------------
|
|
155
|
+
const meta = {
|
|
156
|
+
title: 'Components/Widget/WidgetCalendar',
|
|
157
|
+
component: WidgetCalendar,
|
|
158
|
+
parameters: {
|
|
159
|
+
layout: 'padded',
|
|
160
|
+
docs: {
|
|
161
|
+
page: WidgetCalendarDocsPage,
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
tags: ['autodocs'],
|
|
165
|
+
argTypes: {
|
|
166
|
+
heading: {
|
|
167
|
+
control: 'text',
|
|
168
|
+
description: 'Content rendered in the widget header. Usually a plain string, but accepts any `React.ReactNode`.',
|
|
169
|
+
table: {
|
|
170
|
+
type: { summary: 'React.ReactNode' },
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
tooltip: {
|
|
174
|
+
control: 'text',
|
|
175
|
+
description: 'Optional tooltip text shown alongside the heading, for supplementary context.',
|
|
176
|
+
table: {
|
|
177
|
+
type: { summary: 'string' },
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
headingHref: {
|
|
181
|
+
control: 'text',
|
|
182
|
+
description: 'When provided, renders the heading as a link to this URL (e.g. a full calendar page).',
|
|
183
|
+
table: {
|
|
184
|
+
type: { summary: 'string' },
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
items: {
|
|
188
|
+
control: false,
|
|
189
|
+
description: [
|
|
190
|
+
'Array of `WidgetCalendar.ItemData` objects rendered as event rows. Each item requires `id`,',
|
|
191
|
+
'`eventPrimaryTerm`, `eventPrimaryDefinition`, `eventSecondaryTerm`, `eventDateTime`, and `eventTimeLabel`.',
|
|
192
|
+
'`eventType`, `eventLocation`, `eventSecondaryDefinition`, and `buttonA11yText` are optional.',
|
|
193
|
+
].join(' '),
|
|
194
|
+
table: {
|
|
195
|
+
type: { summary: 'WidgetCalendar.ItemData[]' },
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
isFloating: {
|
|
199
|
+
control: 'boolean',
|
|
200
|
+
description: 'Applies the floating (elevated/shadowed) widget style. Defaults to `false`.',
|
|
201
|
+
table: {
|
|
202
|
+
type: { summary: 'boolean' },
|
|
203
|
+
defaultValue: { summary: 'false' },
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
onItemClick: {
|
|
207
|
+
control: false,
|
|
208
|
+
description: [
|
|
209
|
+
'Called with the clicked item\'s `id` when a row is activated (click, Enter, or Space).',
|
|
210
|
+
'Rows with no `onItemClick` render as plain, non-interactive list items.',
|
|
211
|
+
].join(' '),
|
|
212
|
+
table: {
|
|
213
|
+
type: { summary: '(id: string) => void' },
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
emptyState: {
|
|
217
|
+
control: false,
|
|
218
|
+
description: [
|
|
219
|
+
'Custom content rendered in place of the event list when `items` is empty.',
|
|
220
|
+
'Defaults to a built-in EmptyState with a date icon, "Nothing scheduled" heading, and',
|
|
221
|
+
'"There are no upcoming events." sub-heading.',
|
|
222
|
+
].join(' '),
|
|
223
|
+
table: {
|
|
224
|
+
type: { summary: 'React.ReactNode' },
|
|
225
|
+
defaultValue: { summary: 'Built-in EmptyState ("Nothing scheduled")' },
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
className: {
|
|
229
|
+
control: false,
|
|
230
|
+
description: 'Additional CSS class names on the root Widget element. Use sparingly.',
|
|
231
|
+
table: {
|
|
232
|
+
type: { summary: 'string' },
|
|
233
|
+
},
|
|
234
|
+
},
|
|
235
|
+
},
|
|
236
|
+
};
|
|
237
|
+
export default meta;
|
|
238
|
+
// ---------------------------------------------------------------------------
|
|
239
|
+
// Helper: attach a per-story description to docs
|
|
240
|
+
// ---------------------------------------------------------------------------
|
|
87
241
|
const withDescription = (story, description) => ({
|
|
88
242
|
...story,
|
|
89
243
|
parameters: {
|
|
90
244
|
...story.parameters,
|
|
91
245
|
docs: {
|
|
92
246
|
...story.parameters?.docs,
|
|
93
|
-
description: {
|
|
247
|
+
description: {
|
|
248
|
+
story: description,
|
|
249
|
+
},
|
|
94
250
|
},
|
|
95
251
|
},
|
|
96
252
|
});
|
|
253
|
+
// ---------------------------------------------------------------------------
|
|
254
|
+
// Template components for composition and stateful stories.
|
|
255
|
+
// Named components avoid react-hooks lint issues — the react-hooks ESLint
|
|
256
|
+
// plugin is NOT configured in this project, so do NOT add eslint-disable.
|
|
257
|
+
// ---------------------------------------------------------------------------
|
|
97
258
|
function FixedHeightWidgetContainer({ children, height = 280, }) {
|
|
98
259
|
return (_jsx("div", { style: {
|
|
99
260
|
height,
|
|
@@ -102,39 +263,291 @@ function FixedHeightWidgetContainer({ children, height = 280, }) {
|
|
|
102
263
|
border: '1px dashed var(--color-grey-300)',
|
|
103
264
|
}, children: children }));
|
|
104
265
|
}
|
|
105
|
-
const
|
|
106
|
-
const
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
266
|
+
const InteractiveTemplate = () => {
|
|
267
|
+
const [lastClickedId, setLastClickedId] = useState(null);
|
|
268
|
+
const lastClickedLabel = lastClickedId
|
|
269
|
+
? initialItems.find(item => item.id === lastClickedId)?.eventPrimaryDefinition
|
|
270
|
+
: null;
|
|
271
|
+
return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 'var(--spacing-large)' }, children: [_jsx(WidgetCalendar, { heading: "Calendar", isFloating: true, items: initialItems, onItemClick: setLastClickedId }), _jsx("span", { className: "ds-text", style: { color: 'var(--color-grey-600)' }, children: lastClickedLabel
|
|
272
|
+
? (_jsxs(_Fragment, { children: ["Last clicked:", ' ', lastClickedLabel] }))
|
|
273
|
+
: 'Click an event to see its id captured here.' })] }));
|
|
274
|
+
};
|
|
275
|
+
const handleComposableItemClick = fn();
|
|
276
|
+
const ComposableTemplate = () => (_jsxs(Widget, { isFloating: true, children: [_jsx(Widget.Header, { children: "Calendar" }), _jsx(Widget.Body, { children: _jsx("ul", { className: "ds-widget-calendar__list", children: initialItems.slice(0, 2).map(item => (_jsx(WidgetCalendar.Item, { id: item.id, eventType: item.eventType, eventPrimaryTerm: item.eventPrimaryTerm, eventPrimaryDefinition: item.eventPrimaryDefinition, eventSecondaryTerm: item.eventSecondaryTerm, eventDateTime: item.eventDateTime, eventTimeLabel: item.eventTimeLabel, eventLocation: item.eventLocation, onItemClick: () => handleComposableItemClick(item.id) }, item.id))) }) })] }));
|
|
277
|
+
// ---------------------------------------------------------------------------
|
|
278
|
+
// Stories
|
|
279
|
+
// ---------------------------------------------------------------------------
|
|
280
|
+
export const Default = withDescription({
|
|
116
281
|
args: {
|
|
117
282
|
heading: 'Calendar',
|
|
283
|
+
headingHref: '/calendar',
|
|
118
284
|
isFloating: true,
|
|
119
285
|
items: initialItems,
|
|
120
286
|
onItemClick: fn(),
|
|
121
287
|
},
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
288
|
+
render: args => _jsx(WidgetCalendar, { ...args }),
|
|
289
|
+
}, [
|
|
290
|
+
'The interactive canvas — every prop is wired to the Controls panel below.',
|
|
291
|
+
'A linked header, floating style, a realistic mix of primary (green) and secondary (grey) event types,',
|
|
292
|
+
'and clickable rows. This is the only story where combining every prop together is the point.',
|
|
293
|
+
].join(' '));
|
|
294
|
+
export const Interactive = withDescription({
|
|
295
|
+
render: InteractiveTemplate,
|
|
296
|
+
parameters: {
|
|
297
|
+
controls: { disable: true },
|
|
298
|
+
docs: {
|
|
299
|
+
source: {
|
|
300
|
+
language: 'tsx',
|
|
301
|
+
code: `
|
|
302
|
+
import { useState } from 'react';
|
|
303
|
+
import { WidgetCalendar } from '@arbor-education/design-system.components';
|
|
304
|
+
|
|
305
|
+
function InteractiveCalendarExample() {
|
|
306
|
+
const [lastClickedId, setLastClickedId] = useState<string | null>(null);
|
|
307
|
+
|
|
308
|
+
return (
|
|
309
|
+
<>
|
|
310
|
+
<WidgetCalendar
|
|
311
|
+
heading="Calendar"
|
|
312
|
+
isFloating
|
|
313
|
+
items={items}
|
|
314
|
+
onItemClick={setLastClickedId}
|
|
315
|
+
/>
|
|
316
|
+
<span>Last clicked id: {lastClickedId ?? 'none yet'}</span>
|
|
317
|
+
</>
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
export default InteractiveCalendarExample;
|
|
321
|
+
`.trim(),
|
|
322
|
+
},
|
|
323
|
+
},
|
|
324
|
+
},
|
|
325
|
+
}, [
|
|
326
|
+
'Demonstrates `onItemClick(id)` behaviourally: clicking a row calls back with that item\'s `id`,',
|
|
327
|
+
'tracked here in a named template component\'s `useState` and displayed below the widget as',
|
|
328
|
+
'"Last clicked: {label}". In a real consumer, use the id to navigate or open a detail view.',
|
|
329
|
+
].join(' '));
|
|
330
|
+
export const PrimaryEventType = withDescription({
|
|
331
|
+
args: {
|
|
332
|
+
heading: 'Calendar',
|
|
333
|
+
items: [
|
|
334
|
+
{
|
|
335
|
+
id: 'year-10-10g',
|
|
336
|
+
eventType: 'primary',
|
|
337
|
+
eventPrimaryTerm: 'Class',
|
|
338
|
+
eventPrimaryDefinition: 'Year 10: 10G',
|
|
339
|
+
...classEventTime,
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
id: 'maths-year-10',
|
|
343
|
+
eventType: 'primary',
|
|
344
|
+
eventPrimaryTerm: 'Class',
|
|
345
|
+
eventPrimaryDefinition: 'Maths: Year 10: 10x/Ma3',
|
|
346
|
+
...classEventTime,
|
|
347
|
+
},
|
|
348
|
+
],
|
|
349
|
+
onItemClick: fn(),
|
|
350
|
+
},
|
|
351
|
+
render: args => _jsx(WidgetCalendar, { ...args }),
|
|
126
352
|
parameters: { controls: { disable: true } },
|
|
127
|
-
},
|
|
353
|
+
}, [
|
|
354
|
+
'Isolates `eventType="primary"` — the brand-green type bar. This is also the default when',
|
|
355
|
+
'`eventType` is omitted entirely.',
|
|
356
|
+
].join(' '));
|
|
357
|
+
export const SecondaryEventType = withDescription({
|
|
358
|
+
args: {
|
|
359
|
+
heading: 'Calendar',
|
|
360
|
+
items: [
|
|
361
|
+
{
|
|
362
|
+
id: 'class-generic',
|
|
363
|
+
eventType: 'secondary',
|
|
364
|
+
eventPrimaryTerm: 'Class',
|
|
365
|
+
eventPrimaryDefinition: 'Class',
|
|
366
|
+
...classEventTime,
|
|
367
|
+
},
|
|
368
|
+
{
|
|
369
|
+
id: 'class-generic-2',
|
|
370
|
+
eventType: 'secondary',
|
|
371
|
+
eventPrimaryTerm: 'Class',
|
|
372
|
+
eventPrimaryDefinition: 'Class',
|
|
373
|
+
eventDateTime: '2026-06-08T10:15/2026-06-08T11:00',
|
|
374
|
+
eventTimeLabel: '10:15 – 11:00',
|
|
375
|
+
eventLocation: 'Hall',
|
|
376
|
+
eventSecondaryTerm: 'Time and location',
|
|
377
|
+
},
|
|
378
|
+
],
|
|
379
|
+
onItemClick: fn(),
|
|
380
|
+
},
|
|
381
|
+
render: args => _jsx(WidgetCalendar, { ...args }),
|
|
382
|
+
parameters: { controls: { disable: true } },
|
|
383
|
+
}, 'Isolates `eventType="secondary"` — the neutral grey type bar, used for lower-priority or generic events.');
|
|
384
|
+
export const NonInteractiveItems = withDescription({
|
|
385
|
+
args: {
|
|
386
|
+
heading: 'Calendar',
|
|
387
|
+
items: [
|
|
388
|
+
{
|
|
389
|
+
id: 'year-10-10g',
|
|
390
|
+
eventType: 'primary',
|
|
391
|
+
eventPrimaryTerm: 'Class',
|
|
392
|
+
eventPrimaryDefinition: 'Year 10: 10G',
|
|
393
|
+
...classEventTime,
|
|
394
|
+
},
|
|
395
|
+
{
|
|
396
|
+
id: 'maths-year-10',
|
|
397
|
+
eventType: 'secondary',
|
|
398
|
+
eventPrimaryTerm: 'Class',
|
|
399
|
+
eventPrimaryDefinition: 'Maths: Year 10: 10x/Ma3',
|
|
400
|
+
...classEventTime,
|
|
401
|
+
},
|
|
402
|
+
],
|
|
403
|
+
},
|
|
404
|
+
render: args => _jsx(WidgetCalendar, { ...args }),
|
|
405
|
+
parameters: { controls: { disable: true } },
|
|
406
|
+
}, [
|
|
407
|
+
'No `onItemClick` is passed. Rows render as plain, non-interactive `<li>` elements —',
|
|
408
|
+
'no `role="button"`, no `tabIndex`, no hover chevron-to-arrow swap. The absence of interactivity',
|
|
409
|
+
'is exactly what this story demonstrates.',
|
|
410
|
+
].join(' '));
|
|
411
|
+
export const WithoutLocation = withDescription({
|
|
412
|
+
args: {
|
|
413
|
+
heading: 'Calendar',
|
|
414
|
+
items: [
|
|
415
|
+
{
|
|
416
|
+
id: 'staff-briefing',
|
|
417
|
+
eventType: 'primary',
|
|
418
|
+
eventPrimaryTerm: 'Meeting',
|
|
419
|
+
eventPrimaryDefinition: 'Staff briefing',
|
|
420
|
+
eventSecondaryTerm: 'Time',
|
|
421
|
+
eventDateTime: '2026-06-08T08:00/2026-06-08T08:15',
|
|
422
|
+
eventTimeLabel: '08:00 – 08:15',
|
|
423
|
+
},
|
|
424
|
+
],
|
|
425
|
+
onItemClick: fn(),
|
|
426
|
+
},
|
|
427
|
+
render: args => _jsx(WidgetCalendar, { ...args }),
|
|
428
|
+
parameters: { controls: { disable: true } },
|
|
429
|
+
}, [
|
|
430
|
+
'`eventLocation` is omitted and `eventSecondaryDefinition` is not set, so the secondary line',
|
|
431
|
+
'renders as time-only — no trailing comma fragment where the location would otherwise appear.',
|
|
432
|
+
].join(' '));
|
|
433
|
+
export const CustomSecondaryContent = withDescription({
|
|
434
|
+
args: {
|
|
435
|
+
heading: 'Calendar',
|
|
436
|
+
items: [
|
|
437
|
+
{
|
|
438
|
+
id: 'exam-window',
|
|
439
|
+
eventType: 'primary',
|
|
440
|
+
eventPrimaryTerm: 'Exam',
|
|
441
|
+
eventPrimaryDefinition: 'GCSE Maths: Paper 2',
|
|
442
|
+
eventSecondaryTerm: 'Details',
|
|
443
|
+
eventDateTime: '2026-06-08T09:00/2026-06-08T10:30',
|
|
444
|
+
eventTimeLabel: '09:00 – 10:30',
|
|
445
|
+
eventSecondaryDefinition: 'Silent conditions — no calculators permitted',
|
|
446
|
+
},
|
|
447
|
+
],
|
|
448
|
+
onItemClick: fn(),
|
|
449
|
+
},
|
|
450
|
+
render: args => _jsx(WidgetCalendar, { ...args }),
|
|
451
|
+
parameters: { controls: { disable: true } },
|
|
452
|
+
}, [
|
|
453
|
+
'`eventSecondaryDefinition` replaces the default time + location line entirely with custom content.',
|
|
454
|
+
'Note that `eventLocation` is deliberately not passed alongside it here — the two are an all-or-nothing',
|
|
455
|
+
'pair, and passing both would silently drop the location, which would be misleading to show in a story.',
|
|
456
|
+
].join(' '));
|
|
457
|
+
export const WithTooltip = withDescription({
|
|
458
|
+
args: {
|
|
459
|
+
heading: 'Calendar',
|
|
460
|
+
tooltip: 'Shows your next 5 scheduled events',
|
|
461
|
+
items: [
|
|
462
|
+
{
|
|
463
|
+
id: 'year-10-10g',
|
|
464
|
+
eventType: 'primary',
|
|
465
|
+
eventPrimaryTerm: 'Class',
|
|
466
|
+
eventPrimaryDefinition: 'Year 10: 10G',
|
|
467
|
+
...classEventTime,
|
|
468
|
+
},
|
|
469
|
+
],
|
|
470
|
+
onItemClick: fn(),
|
|
471
|
+
},
|
|
472
|
+
render: args => _jsx(WidgetCalendar, { ...args }),
|
|
473
|
+
parameters: { controls: { disable: true } },
|
|
474
|
+
}, 'Adding `tooltip` shows supplementary context alongside the heading. A single event keeps the focus on the tooltip.');
|
|
128
475
|
export const Empty = withDescription({
|
|
129
|
-
|
|
476
|
+
args: {
|
|
477
|
+
heading: 'Calendar',
|
|
478
|
+
items: [],
|
|
479
|
+
},
|
|
480
|
+
render: args => _jsx(WidgetCalendar, { ...args }),
|
|
481
|
+
parameters: { controls: { disable: true } },
|
|
482
|
+
}, 'An empty `items` array shows the default built-in EmptyState — a date icon, "Nothing scheduled", and "There are no upcoming events."');
|
|
483
|
+
export const CustomEmptyState = withDescription({
|
|
484
|
+
args: {
|
|
485
|
+
heading: 'Calendar',
|
|
486
|
+
items: [],
|
|
487
|
+
emptyState: (_jsx("div", { style: { padding: 'var(--spacing-large)', textAlign: 'center' }, children: _jsx("p", { className: "ds-text", style: { margin: 0, color: 'var(--color-grey-600)' }, children: "No events for the rest of term. Enjoy the break!" }) })),
|
|
488
|
+
},
|
|
489
|
+
render: args => _jsx(WidgetCalendar, { ...args }),
|
|
130
490
|
parameters: { controls: { disable: true } },
|
|
131
|
-
}, '
|
|
491
|
+
}, 'Overrides the default EmptyState with custom content via the `emptyState` prop when the default copy does not fit the context.');
|
|
132
492
|
export const FixedHeightOverflow = withDescription({
|
|
133
|
-
|
|
493
|
+
args: {
|
|
494
|
+
heading: 'Calendar',
|
|
495
|
+
items: overflowItems,
|
|
496
|
+
onItemClick: fn(),
|
|
497
|
+
},
|
|
498
|
+
render: args => (_jsx(FixedHeightWidgetContainer, { children: _jsx(WidgetCalendar, { ...args }) })),
|
|
134
499
|
parameters: { controls: { disable: true } },
|
|
135
|
-
},
|
|
500
|
+
}, [
|
|
501
|
+
'A parent container with a fixed 280px height constrains the widget — the header stays pinned while',
|
|
502
|
+
'the 14-item event list scrolls inside `Widget.Body`. The dashed border marks the constraining wrapper,',
|
|
503
|
+
'not part of the widget itself.',
|
|
504
|
+
].join(' '));
|
|
136
505
|
export const Composable = withDescription({
|
|
137
|
-
render:
|
|
138
|
-
parameters: {
|
|
139
|
-
|
|
506
|
+
render: ComposableTemplate,
|
|
507
|
+
parameters: {
|
|
508
|
+
controls: { disable: true },
|
|
509
|
+
docs: {
|
|
510
|
+
source: {
|
|
511
|
+
language: 'tsx',
|
|
512
|
+
code: `
|
|
513
|
+
import { Widget, WidgetCalendar } from '@arbor-education/design-system.components';
|
|
514
|
+
|
|
515
|
+
function ComposableCalendarExample() {
|
|
516
|
+
return (
|
|
517
|
+
<Widget isFloating>
|
|
518
|
+
<Widget.Header>Calendar</Widget.Header>
|
|
519
|
+
<Widget.Body>
|
|
520
|
+
<ul className="ds-widget-calendar__list">
|
|
521
|
+
{items.map(item => (
|
|
522
|
+
<WidgetCalendar.Item
|
|
523
|
+
key={item.id}
|
|
524
|
+
id={item.id}
|
|
525
|
+
eventType={item.eventType}
|
|
526
|
+
eventPrimaryTerm={item.eventPrimaryTerm}
|
|
527
|
+
eventPrimaryDefinition={item.eventPrimaryDefinition}
|
|
528
|
+
eventSecondaryTerm={item.eventSecondaryTerm}
|
|
529
|
+
eventDateTime={item.eventDateTime}
|
|
530
|
+
eventTimeLabel={item.eventTimeLabel}
|
|
531
|
+
eventLocation={item.eventLocation}
|
|
532
|
+
// WidgetCalendar.Item's onItemClick takes NO id argument, unlike
|
|
533
|
+
// WidgetCalendar's own onItemClick — close over the id yourself.
|
|
534
|
+
onItemClick={() => handleItemClick(item.id)}
|
|
535
|
+
/>
|
|
536
|
+
))}
|
|
537
|
+
</ul>
|
|
538
|
+
</Widget.Body>
|
|
539
|
+
</Widget>
|
|
540
|
+
);
|
|
541
|
+
}
|
|
542
|
+
export default ComposableCalendarExample;
|
|
543
|
+
`.trim(),
|
|
544
|
+
},
|
|
545
|
+
},
|
|
546
|
+
},
|
|
547
|
+
}, [
|
|
548
|
+
'Composes `Widget` and `WidgetCalendar.Item` manually — bypassing the `items` prop entirely — to prove',
|
|
549
|
+
'`WidgetCalendar.Item` is genuine public API for custom list logic. Note the closure: `WidgetCalendar.Item`\'s',
|
|
550
|
+
'own `onItemClick` takes no id argument, so each item must close over its own `id` (`() => handleClick(item.id)`),',
|
|
551
|
+
'unlike the parent `WidgetCalendar`\'s `onItemClick(id)`.',
|
|
552
|
+
].join(' '));
|
|
140
553
|
//# sourceMappingURL=WidgetCalendar.stories.js.map
|