@cmgfi/clear-ds 1.0.1 → 1.1.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/README.md ADDED
@@ -0,0 +1,478 @@
1
+ # @cmgfi/clear-ds
2
+
3
+ **Clear Design System** — CMG Financial's official React component library.
4
+
5
+ The UI foundation for CMG's internal loan origination platform. Built from scratch for pixel-fidelity control, token ownership, and zero dead code. Every component was designed in Pencil before it was written in code.
6
+
7
+ - **npm:** [`@cmgfi/clear-ds`](https://www.npmjs.com/package/@cmgfi/clear-ds)
8
+ - **Storybook:** [clear-pimkxt5rp-cmgprojects.vercel.app](https://clear-pimkxt5rp-cmgprojects.vercel.app)
9
+ - **Version:** 1.0.1
10
+ - **License:** UNLICENSED (CMG Financial internal)
11
+
12
+ ---
13
+
14
+ ## Table of Contents
15
+
16
+ 1. [Installation](#installation)
17
+ 2. [Consumer Setup](#consumer-setup)
18
+ 3. [Component Catalog](#component-catalog)
19
+ 4. [Token System](#token-system)
20
+ 5. [Tech Stack](#tech-stack)
21
+ 6. [Project Structure](#project-structure)
22
+ 7. [Local Development](#local-development)
23
+ 8. [Build Output](#build-output)
24
+ 9. [Publishing](#publishing)
25
+ 10. [Contributing](#contributing)
26
+ 11. [Hard-won Rules](#hard-won-rules)
27
+
28
+ ---
29
+
30
+ ## Installation
31
+
32
+ **Full install — with PrimeIcons (recommended):**
33
+
34
+ ```bash
35
+ npm install @cmgfi/clear-ds primeicons
36
+ ```
37
+
38
+ **Without PrimeIcons** — if your composition does not use any icon-bearing components:
39
+
40
+ ```bash
41
+ npm install @cmgfi/clear-ds
42
+ ```
43
+
44
+ > PrimeIcons is a `peerDependency`. It must be installed in the consumer app — it is not bundled into the library.
45
+
46
+ ---
47
+
48
+ ## Consumer Setup
49
+
50
+ Add these three imports once, in your application root (e.g. `main.tsx` or `App.tsx`):
51
+
52
+ ```tsx
53
+ import '@cmgfi/clear-ds/tokens'; // CSS custom property definitions (:root)
54
+ import '@cmgfi/clear-ds/styles'; // compiled component styles
55
+ import 'primeicons/primeicons.css'; // omit if PrimeIcons not installed
56
+ ```
57
+
58
+ Set the base font size. The token system is built on **`1rem = 12px`**:
59
+
60
+ ```css
61
+ /* global.css or index.css */
62
+ html {
63
+ font-size: 12px;
64
+ }
65
+ ```
66
+
67
+ Then use components anywhere in your app:
68
+
69
+ ```tsx
70
+ import { Button, InputText, Modal, DataTable } from '@cmgfi/clear-ds';
71
+ ```
72
+
73
+ ---
74
+
75
+ ## Component Catalog
76
+
77
+ 43 components across 7 sections. All are fully typed with JSDoc-annotated props, `React.forwardRef`-wrapped, and documented in Storybook.
78
+
79
+ ### Buttons
80
+
81
+ | Component | Description |
82
+ |---|---|
83
+ | `Button` | Primary action button. Variants: `primary`, `secondary`, `ghost`, `danger`. Sizes: `sm`, `md`, `lg`. |
84
+ | `IconButton` | Square button containing a single PrimeIcon. |
85
+ | `DropdownButton` | Button that opens a dropdown action menu. |
86
+ | `SplitButton` | Combined primary action + dropdown trigger in one control. |
87
+ | `LightningButton` | Quick-action button with a lightning icon affordance. |
88
+ | `CloseButton` | Compact `×` dismiss button used inside overlays and alerts. |
89
+
90
+ ### Form Controls
91
+
92
+ | Component | Description |
93
+ |---|---|
94
+ | `InputText` | Single-line text input with label, helper text, and validation states. |
95
+ | `TextArea` | Multi-line text input. |
96
+ | `Checkbox` | Controlled checkbox with label. Supports indeterminate state. |
97
+ | `RadioButton` | Single radio option. Compose multiples in a group. |
98
+ | `Select` | Single-select dropdown. |
99
+ | `MultiSelect` | Multi-select dropdown with chip display and search. |
100
+ | `ListBox` | Inline scrollable single or multi-select list. |
101
+ | `SelectButton` | Segmented button group for mutually exclusive options. |
102
+ | `DatePicker` | Date input with calendar popover. |
103
+ | `ToggleSwitch` | On/off toggle. |
104
+ | `FileUpload` | Drag-and-drop file upload with type validation. |
105
+
106
+ ### Data
107
+
108
+ | Component | Description |
109
+ |---|---|
110
+ | `DataTable` | Feature-rich table with sorting, filtering, row selection, grouping, inline editing, column resizing, and row expansion. |
111
+ | `Paginator` | Standalone pagination control for use with any data list. |
112
+ | `Picklist` | Dual-list transfer control for moving items between two sets. |
113
+
114
+ ### Messages
115
+
116
+ | Component | Description |
117
+ |---|---|
118
+ | `BannerAlert` | Full-width page-level alert. Severities: `info`, `success`, `warning`, `error`. |
119
+ | `InlineAlert` | Compact inline alert for form-level feedback. |
120
+ | `InlineContainedAlert` | Bordered inline alert for use inside panels or cards. |
121
+ | `Toast` / `Toaster` / `toast` | Programmatic toast notifications. Mount `<Toaster />` once; call `toast(options)` anywhere. |
122
+
123
+ ### Overlay
124
+
125
+ | Component | Description |
126
+ |---|---|
127
+ | `Modal` | Dialog overlay with header, body, and footer action slots. |
128
+ | `Drawer` | Slide-in panel overlay. |
129
+ | `SidePanel` / `SidePanelLayout` | Fixed side panel for persistent secondary content. |
130
+ | `Popup` | Anchored floating popup (e.g. contextual menus, micro-overlays). |
131
+ | `Tooltip` | Hover tooltip. Placements: `top`, `bottom`, `left`, `right`. |
132
+
133
+ ### Panel
134
+
135
+ | Component | Description |
136
+ |---|---|
137
+ | `Card` | Surface container with optional header and footer. |
138
+ | `Accordion` | Collapsible section list. Variants: `default`, `flush`. |
139
+ | `Tabs` | Horizontal tab navigation with panel content. |
140
+ | `BannerTabs` | Loan-workflow tab bar with status chips and badge groups. |
141
+
142
+ ### Status
143
+
144
+ | Component | Description |
145
+ |---|---|
146
+ | `SeverityChip` | Color-coded severity label. Variants: `info`, `success`, `warning`, `error`. |
147
+ | `MiscChip` | General-purpose label chip with configurable color. |
148
+ | `ProfileChip` | Avatar-style chip showing a person's initials or image. |
149
+ | `AUSChip` | Automated Underwriting System result chip. |
150
+ | `ProgressBar` | Horizontal progress indicator. |
151
+ | `ProgressSpinner` | Circular loading indicator. Large variant includes the Clear brand logo. |
152
+
153
+ ### Navigation
154
+
155
+ | Component | Description |
156
+ |---|---|
157
+ | `TopBar` | App-level top navigation bar with logo, nav items, user menu, and notifications. |
158
+ | `TopBarMobile` | Mobile variant of `TopBar` with drawer-based navigation. |
159
+ | `URLATabsNav` | URLA section tab navigation with applicant selector. Responsive: desktop, tablet, mobile variants. |
160
+ | `LoanBannerNav` | Loan detail page banner with action toolbar, condition badges, and collapsible sections. |
161
+ | `FullNav` / `FullNavMobile` | Full application navigation shell with primary and secondary nav areas. |
162
+
163
+ ---
164
+
165
+ ## Token System
166
+
167
+ All tokens are CSS custom properties defined in `src/tokens/tokens.css` and exported via `@cmgfi/clear-ds/tokens`.
168
+
169
+ **Base scale:** `1rem = 12px` — the consumer app must set `html { font-size: 12px }`.
170
+
171
+ ### Colors
172
+
173
+ Seven families, 10 shades each (`-50` lightest → `-900` darkest):
174
+
175
+ | Family | Prefix | Role |
176
+ |---|---|---|
177
+ | Teal | `--teal-*` | Brand color; primary actions, focus rings, active states |
178
+ | Surface | `--surface-*` | Neutral backgrounds; `50` = white, `900` = near-black |
179
+ | Navy | `--navy-*` | Text and borders |
180
+ | Yellow | `--yellow-*` | Warning severity |
181
+ | Blue | `--blue-*` | Informational severity |
182
+ | Green | `--green-*` | Success severity |
183
+ | Red | `--red-*` | Error severity |
184
+
185
+ Semantic aliases:
186
+
187
+ ```css
188
+ --color-text: var(--navy-800); /* primary body text */
189
+ --color-text-secondary: var(--navy-500); /* helper / secondary text */
190
+ ```
191
+
192
+ ### Spacing
193
+
194
+ Two complementary scales:
195
+
196
+ ```css
197
+ /* Numeric — direct pixel mapping */
198
+ --spacing-02: 2px; --spacing-04: 4px; --spacing-06: 6px;
199
+ --spacing-08: 8px; --spacing-12: 12px; --spacing-16: 16px;
200
+ --spacing-20: 20px; --spacing-24: 24px; --spacing-32: 32px;
201
+ --spacing-40: 40px;
202
+
203
+ /* Named aliases — semantic */
204
+ --spacing-xxxs: 2px; --spacing-xxs: 4px; --spacing-xs: 6px;
205
+ --spacing-sm: 8px; --spacing-md: 12px; --spacing-lg: 16px;
206
+ --spacing-xl: 24px; --spacing-xxl: 32px;
207
+ ```
208
+
209
+ ### Typography
210
+
211
+ Font family: **Open Sans** (`--font-family: 'Open Sans', sans-serif`)
212
+
213
+ Heading utility classes: `.text-h1` through `.text-h6`
214
+
215
+ Body utility classes — 4 sizes × 3 weights = 12 combinations:
216
+ ```
217
+ .text-xl-bold .text-xl-semibold .text-xl-regular
218
+ .text-large-bold .text-large-semibold .text-large-regular
219
+ .text-normal-bold .text-normal-semibold .text-normal-regular
220
+ .text-small-bold .text-small-semibold .text-small-regular
221
+ ```
222
+
223
+ ### Elevation
224
+
225
+ ```css
226
+ --shadow-depth-1: 0 1px 4px rgba(0,0,0,0.12);
227
+ --shadow-depth-2: 0 2px 8px rgba(0,0,0,0.16);
228
+ --shadow-depth-3: 0 4px 16px rgba(0,0,0,0.20);
229
+ --shadow-depth-4: 0 8px 32px rgba(0,0,0,0.24);
230
+ --scrim: /* navy-500 at 25% opacity */;
231
+ ```
232
+
233
+ ### Icons
234
+
235
+ ```css
236
+ --icon-size-sm: 12px;
237
+ --icon-size-md: 14px;
238
+ --icon-size-lg: 18px;
239
+ --icon-size-xl: 24px;
240
+ ```
241
+
242
+ Usage with PrimeIcons:
243
+ ```tsx
244
+ <i className="pi pi-check" style={{ fontSize: 'var(--icon-size-lg)' }} />
245
+ ```
246
+
247
+ ### Grid
248
+
249
+ ```css
250
+ /* Mobile — 4 column */
251
+ --grid-columns-mobile: 4; --grid-gutter-mobile: 16px;
252
+ --grid-margin-mobile: 24px; --grid-container-mobile: 576px;
253
+
254
+ /* Tablet — 6 column */
255
+ --grid-columns-tablet: 6; --grid-gutter-tablet: 16px;
256
+ --grid-margin-tablet: 48px; --grid-container-tablet: 768px;
257
+
258
+ /* Desktop — 12 column */
259
+ --grid-columns-desktop: 12; --grid-gutter-desktop: 18px;
260
+ --grid-margin-desktop: 128px; --grid-container-desktop: 1200px;
261
+
262
+ /* Breakpoints */
263
+ --breakpoint-sm: 576px; --breakpoint-md: 768px; --breakpoint-lg: 1200px;
264
+ ```
265
+
266
+ ---
267
+
268
+ ## Tech Stack
269
+
270
+ | Concern | Choice | Notes |
271
+ |---|---|---|
272
+ | Framework | React `>=17` | Peer dep — never bundled; consumer's React is used |
273
+ | Language | TypeScript 5 strict | Exported types are part of the public API |
274
+ | Build | Vite 5 library mode | ESM + CJS dual output; CSS Modules built-in |
275
+ | Type declarations | `vite-plugin-dts` `rollupTypes: true` | Single rolled-up `index.d.ts` |
276
+ | Styles | CSS Modules | Scoped, zero runtime overhead; token vars pass through without JS |
277
+ | Docs / QA | Storybook v8 `@storybook/react-vite` | Shares the same Vite config; no duplicate setup |
278
+ | Icons | PrimeIcons `>=7` | CSS font glyphs; `pi pi-*` class API; must be peer dep |
279
+ | Node / npm | 22 / 11 (LTS) | |
280
+
281
+ **Not present (intentional):**
282
+ - No CSS-in-JS — runtime overhead is unacceptable in a library
283
+ - No Tailwind — custom token system conflicts with Tailwind's class API
284
+ - No third-party component library — pixel-fidelity requires full control
285
+
286
+ ---
287
+
288
+ ## Project Structure
289
+
290
+ ```
291
+ clear-ds/
292
+ ├── src/
293
+ │ ├── index.ts ← explicit public API; all component + type exports
294
+ │ ├── tokens/
295
+ │ │ ├── tokens.css ← all CSS custom properties (428 lines)
296
+ │ │ ├── TokenDocs.module.css ← shared layout for token Storybook pages
297
+ │ │ ├── Colors.stories.tsx
298
+ │ │ ├── Typography.stories.tsx
299
+ │ │ ├── Spacing.stories.tsx
300
+ │ │ ├── Elevation.stories.tsx
301
+ │ │ ├── Icons.stories.tsx
302
+ │ │ └── Grid.stories.tsx
303
+ │ └── components/
304
+ │ └── ComponentName/
305
+ │ ├── ComponentName.tsx ← React.forwardRef; JSDoc props; displayName
306
+ │ ├── ComponentName.module.css← token vars only; no hardcoded values
307
+ │ ├── ComponentName.stories.tsx← autodocs; named states; AllStates last
308
+ │ └── index.ts ← re-exports component + all types
309
+ ├── .storybook/
310
+ │ ├── main.ts ← framework, addons, stories glob
311
+ │ └── preview.ts ← global CSS imports, backgrounds, storySort
312
+ ├── dist/ ← build output (not committed)
313
+ ├── vercel.json ← Storybook deployment config
314
+ ├── vite.config.ts
315
+ ├── tsconfig.json
316
+ └── package.json
317
+ ```
318
+
319
+ ---
320
+
321
+ ## Local Development
322
+
323
+ **Prerequisites:** Node 22, npm 11
324
+
325
+ ```bash
326
+ # Install dependencies
327
+ npm install
328
+
329
+ # Start Storybook dev server (localhost:6006)
330
+ npm run storybook
331
+
332
+ # Build the library (outputs to dist/)
333
+ npm run build
334
+
335
+ # Type-check without emitting
336
+ npm run type-check
337
+
338
+ # Watch mode for library build
339
+ npm run build:watch
340
+ ```
341
+
342
+ ### Adding a new component
343
+
344
+ Every component follows the same invariant structure — do not deviate:
345
+
346
+ 1. Create `src/components/ComponentName/` with four files:
347
+ - `ComponentName.tsx` — always `React.forwardRef`; props extend `React.HTMLAttributes<HTMLElement>`; every prop has a JSDoc comment; set `displayName`; spread `...props`; merge `className`
348
+ - `ComponentName.module.css` — token vars exclusively; no hardcoded hex or pixel values (except sub-pixel precision: `border: 1px`, `outline-offset: 2px`); use `:focus-visible` not `:focus`
349
+ - `ComponentName.stories.tsx` — `tags: ['autodocs']` on meta; individual state stories; `AllStates` is always the last export
350
+ - `index.ts` — re-export component and all types
351
+ 2. Add exports to `src/index.ts`
352
+ 3. Add the component to `storySort.order` in `.storybook/preview.ts`
353
+
354
+ ### Storybook conventions
355
+
356
+ - **Sidebar order:** `Best Practices → Tokens → Components (Buttons, Data, Form Controls, Messages, Overlay, Panel, Status) → Navigation`
357
+ - `tags: ['autodocs']` is mandatory on every `meta` — without it there is no Docs tab
358
+ - `AllStates` (or `AllSizes` / `AllSeverities`) must always be the last story export — Storybook renders in declaration order
359
+ - When a story `render:` function needs `useState`, define a named inner component and call the hook inside it (hooks cannot be called inside `render` directly)
360
+
361
+ ---
362
+
363
+ ## Build Output
364
+
365
+ ```
366
+ dist/
367
+ ├── index.mjs ← ESM bundle (tree-shakeable), 230 KB
368
+ ├── index.cjs ← CommonJS bundle, 153 KB
369
+ ├── index.css ← all compiled component styles, 126 KB
370
+ ├── index.d.ts ← single rolled-up TypeScript declarations, 81 KB
371
+ ├── index.mjs.map ← ESM source map
372
+ ├── index.cjs.map ← CJS source map
373
+ └── tokens/
374
+ └── tokens.css ← design tokens (separate import path), 14 KB
375
+ ```
376
+
377
+ **Vite config highlights:**
378
+ - `cssCodeSplit: false` — all CSS Modules compile into a single `index.css`
379
+ - `external: ['react', 'react/jsx-runtime', 'react-dom']` — React is never bundled
380
+ - `rollupTypes: true` — single `index.d.ts` instead of one file per source file
381
+ - `assetFileNames: 'index.css'` — predictable output filename
382
+ - `postbuild` npm script — copies `src/tokens/tokens.css` → `dist/tokens/tokens.css`
383
+
384
+ ---
385
+
386
+ ## Publishing
387
+
388
+ ### npm
389
+
390
+ Auth token must be in `~/.npmrc` (never in the project `.npmrc`):
391
+ ```
392
+ //registry.npmjs.org/:_authToken=<automation-token>
393
+ ```
394
+
395
+ Use an **automation token** — it bypasses 2FA which is required for unattended publishes.
396
+
397
+ ```bash
398
+ # Bump version in package.json, then:
399
+ npm publish
400
+ # prepublishOnly runs `npm run build` automatically
401
+ ```
402
+
403
+ The package publishes to `@cmgfi/clear-ds` on public npm under the `andyg-cmg-xd` account.
404
+
405
+ ### Storybook (Vercel)
406
+
407
+ The Storybook is hosted on Vercel under the `cmgprojects` team.
408
+
409
+ ```bash
410
+ # First deploy (no .vercel/ present):
411
+ vercel --prod --yes --scope cmgprojects
412
+
413
+ # Subsequent deploys:
414
+ vercel --prod --yes
415
+
416
+ # Disable SSO protection after first deploy (new projects inherit team SSO):
417
+ TOKEN=$(cat ~/Library/Application\ Support/com.vercel.cli/auth.json | python3 -c "import sys,json; print(json.load(sys.stdin)['token'])")
418
+ curl -s -X PATCH "https://api.vercel.com/v9/projects/clear-ds?teamId=team_fm7yrQmYpugXC0N7tiglM4Bw" \
419
+ -H "Authorization: Bearer $TOKEN" \
420
+ -H "Content-Type: application/json" \
421
+ -d '{"ssoProtection": null}'
422
+ ```
423
+
424
+ `vercel.json` configures the Storybook build:
425
+ ```json
426
+ {
427
+ "buildCommand": "npm run build-storybook",
428
+ "outputDirectory": "storybook-static"
429
+ }
430
+ ```
431
+
432
+ ---
433
+
434
+ ## Contributing
435
+
436
+ ### Git workflow
437
+
438
+ - Remote: `https://cmg-financial.ghe.com/andyg/clear-ds.git`
439
+ - Branch from `main`, PR back to `main`
440
+ - Commit messages follow conventional commits (`feat:`, `fix:`, `chore:`, `docs:`)
441
+
442
+ ### Before submitting a PR
443
+
444
+ - [ ] `npm run type-check` passes with zero errors
445
+ - [ ] `npm run build` completes successfully
446
+ - [ ] New component has all four required files
447
+ - [ ] `tags: ['autodocs']` is on the story meta
448
+ - [ ] `AllStates` is the last story export
449
+ - [ ] No hardcoded colors or spacing values — token vars only
450
+ - [ ] `src/index.ts` exports the new component and all its types
451
+ - [ ] Component added to `storySort.order` in `.storybook/preview.ts`
452
+
453
+ ---
454
+
455
+ ## Hard-won Rules
456
+
457
+ These are not guidelines — they are invariants derived from real failures during the build of this library:
458
+
459
+ | Rule | Why |
460
+ |---|---|
461
+ | `tags: ['autodocs']` on every story meta | Without it there is no Docs tab. Retroactively adding it across many files is painful. Add it when the file is created. |
462
+ | `AllStates` always last story export | Storybook renders in declaration order. A misplaced overview story at the top pollutes the component's story list. |
463
+ | `primeicons` in `peerDependencies`, not `devDependencies` | If it is only in `devDeps`, it will not be available in consumer bundles — icon glyphs silently disappear. |
464
+ | `publishConfig: { "access": "public" }` is required | Scoped packages are private by default on npm. Without this field, `npm publish` is rejected even with a valid token. |
465
+ | Never hardcode colors or spacing | Always use token vars. Hardcoded values break theming and make design drift invisible. |
466
+ | `React.forwardRef` on every component | Consumers need ref access for focus management, animations, and third-party integrations. |
467
+ | `:focus-visible` not `:focus` | `:focus` shows focus rings on mouse clicks. `:focus-visible` restricts them to keyboard navigation. |
468
+ | `min-width: 0` on shrinkable flex children | `flex: 1` alone does not allow an item to shrink below its content size. Without `min-width: 0`, the item overflows its container. |
469
+ | Merge consumer `className`, never replace | Spreading `...props` is not enough. Explicitly merge: `className={[styles.root, props.className].filter(Boolean).join(' ')}`. |
470
+ | The `postbuild` script must stay in `package.json` | It copies `src/tokens/tokens.css` → `dist/tokens/tokens.css`. Without it, the `@cmgfi/clear-ds/tokens` import path resolves to a file that does not exist. |
471
+
472
+ ---
473
+
474
+ ## Design Source
475
+
476
+ All visual decisions originate in **Pencil** — a design tool integrated as an MCP server. The `.pen` source files are in the project experiments directory alongside this package. The code is a direct translation of those designs: same structure, same hierarchy, same property names.
477
+
478
+ The token master copy lives at `../clear-ds-tokens.css` (one level above this package). When updating tokens, sync that file into `src/tokens/tokens.css`.