@spark-web/design-system 5.1.3 → 5.1.5

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.
@@ -0,0 +1,309 @@
1
+ # Vendor admin — vendor-portal consumer overlay
2
+
3
+ This is the consumer overlay for the **vendor-portal** repo, per the Consumer
4
+ overlays convention in
5
+ `node_modules/@spark-web/design-system/patterns/CLAUDE.md`. Apply these
6
+ substitutions only when working in the vendor-portal repo. This file overrides
7
+ pattern files AND component-level docs ONLY where it explicitly says so (each
8
+ heading names what it overrides); every other surface and pattern rule applies
9
+ to vendor-portal unchanged.
10
+
11
+ ---
12
+
13
+ ## Pagination (overrides vendor-admin CLAUDE.md "List loading rule" — pagination control)
14
+
15
+ vendor-portal supplies its own `TablePagination` at
16
+ `src/components/TablePagination.tsx` — the consumer substitution for the surface
17
+ rules' "List loading rule" pagination control
18
+ (`// COMPONENT GAP: TablePagination needed — not yet in Spark`).
19
+
20
+ It wraps `rc-pagination` and renders a `Text` summary
21
+ `Show {dataShowing} of {total} Results` beside the page controls. Props used by
22
+ the patterns:
23
+
24
+ | Prop | Type | Notes |
25
+ | ------------- | -------------------------- | ------------------------------------------------ |
26
+ | `total` | `number` | Total record count — from a count query |
27
+ | `current` | `number` | Current page number |
28
+ | `pageSize` | `number` | Defaults to `DATA_PER_PAGE` (`@/util/constants`) |
29
+ | `dataShowing` | `number` | Number of rows on the current page |
30
+ | `onChange` | `(page, pageSize) => void` | Page change handler |
31
+
32
+ `TablePagination` returns `null` when `total` is falsy — never render an empty
33
+ pagination bar.
34
+
35
+ ---
36
+
37
+ ## Side panel (overrides vendor-admin CLAUDE.md "Detail interaction rule" — side panel implementation)
38
+
39
+ vendor-portal supplies its own `SidePanel` at `src/components/SidePanel.tsx` —
40
+ the consumer substitution for the surface rules' "Detail interaction rule"
41
+ drawer (`// COMPONENT GAP: SidePanel/Drawer needed — not yet in Spark`;
42
+ verified: the only overlay primitive published is `@spark-web/modal-dialog`, a
43
+ centered modal, not a right-anchored drawer).
44
+
45
+ It is an absolute-positioned right slide-in built on `@spark-web/stack` /
46
+ `@spark-web/box`, `90vw` wide on mobile up to `maxWidth: 500px` from tablet,
47
+ offset below the header (`top: 64`), with an optional `heading` + close
48
+ (`XIcon`) header row and a built-in `loading` skeleton state. Props:
49
+
50
+ | Prop | Type | Notes |
51
+ | ----------- | ------------ | --------------------------------------------------------------------------------- |
52
+ | `children` | `ReactNode` | Panel body |
53
+ | `heading` | `string` | Optional — header title (renders header only when `heading` AND `onHide` are set) |
54
+ | `onHide` | `() => void` | Optional — close handler; renders the close button |
55
+ | `loading` | `boolean` | Optional — shows the skeleton placeholder state |
56
+ | `fullWidth` | `boolean` | Optional — removes horizontal body padding |
57
+ | `...props` | `StackProps` | Spread onto the outer `Stack` |
58
+
59
+ ---
60
+
61
+ ## Multi-select filter (overrides list-page.md Section 4 filter usage — substitution note)
62
+
63
+ The canonical vendor-admin choice for a multi-select filter dropdown is
64
+ `@spark-web/multi-select` (verified current export: it exports `MultiSelect`,
65
+ not `MultiSelectField`). vendor-portal currently ships a local
66
+ `MultiselectCheckbox` at `src/components/MultiselectCheckbox.tsx`.
67
+
68
+ **Prefer `MultiSelect` from `@spark-web/multi-select`** for new work — the two
69
+ have effectively the same public API, so the substitution is direct:
70
+
71
+ | Prop | `MultiselectCheckbox` (local) | `@spark-web/multi-select` `MultiSelect` |
72
+ | ---------------- | ------------------------------------------------- | --------------------------------------- |
73
+ | `options` | `Options` (`Array<{ label, options: Option[] }>`) | same grouped shape |
74
+ | `onChange` | `(selected: SelectedOptions) => void` | same — grouped by group label |
75
+ | `placeholder` | `string` (defaults `"Filter By..."`) | same |
76
+ | `defaultOptions` | `DefaultOption[]` (`{ label, value }`) | `Option[]` — same shape |
77
+
78
+ Both group selections into `SelectedOptions` keyed by group label (e.g.
79
+ `{ Months: ["12", "24"] }` in `ProductSettings`). Because the shapes match, swap
80
+ the import and component name with no call-site changes. The local
81
+ `MultiselectCheckbox` is retained only where already wired; do not reach for it
82
+ in new code.
83
+
84
+ ---
85
+
86
+ ## Form fields (overrides @spark-web/field CLAUDE.md "controlled usage" — react-hook-form wrapper)
87
+
88
+ vendor-portal wraps `@spark-web/field` in a local `FormField` exported as
89
+ `Field` (`src/components/FormField.tsx`). It maps a react-hook-form
90
+ `errors[name]` entry to the field's error presentation:
91
+
92
+ ```tsx
93
+ import { Field } from '@/components/FormField';
94
+
95
+ <Field label="Email" name="email" errors={errors}>
96
+ <TextInput {...register('email')} />
97
+ </Field>;
98
+ ```
99
+
100
+ Wrapper signature: `FieldProps & { name: string; errors?: FieldErrors }`. When
101
+ `errors[name]` is present it surfaces `errors[name].message`; otherwise it falls
102
+ back to a passed `message`. Either way the wrapper sets `tone="critical"` — its
103
+ `(!!errors && errors[name]) || { message }` expression is always truthy. Note: a
104
+ static help `message` with no validation error still renders with critical tone
105
+ — a known quirk in the consumer wrapper. Wire forms with `react-hook-form` and
106
+ Zod schemas via `@hookform/resolvers` (`zodResolver`) — pass the resolver's
107
+ `errors` straight into `Field`. Prefer this `Field` wrapper over hand-wiring
108
+ `@spark-web/field` with manual error props on vendor-portal form pages.
109
+
110
+ ---
111
+
112
+ ## Section / setting wrapper (overrides detail/form section grouping)
113
+
114
+ vendor-portal uses a local `SettingsPanel`
115
+ (`src/components/settings/SettingsPanel.tsx`) for settings sections: a 2-column
116
+ `Columns` layout with a left `heading` (`Heading level="4"`) + `description`
117
+ (`Text`) and a right control slot (`children`).
118
+
119
+ ```tsx
120
+ import { SettingsPanel } from '@/components/settings/SettingsPanel';
121
+
122
+ <SettingsPanel heading="Terms" description="Enable the terms available...">
123
+ <MultiSelect options={termsOptionList} onChange={handleTermUpdates} />
124
+ </SettingsPanel>;
125
+ ```
126
+
127
+ | Prop | Type | Notes |
128
+ | ------------- | ----------------------- | ---------------------------- |
129
+ | `heading` | `string` | Required — left-column title |
130
+ | `description` | `string \| JSX.Element` | Required — left-column body |
131
+ | `children` | `ReactNode` | Right-column control slot |
132
+
133
+ **Relationship to `@spark-web/section-card`:** these are NOT interchangeable.
134
+ `SectionCard` is a bordered card with `header` / `footer` / `children` slots
135
+ (compose with `@spark-web/section-header`) — use it for record-detail section
136
+ **cards**. `SettingsPanel` is a label-beside-control settings row with no card
137
+ chrome. Canonically, prefer `@spark-web/section-card` for detail-page section
138
+ cards; keep `SettingsPanel` for the settings-page label/description/control
139
+ layout it was built for. Do not substitute one for the other.
140
+
141
+ ---
142
+
143
+ ## Feedback (overrides @spark-web/alert usage — local FlashMessage)
144
+
145
+ vendor-portal's `FlashMessage` (`src/components/settings/FlashMessage.tsx`) is a
146
+ thin wrapper over `@spark-web/alert`:
147
+
148
+ ```tsx
149
+ <Alert
150
+ tone={success ? 'positive' : 'critical'}
151
+ onClose={onClose}
152
+ closeLabel="Dismiss"
153
+ >
154
+ {message}
155
+ </Alert>
156
+ ```
157
+
158
+ It maps `{ success, message, onClose }` to an `Alert` with `tone="positive"`
159
+ (success) or `tone="critical"` (failure). The canonical feedback component is
160
+ `@spark-web/alert` (`node_modules/@spark-web/alert/CLAUDE.md`); `FlashMessage`
161
+ is just the local success/error binding. Prefer `Alert` directly for new
162
+ inline-feedback needs; reuse `FlashMessage` only where the success/error toggle
163
+ is convenient.
164
+
165
+ ---
166
+
167
+ ## Read-only display (overrides detail read-only display — local InfoBox to portal-table)
168
+
169
+ vendor-portal's profile page uses a local `InfoBox` (`src/pages/profile.tsx`)
170
+ that renders an icon + `label` + `value` row for read-only display. This is the
171
+ label/value display the canonical pattern fulfils with `@spark-web/portal-table`
172
+ (`PortalTable`), whose rows are `DescriptionListItem`s (`{ label, value }`,
173
+ where `value` may be a `ReactNode`). Prefer `@spark-web/portal-table` for
174
+ read-only label/value record displays; the local `InfoBox` is retained only for
175
+ its icon-decorated profile layout.
176
+
177
+ ---
178
+
179
+ ## Data conventions (vendor-portal data fetching)
180
+
181
+ vendor-portal is a Next.js Pages-Router app with two data layers:
182
+
183
+ - **Server-side GraphQL** via Apollo Client inside `getServerSideProps`. Create
184
+ the client with `createClient(session.access_token)` from
185
+ `@/lib/apollo-client` and run queries (`@/queries/*`) there; pass results to
186
+ the page as props.
187
+ - **Client-side** via `@tanstack/react-query`. Use `useInfiniteQuery` for
188
+ infinite-scroll lists (e.g. leads) and `useQuery` for bounded reads. Mutations
189
+ live in custom hooks under `src/hooks/**` that wrap `useMutation` (typically
190
+ `fetch`-ing a Next.js API route) and expose `mutate` / `mutateAsync`.
191
+
192
+ These conventions are consumer logic, not a design-system rule — but follow them
193
+ when wiring vendor-portal screens rather than introducing a new data layer.
194
+
195
+ ---
196
+
197
+ ## Legacy / embedded screens (overlay note — NOT a Layer-2 pattern)
198
+
199
+ Some vendor-portal routes do not build a Spark component tree at all:
200
+
201
+ - **Legacy iframe fallback** — `PortalIframe`
202
+ (`src/components/portal-iframe.tsx`) embeds the legacy portal in an
203
+ `<iframe>`. Used (often gated by a LaunchDarkly feature flag) on
204
+ `applications/[id]`, `leads/[id]`, `users`, `users/add`, and as a fallback on
205
+ `applications/index` (e.g. behind `vendorPortalOwnApplicationPage`).
206
+ - **Dynamic micro-frontend embed** — `referrals/add`
207
+ (`src/pages/referrals/add.tsx`) injects a remote `vendor_form` script and
208
+ mounts it into a container `div` (version pinned by a flag).
209
+
210
+ These screens have **no Layer-2 pattern**. Do not try to reconstruct them from a
211
+ pattern file. When working on them, follow the consumer's existing iframe /
212
+ micro-frontend convention (mount container, feature-flag gate, session token
213
+ plumbing) — they are intentionally outside the pattern system.
214
+
215
+ ---
216
+
217
+ ## Dashboard pieces (overrides dashboard.md Sections 1-3 placeholders — consumer substitutions)
218
+
219
+ vendor-portal supplies its own dashboard components in
220
+ `src/components/dashboard/**` and `src/components/**`. These are the consumer
221
+ substitutions for the placeholders the dashboard pattern flags as COMPONENT
222
+ GAPs. Use them in vendor-portal in place of the primitives placeholders; in new
223
+ code outside vendor-portal, the pattern's placeholders remain the stop-gap until
224
+ a Spark component exists.
225
+
226
+ ### Metrics — `DashboardMetrics` + `MetricCard` (overrides dashboard.md Section 1)
227
+
228
+ `DashboardMetrics` (`src/components/dashboard/DashboardMetrics.tsx`) renders a
229
+ `@spark-web/columns` `Columns` (`collapseBelow="wide"`) of local `MetricCard`s
230
+ (`src/components/MetricCard.tsx`). This is the consumer substitution for the
231
+ **MetricCard COMPONENT GAP** (dashboard.md Section 1).
232
+
233
+ `MetricCard` props:
234
+
235
+ | Prop | Type | Notes |
236
+ | ---------- | ------------ | -------------------------------------------------- |
237
+ | `header` | `string` | The headline number (the pattern's `value`) |
238
+ | `title` | `string` | The card label (the pattern's `label`) |
239
+ | `subTitle` | `string` | Optional secondary line (the pattern's `subtitle`) |
240
+ | `onClick` | `() => void` | Click-through (the pattern's `onClick`/`href`) |
241
+
242
+ `DashboardMetrics` props: `data` (`TMetricSummary[]`) and
243
+ `onMetricClick(metric)`. Note the local `MetricCard` renders the headline number
244
+ with a raw `style={{ fontSize: '34px' }}` — that inline `fontSize` is a known
245
+ deviation from the pattern (which mandates a `Text` size token); do not copy it
246
+ into new cards.
247
+
248
+ ### Resources — `DashboardAds` / `AdCard` + `DocumentsDialog` (overrides dashboard.md Section 2)
249
+
250
+ `DashboardAds` (`src/components/dashboard/DashBoardAds.tsx`) renders a `Columns`
251
+ of local `AdCard`s (`src/components/AdCard.tsx`) plus a `DocumentsDialog`. These
252
+ are the consumer's resource/promo cards — the pattern treats resource cards as
253
+ buildable from primitives (no gap flag), so these are a convenience factoring,
254
+ not a substitution for a missing Spark primitive.
255
+
256
+ `AdCard` props: `mediaSrc`, `mediaAlt`, `cardContent` (`ReactNode`),
257
+ `cardActions` (`ReactNode`). It composes `@spark-web/box` + `@spark-web/text` +
258
+ a `next/image` media region.
259
+
260
+ `DocumentsDialog` (`src/components/dashboard/DocumentsDialog.tsx`) is the
261
+ document-list modal — built **canonically** on `@spark-web/modal-dialog`
262
+ `ContentDialog` (`size="small"`, `showCloseButton={false}`) with a body of
263
+ `next/link` items. Props: `links` (`Array<{ title; link }>`), `isOpen`,
264
+ `onClose`. (Canonically the dashboard pattern uses `@spark-web/text-link`
265
+ `TextLink` for the link rows; the local dialog uses raw `next/link` — prefer
266
+ `TextLink` in new code.)
267
+
268
+ ### Announcements feed — `DashboardProductAnnouncements` (overrides dashboard.md Section 3)
269
+
270
+ `DashboardProductAnnouncements`
271
+ (`src/components/dashboard/DashboardProductAnnouncements.tsx`) is the consumer
272
+ substitution for the **AnnouncementsFeed COMPONENT GAP** (dashboard.md Section
273
+ 3). It renders the "What's New" timeline (article items built from
274
+ `@spark-web/box as="article"` / `@spark-web/stack` / `@spark-web/text`) and the
275
+ **"Load More"** `@spark-web/button` `Button` (button-triggered incremental
276
+ loading — the third list-loading mode), with a `@tanstack/react-query`
277
+ `useQuery` driving the incremental fetch on the `/dashboard/articles` page.
278
+ Props: `data` (the announcements array) and `apiUrl`. The feed is gated by the
279
+ `newsFeedVendorPortal` LaunchDarkly flag in `src/pages/dashboard/index.tsx`
280
+ (consumer gating). The connector-line / `next/image` chrome is consumer styling
281
+ applied via `@emotion/css` `css(...)` — outside vendor-portal, keep to the
282
+ pattern's semantic placeholder.
283
+
284
+ ### Consent modal — `ContentDialog` (no substitution)
285
+
286
+ The first-run consent modal in `src/pages/dashboard/index.tsx` uses
287
+ `@spark-web/modal-dialog` `ContentDialog` **canonically**
288
+ (`showCloseButton={false}`, a `Button tone="primary"` Continue footer that POSTs
289
+ `initialized: true` to `/api/user-profile`, gated on
290
+ `userProfile && !userProfile.initialized`). There is **no consumer
291
+ substitution** — build it exactly as the pattern's Section 4 describes.
292
+
293
+ ### Dashboard route gating
294
+
295
+ The whole dashboard is gated by the `vendorPortalOwnDashboard` LaunchDarkly
296
+ flag; when off, `src/pages/dashboard/index.tsx` falls back to a `PortalIframe`
297
+ (`users/dashboard`) — a legacy embed with **no Layer-2 pattern** (see "Legacy /
298
+ embedded screens" above). This flag gating is consumer logic, not a
299
+ design-system rule.
300
+
301
+ ---
302
+
303
+ ## No `@brighte/ui-components`
304
+
305
+ Unlike the portal-hub overlay, vendor-portal does **NOT** use
306
+ `@brighte/ui-components` — it is not a dependency (verified: absent from
307
+ `package.json`). Do not import from `@brighte/ui-components` in vendor-portal.
308
+ Filter and form fields come from `@spark-web/*` (and the local `Field` /
309
+ `MultiselectCheckbox` wrappers documented above), not from that package.
@@ -1,158 +0,0 @@
1
- # Layer 1 — Root CLAUDE.md
2
-
3
- ## What this layer is
4
-
5
- The root `CLAUDE.md` sits at the top of the repository. It is the first file an
6
- AI agent reads in any session. Its job is to orient the agent: what this repo
7
- is, how it is structured, and — most importantly — **what reading order to
8
- follow before writing any code**.
9
-
10
- Think of it as the agent's onboarding document. Without it, an agent has no
11
- contract to work from and will make assumptions. With it, every build task
12
- starts from the same controlled entry point.
13
-
14
- ---
15
-
16
- ## Where it lives
17
-
18
- ```
19
- /CLAUDE.md ← root of the repository
20
- ```
21
-
22
- ---
23
-
24
- ## What it must contain
25
-
26
- ### 1. Repo overview (2–4 sentences)
27
-
28
- A plain-language description of what the codebase is — the tech stack, the
29
- package structure, and the general purpose. This gives the agent enough context
30
- to interpret file paths and package names correctly.
31
-
32
- ```markdown
33
- ## Overview
34
-
35
- Spark Web is the Brighte Design System — a React component library organized as
36
- a Yarn monorepo (~50 packages). It uses Preconstruct for bundling, Emotion for
37
- CSS-in-JS, and Changesets for versioning.
38
- ```
39
-
40
- ### 2. Common commands
41
-
42
- Shell commands the agent can run for development, testing, building, and
43
- linting. These prevent the agent from guessing or inventing commands.
44
-
45
- ```markdown
46
- ## Common Commands
47
-
48
- yarn test:unit yarn check yarn build:packages
49
- ```
50
-
51
- ### 3. The build task reading order — the most critical section
52
-
53
- This is the mechanism that enforces layered reading. It is a numbered sequence
54
- of steps the agent **must** follow before writing any code. Each step references
55
- a specific file or layer. The agent is explicitly told never to skip steps.
56
-
57
- ```markdown
58
- ## How to approach any build task
59
-
60
- ### Step 1 — Classify the surface
61
-
62
- Read docs/patterns/CLAUDE.md in full. Determine which surface type the task is
63
- for based on language in the PRD.
64
-
65
- ### Step 2 — Read the surface rules
66
-
67
- Read the surface rules file for the classified surface.
68
-
69
- ### Step 3 — Identify the feature type and read the pattern file
70
-
71
- Match the task to a pattern file in the surface folder.
72
-
73
- ### Step 4 — Read the relevant component CLAUDE.md files
74
-
75
- Only read the components the pattern file tells you to use.
76
-
77
- ### Step 5 — Read the component stories
78
-
79
- Read the Storybook story file for each component you will use.
80
-
81
- ### Step 6 — Assemble, do not invent
82
-
83
- Use only components that exist in packages/. Do not build custom components.
84
-
85
- ### Step 7 — Validate before marking complete
86
-
87
- Run the validation checklist from the pattern file before marking the task
88
- complete.
89
- ```
90
-
91
- ### 4. Architecture section
92
-
93
- A description of the monorepo structure, dependency layers, and key patterns
94
- (styling, accessibility, TypeScript conventions). This prevents the agent from
95
- making incorrect assumptions about how components relate to each other.
96
-
97
- ### 5. New packages index
98
-
99
- A brief entry for each new or in-progress package — its purpose, its
100
- sub-components, and its key rules. This acts as a registry so the agent knows
101
- what exists without having to explore the filesystem.
102
-
103
- ```markdown
104
- ## New packages (in progress)
105
-
106
- ### table
107
-
108
- Composable table component. See packages/table/CLAUDE.md before writing any
109
- code. Sub-components: Table, TableHeaderRow, TableHeaderCell, TableRow,
110
- TableCell, TablePagination.
111
-
112
- ### status-badge
113
-
114
- Pill badge with a colored status dot and text label. See
115
- packages/status-badge/CLAUDE.md. Tones: positive, caution, critical, neutral,
116
- pending.
117
- ```
118
-
119
- ---
120
-
121
- ## How it connects to the other layers
122
-
123
- The root file does not define _how_ to use any component. It only tells the
124
- agent **what order to read things in**. Every rule lives in the layer it belongs
125
- to:
126
-
127
- | Concern | Lives in |
128
- | ---------------------------- | -------------------------------------- |
129
- | Surface classification logic | `docs/patterns/CLAUDE.md` |
130
- | Surface interaction rules | `docs/patterns/[surface]/CLAUDE.md` |
131
- | Feature assembly patterns | `docs/patterns/[surface]/[pattern].md` |
132
- | Component-specific rules | `packages/[component]/CLAUDE.md` |
133
-
134
- The root file is the table of contents. The other layers are the chapters.
135
-
136
- ---
137
-
138
- ## What happens if this layer is missing or incomplete
139
-
140
- - The agent skips directly to component documentation and assembles UI without
141
- understanding which surface it is building for.
142
- - Surface-level interaction rules (hover states, overflow menus, badge tones)
143
- are ignored.
144
- - The agent invents components or custom styles that do not exist in the design
145
- system.
146
-
147
- ---
148
-
149
- ## Implementation notes
150
-
151
- - Keep this file short. Its job is orientation and reading-order enforcement,
152
- not documentation.
153
- - The build task steps should be imperatives, not suggestions: "Read X before Y.
154
- Never skip steps."
155
- - The new packages index should stay in sync with `packages/` — add an entry
156
- here whenever a new package is scaffolded, even if it is not yet published.
157
- - Do not duplicate rules here that already live in a lower layer. If a rule
158
- belongs to a component, keep it in the component's CLAUDE.md.