@vectramindnpm/unified-design-system 0.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,255 @@
1
+ # unified-design-system
2
+
3
+ Standalone, publishable UI component library extracted from Firstpass-Neo's
4
+ `src/designSystem` (design tokens) and `src/components/ui` (29 components), with the
5
+ app-specific couplings removed so it can be installed as a plain npm dependency in any
6
+ React app — and, later, browsed in its own Storybook.
7
+
8
+ ## What's here
9
+
10
+ ```
11
+ src/
12
+ tokens/ ← designTokens.js → semanticTokens.js → componentTokens.js (+ index.js barrel)
13
+ lib/utils.ts ← cn() (clsx + tailwind-merge)
14
+ types/view-mode.ts ← local EViewMode enum (stand-in for the app's models/utilitiesModel)
15
+ components/ui/ ← all 29 components — implementation only (.tsx/.ts/.css), no stories
16
+ stories/ ← one *.stories.tsx per component, kept separate from components/ui/
17
+ styles/
18
+ tokens.css ← design-token CSS custom properties (from the app's variables.css)
19
+ shadcn-base.css ← the shadcn-style base HSL vars a few components use
20
+ theme.css ← Tailwind v4 `@theme inline` bridge (auto-generated, see below)
21
+ components.css ← custom classNames the components render beyond tokens/Tailwind
22
+ icon-font.css ← the `fp-neo` icon font (icon-neo-*) — see below
23
+ fonts.css ← Inter variable-font @font-face
24
+ fonts/ ← the actual font files (fp-neo.*, Inter .woff2)
25
+ index.css ← styles.css entry: imports all of the above
26
+ index.ts ← the package's public barrel export
27
+ tailwind-tokens.cjs ← flat Tailwind v3 color/radius/spacing map (from the app's tailwind.tokens.cjs)
28
+ tailwind-preset.js ← Tailwind v3 preset for consumers still on Tailwind v3
29
+ ```
30
+
31
+ The Rsbuild app in the rest of this repo (`src/App.tsx`, `.storybook/`) is a local dev
32
+ harness / smoke test for the same `src/` — it is **not** what gets published.
33
+
34
+ ## What changed vs. the original app code
35
+
36
+ Per [docs/design-system-npm-storybook-guide.md](../../../GITHUB/Firstpass-Neo/docs/design-system-npm-storybook-guide.md)'s
37
+ Phase 1 decoupling, plus a few couplings that guide's scan didn't catch:
38
+
39
+ - **`semanticTokens`/`designTokens`/`componentTokens`** — every `ui/*` file that imported
40
+ these through `../../helpers/helpersIndex` now imports directly from `../../tokens`.
41
+ - **i18n removed entirely** (per explicit request, not just made optional) — `icon.tsx`,
42
+ `buttonIcon.tsx`, `delete-action.tsx` no longer import `react-i18next` or
43
+ `getTranslationKey`. Tooltip/aria labels fall back to a **static string** (the icon
44
+ `name`, or a hardcoded label like `"Delete"`/`"Refresh"`) instead of a translation
45
+ lookup. If you need i18n later, wrap the exported components in your own thin
46
+ wrapper that passes a translated `tooltipText` prop — every component that shows a
47
+ label already accepts one.
48
+ - **`view-toggle.tsx`** — dropped `getSessionStateKey`/`getViewType` (app's
49
+ `internaljscontrols.ts`) and `useSelector` (Redux). `ViewToggle` is now a controlled
50
+ component (`viewMode` + `onViewModeChange` props); `useViewMode()` is a plain
51
+ `useState` + `localStorage` hook with no Redux/app dependency.
52
+ - **`truncated-text.tsx`** — same Redux/session coupling removed; takes a `viewMode` prop
53
+ instead of reading it from the app's store.
54
+ - **`toast.tsx`** — the `Toast` component itself was already prop-driven. Its
55
+ `useToast()` hook (which dispatched into the app's own Redux alert store) was
56
+ app-specific state wiring, not a reusable UI primitive, so it's not included — drive
57
+ `<Toast onClose={...} />` from your own state instead.
58
+ - **`loader.tsx`** — dropped the `useSelector` read of the app's `Layout.loading` Redux
59
+ state; `Loader` now just uses its own `isLoading` prop. The loading GIF is bundled
60
+ into the package (inlined as a data URI at build time) instead of importing from the
61
+ app's `src/icons/`.
62
+ - **`time-picker.scss` → `time-picker.css`** — the original Sass file was nested under
63
+ the app's `#fpneo` Tailwind-important selector (would never match elsewhere) and used
64
+ Sass-only nesting. Flattened to plain, framework-agnostic CSS with its `@apply`
65
+ utilities inlined as literal styles, so it needs no Sass or Tailwind build step at all.
66
+ - **`models/utilitiesModel.EViewMode`** → copied locally as `types/view-mode.ts` (it was
67
+ a two-value enum with no other app dependency).
68
+ - A couple of components (`dropdown-menu.tsx`, `popover.tsx`, `tooltip.tsx`,
69
+ `expandable-search.tsx`, `table.tsx`) needed small, behavior-preserving TypeScript
70
+ fixes (a stricter `useRef`/`RefObject` generic, a `type`-only import,
71
+ `JSX.Element` → `React.JSX.Element`) to type-check as an isolated package — same
72
+ runtime behavior, just satisfies a standalone `tsup` build.
73
+
74
+ **Untouched:** all component markup, class names, and visual variants — this was a
75
+ decoupling pass, not a redesign.
76
+
77
+ ### Component-level styles (beyond tokens)
78
+
79
+ Several components render classNames that are neither Tailwind utilities nor covered by
80
+ `tokens.css`/`shadcn-base.css` — plain custom classes like `.app-card`, `.switch-pill`,
81
+ `.view-toggle__btn`, `.dropdownHead`, `.item-table__head`, `.fp-loader-overlay`, and the
82
+ `icon-neo-*` icon font every Icon/ButtonIcon/DeleteAction/Toast/TimePicker glyph depends
83
+ on. These lived in the source app's `src/styles/common.scss`, `mainstyles.css`, and
84
+ `font-icons.css` — SCSS nested under that app's `#fpneo` Tailwind-important wrapper, with
85
+ `@apply` directives, so they'd neither match nor build here as-is.
86
+
87
+ - **`src/styles/components.css`** — every one of those classes, flattened to plain CSS
88
+ (`@apply` resolved to literal declarations, `#fpneo`/SCSS nesting stripped). Page- and
89
+ business-specific rules were left out on purpose: Table's per-page `*-cols` grid-column
90
+ layouts (`.flows-cols`, `.appt-cols`, ...) are data-shape-specific, not reusable design
91
+ system material — bring your own to match your own columns. (Two of `table.tsx`'s own
92
+ variant keys, `users` → `.users-cols` and `devices` → `.devices-cols`, turned out to
93
+ have no corresponding CSS anywhere in the source app either — likely dead/mismatched
94
+ keys there already, not something introduced here.)
95
+ - **`src/styles/icon-font.css`** + **`src/styles/fonts.css`** — the `fp-neo` icon font
96
+ (`icon-neo-*`) and Inter variable font `@font-face` rules, copied verbatim (already
97
+ plain CSS) with only their `url()` paths adjusted to this package's own
98
+ `src/styles/fonts/` copy of the four `fp-neo.*` files and the Inter `.woff2`.
99
+ - All of the above are wired into `src/styles/index.css` (and so into `styles.css`),
100
+ alongside `tokens.css`/`shadcn-base.css`/`theme.css` — importing that one file is
101
+ enough. Each is also independently `exports`-mapped (`./components.css`,
102
+ `./icon-font.css`, `./fonts.css`) if you'd rather cherry-pick.
103
+
104
+ Verified via `npm run build-storybook` and `npm run build` (the demo app): Rsbuild/Rspack's
105
+ asset pipeline picks up and correctly hashes all five font files from the `url()`
106
+ references in this CSS — proof the paths resolve. (No headless browser was available in
107
+ this environment to capture an actual rendered screenshot; that's the one thing not
108
+ independently confirmed visually.)
109
+
110
+ ### Button variant redesign
111
+
112
+ `Button` originally had **19 `variant` values** conflating four unrelated things: color/
113
+ emphasis, shape (pill vs. rounded), fixed footer sizing, and one-off page-specific skins.
114
+ Redesigned into three independent, composable axes instead:
115
+
116
+ ```tsx
117
+ variant: "primary" | "secondary" | "destructive" | "outline" | "ghost" | "link" // 6
118
+ size: "default" | "sm" | "lg" | "icon" | "iconLg" // unchanged
119
+ shape?: "default" | "pill" // NEW
120
+ selected?: boolean // unchanged
121
+ ```
122
+
123
+ Nothing else in this package used any variant beyond `"ghost"` internally (checked before
124
+ making this change), and the package has no external consumers yet, so this was a clean
125
+ breaking change with nothing to migrate. Mapping from the old 19 to the new API:
126
+
127
+ | Old `variant` | New equivalent | Note |
128
+ |---|---|---|
129
+ | `default` (purple) **and** `primary` (blue) | `variant="primary"` | Consolidated to one color — the token-driven `interactive-brand` (purple), not the hardcoded blue. If you specifically need the old blue, add `className="bg-[var(--color-brand-blue)]"`. |
130
+ | `secondary`, `destructive`, `outline`, `ghost` | same name, unchanged | No visual change. |
131
+ | `pill` | `shape="pill"` (with any `variant`) | Combine, e.g. `variant="outline" shape="pill"`. |
132
+ | `borderPrimary` | `variant="outline"` | Brand-colored border was a one-off tint — add `className="border-interactive-brand text-interactive-brand"` to restore it exactly. |
133
+ | `refreshIcon` | **removed** — use `<ButtonIcon variant="refresh" />` | That's already `ButtonIcon`'s job; `Button` doesn't need to duplicate it. |
134
+ | `selectedIcon` | **removed** — use `<ButtonIcon variant="filled" />` or `<Button variant="primary" size="icon">` | Same reasoning. |
135
+ | `lang` | `variant="ghost"` + `className` | One-off page skin, not a reusable color. |
136
+ | `submenu` | `variant="outline" shape="pill" size="sm"` + `className` | Same. |
137
+ | `deptedit` | `variant="ghost"` + `className="text-interactive-brand hover:bg-[var(--color-ext-eef2ff)]"` | Also drops the legacy `dept-edit-btn` className-sniffing hack entirely. |
138
+ | `cancel` | `variant="outline"` + `className="h-9 px-5 border-[var(--color-button-cancel)] text-[var(--color-button-cancel)]"` | Footer trio: fixed sizing + color both become explicit `className`, since they're page-context, not a semantic color. |
139
+ | `save` | `variant="primary"` + `className="h-9 px-5"` | |
140
+ | `close` | `variant="secondary"` + `className="h-9 px-5"` | |
141
+ | `formButtonSubmit` | `variant="primary" shape="pill"` + `className="h-9 px-6"` | |
142
+ | `formButtonCancel` | `variant="outline" shape="pill"` + `className` for the soft-pink tone | |
143
+ | `formButtonBack` | `variant="outline" shape="pill"` + `className="h-9 px-6"` | |
144
+
145
+ `link` is genuinely new (no old equivalent) — a text-only, underline-on-hover variant with
146
+ no padding/height, the one common pattern the original 19 never actually had.
147
+
148
+ ## Using it in this repo right now
149
+
150
+ ```tsx
151
+ import { Button, Badge, Card } from './index'; // see src/App.tsx for a working example
152
+ ```
153
+
154
+ ## Building the package
155
+
156
+ ```bash
157
+ npm run build:lib # tsup → dist/{index.cjs,index.mjs,index.d.ts} + dist/*.css
158
+ ```
159
+
160
+ This has been verified end-to-end: `npm run build:lib` succeeds, `npm pack` produces a
161
+ clean ~390 KB tarball, and installing that tarball into a scratch project and
162
+ `require()`-ing it resolves all exports correctly (with `react`, `react-dom`, and
163
+ `formik` installed as peers).
164
+
165
+ ## Installing it in another app
166
+
167
+ ```bash
168
+ npm install /path/to/unified-design-system-0.1.0.tgz # or from your registry once published
169
+ ```
170
+
171
+ ```tsx
172
+ import { Button, Icon, Table } from 'unified-design-system';
173
+ ```
174
+
175
+ Peer dependencies you must have installed: `react`, `react-dom`, and `formik` (4 of the
176
+ 29 components — `FormField`, `Input` with `as="field"`, `Textarea` with `as="field"`,
177
+ `ScrollToFieldError` — call real Formik APIs at runtime, so it's a required peer, not
178
+ optional).
179
+
180
+ **Styling** — pick one based on your Tailwind version:
181
+
182
+ - **Tailwind v4**: `@import "unified-design-system/styles.css";` in your global CSS,
183
+ alongside your own `@import "tailwindcss";`. This one import already includes the icon
184
+ font, component classes, and tokens.
185
+ - **Tailwind v3**: add `presets: [require("unified-design-system/tailwind-preset")]` to
186
+ your `tailwind.config.js`, and `@import "unified-design-system/tokens.css";` +
187
+ `@import "unified-design-system/shadcn-base.css";` + `@import "unified-design-system/components.css";`
188
+ + `@import "unified-design-system/icon-font.css";` + `@import "unified-design-system/fonts.css";`
189
+ in your global CSS.
190
+ - **No Tailwind**: same four `@import`s as above (skip `tokens.css`/`shadcn-base.css`
191
+ only if you don't need the CSS-variable-driven components) — most components read CSS
192
+ custom properties (`var(--color-...)`) rather than Tailwind utility classes.
193
+
194
+ Any icon (`<Icon name="icon-neo-trash" />`, etc.) needs `icon-font.css` loaded — without
195
+ it the glyph classes resolve to nothing and icons render blank.
196
+
197
+ ## Publishing (once you're ready)
198
+
199
+ 1. `npm run build:lib`
200
+ 2. `npm version patch|minor|major`
201
+ 3. `npm publish` (add `--registry` for a private/internal registry)
202
+ 4. Tag the release in git.
203
+
204
+ This package has no consumers yet, so no versioning discipline is enforced — once
205
+ something depends on it, breaking changes need a major bump.
206
+
207
+ ## Storybook
208
+
209
+ Set up via the official installer (`npm create storybook@latest`, framework
210
+ auto-detected as `react-rsbuild`), which added `@storybook/addon-a11y` and
211
+ `@storybook/addon-docs` to `.storybook/main.ts` and to `devDependencies`.
212
+
213
+ The generic Storybook onboarding tutorial (the original `src/stories/` — its demo
214
+ Button/Header/Page components, their `.css`, `Configure.mdx`, and the screenshot/icon
215
+ images under `assets/`) was removed entirely; it wasn't part of this design system. That
216
+ same `src/stories/` path has since been repurposed to hold this design system's own
217
+ stories instead (see below) — a fresh, unrelated folder with the same name.
218
+
219
+ **Every one of the 29 components has its own `*.stories.tsx` in `src/stories/`** — kept
220
+ in a separate folder from `src/components/ui/` (which holds only `.tsx`/`.ts`/`.css`
221
+ implementation files, no stories), and importing from there
222
+ (`import { Button } from "../components/ui/button"`). `.storybook/main.ts`'s
223
+ `../src/**/*.stories.tsx` glob picks them up regardless of where under `src/` they live,
224
+ so this split needed no config change. Each story has a `Default` plus variant/state
225
+ stories and, for the compound components (Dialog, DropdownMenu, Popover, Tooltip, Table,
226
+ AppList, TimePicker, Toast, ViewToggle, ...), a composed working example; components with
227
+ a real `cva` variant/size enum (Button, Badge, Icon, ButtonIcon, Checkbox, RadioButton,
228
+ Tab) also get an `AllVariants`/`AllColors`/`AllSizes`-style story enumerating every value
229
+ from their actual source, cross-checked against it rather than sampled. Every meta also
230
+ carries `tags: ["autodocs"]`, a one-line `parameters.docs.description.component`
231
+ explaining what the component is/isn't (e.g. Icon vs. ButtonIcon, Popover/Tooltip/DropdownMenu
232
+ being hand-built rather than Radix-based), and a `layout` parameter (`"centered"` for
233
+ atomic controls, `"padded"` for larger/compound ones) so each renders sensibly on its own
234
+ Docs page.
235
+
236
+ One correctness fix worth knowing about: `.storybook/preview.tsx` originally imported
237
+ `src/styles/index.css` directly, which never triggers Tailwind itself — same as
238
+ `src/App.css` needing its own `@import 'tailwindcss';` alongside the design-system
239
+ styles. Without it, Storybook's build silently produced zero Tailwind-generated utility
240
+ classes (`bg-interactive-brand`, `rounded-md`, `inline-flex`, ...), so every
241
+ utility-class-driven style rendered as if unstyled even though the underlying CSS
242
+ variables were present. Fixed via `.storybook/preview.css` (`@import 'tailwindcss';`
243
+ + the design-system styles) — verified by diffing Storybook's compiled CSS against the
244
+ demo app's byte-for-byte, not just a clean build.
245
+
246
+ Verified working: `npm run build-storybook` produces a static build with exactly 29
247
+ autodocs pages (one per component) alongside 106 individual story variants — confirmed by
248
+ inspecting the build's `index.json`, not just a successful exit code. `npm run storybook`
249
+ boots and serves at `http://localhost:6006/`. `npm run lint` reports zero errors across
250
+ the story files.
251
+
252
+ ```bash
253
+ npm run storybook # dev server
254
+ npm run build-storybook # static build, e.g. for Chromatic/GitHub Pages
255
+ ```
@@ -0,0 +1,387 @@
1
+ /* Component-specific styles these components render classNames for, beyond Tailwind
2
+ utilities and design tokens. Ported from the source app's src/styles/common.scss and
3
+ src/styles/mainstyles.css — originally SCSS nested under an app-specific `#fpneo {}`
4
+ Tailwind-important wrapper (would never match here) with Tailwind `@apply` directives.
5
+ Flattened to plain CSS with `@apply` resolved to literal declarations so this file has
6
+ zero build-step dependency on Sass or Tailwind. Page/business-specific styling (e.g.
7
+ Table's per-page `*-cols` grid-column layouts, and page overrides in clinical.scss) is
8
+ intentionally NOT included — bring your own for those. */
9
+
10
+ /* ── Card (card.tsx) ─────────────────────────────────────────────────────── */
11
+ .app-card {
12
+ background-color: #ffffff;
13
+ border-radius: 1.25rem;
14
+ border: 1px solid #f3f4f6;
15
+ box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
16
+ transition-property: box-shadow;
17
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
18
+ transition-duration: 150ms;
19
+ display: flex;
20
+ flex-direction: column;
21
+ }
22
+ .app-card:hover {
23
+ box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
24
+ }
25
+ .app-card--editing {
26
+ border-color: var(--color-brand-purple);
27
+ box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
28
+ }
29
+
30
+ /* ── AppList (list.tsx) ──────────────────────────────────────────────────── */
31
+ .app-list-wrapper {
32
+ border: 1px solid #f3f4f6;
33
+ border-radius: 1rem;
34
+ overflow: hidden;
35
+ background-color: #ffffff;
36
+ }
37
+ .app-list-header {
38
+ position: sticky;
39
+ top: 0;
40
+ z-index: 10;
41
+ padding: 0.75rem 1.25rem;
42
+ background-color: var(--color-ext-f8f9fa);
43
+ border-bottom: 1px solid #e5e7eb;
44
+ }
45
+ .app-list-header > div {
46
+ font-size: 0.625rem;
47
+ font-weight: 500;
48
+ color: #9ca3af;
49
+ text-transform: uppercase;
50
+ letter-spacing: 0.05em;
51
+ }
52
+ .app-list-body {
53
+ width: 100%;
54
+ }
55
+ .app-list-row {
56
+ padding: 0.9375rem 1.25rem;
57
+ border-bottom: 1px solid #f3f4f6;
58
+ transition-property: background-color, border-color, color, fill, stroke, opacity, box-shadow, transform, filter;
59
+ transition-duration: 100ms;
60
+ }
61
+ .app-list-row:last-child {
62
+ border-bottom-width: 0;
63
+ }
64
+ .app-list-row:hover {
65
+ background-color: var(--color-ext-fafafa);
66
+ }
67
+ .app-list-row--editing {
68
+ background-color: rgb(238 242 255 / 0.4);
69
+ border-left: 2px solid var(--color-brand-purple);
70
+ }
71
+ .app-list-cell {
72
+ font-size: 0.8125rem;
73
+ color: #374151;
74
+ min-width: 0;
75
+ padding-right: 0.75rem;
76
+ }
77
+ .app-list-cell--muted {
78
+ color: #6b7280;
79
+ }
80
+ .app-list-cell--actions {
81
+ display: flex;
82
+ align-items: center;
83
+ gap: 0.125rem;
84
+ justify-content: flex-end;
85
+ padding-right: 0.25rem;
86
+ }
87
+
88
+ /* ── Switch (switch.tsx) ─────────────────────────────────────────────────── */
89
+ .switch-pill {
90
+ position: relative;
91
+ display: inline-flex;
92
+ flex-shrink: 0;
93
+ cursor: pointer;
94
+ align-items: center;
95
+ width: 2rem;
96
+ height: 1.125rem;
97
+ border-radius: 9999px;
98
+ padding: 0.125rem;
99
+ transition: background-color 0.25s ease-in-out;
100
+ }
101
+ .switch-pill:focus-within {
102
+ outline: none;
103
+ }
104
+ .switch-pill--on {
105
+ background-color: var(--color-ext-00c48c);
106
+ }
107
+ .switch-pill--off {
108
+ background-color: var(--color-neutral-450);
109
+ }
110
+ .switch-pill--disabled {
111
+ cursor: not-allowed;
112
+ opacity: 0.5;
113
+ }
114
+ .switch-pill__input {
115
+ position: absolute;
116
+ width: 1px;
117
+ height: 1px;
118
+ padding: 0;
119
+ margin: -1px;
120
+ overflow: hidden;
121
+ clip: rect(0, 0, 0, 0);
122
+ white-space: nowrap;
123
+ border-width: 0;
124
+ }
125
+ .switch-pill__thumb {
126
+ pointer-events: none;
127
+ display: block;
128
+ background-color: #ffffff;
129
+ width: 0.875rem;
130
+ height: 0.875rem;
131
+ border-radius: 9999px;
132
+ box-shadow: 0 1px 3px var(--color-ext-0000001f);
133
+ transition: transform 0.25s cubic-bezier(0.4, 0, 0.2, 1);
134
+ }
135
+ .switch-pill__thumb--on {
136
+ transform: translateX(0.875rem);
137
+ }
138
+ [dir="rtl"] .switch-pill__thumb--on {
139
+ transform: translateX(-0.875rem);
140
+ }
141
+ .switch-pill__thumb--off {
142
+ transform: translateX(0);
143
+ }
144
+
145
+ /* ── ViewToggle (view-toggle.tsx) ────────────────────────────────────────── */
146
+ .view-toggle {
147
+ display: flex;
148
+ align-items: center;
149
+ gap: 0.125rem;
150
+ background-color: #f3f4f6;
151
+ border-radius: 0.5rem;
152
+ padding: 0.25rem;
153
+ }
154
+ .view-toggle__btn {
155
+ display: flex;
156
+ align-items: center;
157
+ justify-content: center;
158
+ width: 2rem;
159
+ height: 2rem;
160
+ border-radius: 0.375rem;
161
+ transition-property: all;
162
+ transition-duration: 150ms;
163
+ color: var(--color-ext-888888);
164
+ opacity: 1 !important;
165
+ }
166
+ .view-toggle__btn:hover {
167
+ color: #4b5563;
168
+ background-color: #f3f4f6;
169
+ }
170
+ .view-toggle__btn:focus,
171
+ .view-toggle__btn:focus-visible {
172
+ outline: none;
173
+ box-shadow: none;
174
+ }
175
+ .view-toggle__btn--active {
176
+ background-color: #ffffff;
177
+ box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
178
+ color: var(--color-brand-purple);
179
+ }
180
+ .view-toggle__btn--active:hover {
181
+ color: var(--color-brand-purple);
182
+ background-color: #ffffff;
183
+ }
184
+
185
+ /* ── DropdownMenu (dropdown-menu.tsx) ────────────────────────────────────── */
186
+ .dropdownHead {
187
+ position: fixed;
188
+ z-index: var(--zindex-overlay);
189
+ min-width: 12rem;
190
+ overflow: hidden;
191
+ border-radius: 0.75rem;
192
+ border: 1px solid #f3f4f6;
193
+ background-color: #ffffff;
194
+ padding-top: 0.5rem;
195
+ padding-bottom: 0.5rem;
196
+ box-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
197
+ transition-property: opacity;
198
+ transition-duration: 150ms;
199
+ }
200
+ .dropdownInner {
201
+ position: relative;
202
+ display: flex;
203
+ cursor: pointer;
204
+ user-select: none;
205
+ align-items: center;
206
+ padding: 0.375rem 0.5rem;
207
+ font-size: 0.875rem;
208
+ line-height: 1.25rem;
209
+ outline: none;
210
+ transition-property: color, background-color, border-color;
211
+ transition-duration: 150ms;
212
+ }
213
+ .dropdownInner[data-disabled] {
214
+ pointer-events: none;
215
+ opacity: 0.5;
216
+ }
217
+
218
+ /* ── FormField / Input (form-field.tsx, expandable-search.tsx) ──────────── */
219
+ .form__field {
220
+ min-width: 0;
221
+ }
222
+ .form__field > * + * {
223
+ margin-top: 0.5rem;
224
+ }
225
+ .formfield__label {
226
+ font-size: 0.75rem;
227
+ font-weight: 500;
228
+ margin-bottom: 0.5rem;
229
+ display: block;
230
+ color: var(--color-text-primary);
231
+ }
232
+ .form-error {
233
+ font-size: 0.75rem;
234
+ line-height: 1rem;
235
+ color: #ef4444;
236
+ margin-top: 0.25rem;
237
+ }
238
+ .required-field {
239
+ color: #ef4444;
240
+ margin-left: 0.25rem;
241
+ }
242
+ .formfield__search {
243
+ display: flex;
244
+ height: 2.25rem;
245
+ width: 100%;
246
+ border-radius: 0.5rem;
247
+ font-size: 0.8125rem;
248
+ padding: 0.25rem 0.75rem 0.25rem 2.5rem;
249
+ background-color: transparent;
250
+ border: 1px solid var(--color-neutral-300);
251
+ }
252
+ .formfield__search::placeholder {
253
+ color: var(--color-neutral-400);
254
+ font-size: 0.8125rem;
255
+ }
256
+ .formfield__search:disabled {
257
+ cursor: not-allowed;
258
+ opacity: 0.5;
259
+ }
260
+ .formfield__search:focus-visible {
261
+ outline: none;
262
+ border-color: var(--focus-ring-color);
263
+ box-shadow: 0 0 0 1px var(--focus-ring-color-soft);
264
+ }
265
+
266
+ /* ── ExpandableSearch's collapsed trigger button ─────────────────────────── */
267
+ .header__round-btn {
268
+ width: 2.5625rem;
269
+ height: 2.5625rem;
270
+ padding: 0;
271
+ background-color: var(--color-ext-edeaf7);
272
+ border-radius: 1.25rem;
273
+ display: flex;
274
+ align-items: center;
275
+ justify-content: center;
276
+ font-size: 0.875rem;
277
+ }
278
+ .header__round-btn:hover {
279
+ background-color: var(--color-ext-e0daf0);
280
+ }
281
+
282
+ /* ── Loader (loader.tsx) — already plain CSS in the source app ──────────── */
283
+ .fp-loader-overlay {
284
+ position: fixed;
285
+ inset: 0;
286
+ z-index: 9999;
287
+ display: flex;
288
+ align-items: center;
289
+ justify-content: center;
290
+ background: rgba(255, 255, 255, 0.45);
291
+ backdrop-filter: blur(2px);
292
+ -webkit-backdrop-filter: blur(2px);
293
+ opacity: 0;
294
+ visibility: hidden;
295
+ transition: opacity 0.25s ease, visibility 0.25s ease;
296
+ pointer-events: none;
297
+ }
298
+ .fp-loader-overlay--visible {
299
+ opacity: 1;
300
+ visibility: visible;
301
+ pointer-events: auto;
302
+ }
303
+ .fp-loader-content {
304
+ display: flex;
305
+ flex-direction: column;
306
+ align-items: center;
307
+ gap: 0.75rem;
308
+ }
309
+ .fp-loader-gif {
310
+ width: 6rem;
311
+ height: 6rem;
312
+ object-fit: contain;
313
+ mix-blend-mode: multiply;
314
+ }
315
+
316
+ /* ── Table structure (table.tsx) ──────────────────────────────────────────
317
+ Only the classNames Table itself renders. Per-page column layouts
318
+ (`.flows-cols`, `.appt-cols`, etc. — passed as the `variant` prop) are
319
+ business/page-specific grid-template-columns and are NOT included here;
320
+ supply your own to match your own data columns. Row/cell styling for the
321
+ children you render inside Table is likewise left to you. */
322
+ .item-table__wrapper {
323
+ overflow-x: auto;
324
+ border: 1px solid #f3f4f6;
325
+ border-top: 0;
326
+ border-bottom-left-radius: 1rem;
327
+ border-bottom-right-radius: 1rem;
328
+ background-color: #ffffff;
329
+ margin-top: 0 !important;
330
+ }
331
+ .item-table__head {
332
+ position: sticky;
333
+ top: 0;
334
+ z-index: 10;
335
+ display: grid;
336
+ align-items: center;
337
+ gap: 0;
338
+ min-width: 36rem;
339
+ padding: 0.75rem 1.25rem;
340
+ background-color: var(--color-ext-f8f9fa);
341
+ border: 1px solid #f3f4f6;
342
+ border-bottom: 0;
343
+ border-top-left-radius: 1rem;
344
+ border-top-right-radius: 1rem;
345
+ }
346
+ .item-table__head > div {
347
+ font-size: 0.75rem;
348
+ font-weight: 700;
349
+ color: #9ca3af;
350
+ text-transform: uppercase;
351
+ letter-spacing: 0.04em;
352
+ }
353
+ .item-table__body {
354
+ width: 100%;
355
+ }
356
+ .item-table__head--comfortable {
357
+ border: 0;
358
+ border-bottom: 1px solid;
359
+ border-radius: 0;
360
+ min-height: 2.375rem;
361
+ background-color: var(--component-table-header-bg);
362
+ border-color: var(--component-table-border);
363
+ padding: 0.75rem 1.875rem;
364
+ }
365
+ .item-table__head--comfortable > div {
366
+ font-size: 0.6875rem;
367
+ color: var(--component-table-header-text);
368
+ word-break: break-word;
369
+ overflow-wrap: break-word;
370
+ white-space: normal;
371
+ line-height: normal;
372
+ }
373
+ .item-table__wrapper--comfortable {
374
+ border: 0;
375
+ border-radius: 0;
376
+ background-color: transparent;
377
+ }
378
+ .item-table__content--comfortable {
379
+ display: flex;
380
+ flex-direction: column;
381
+ gap: 0.5rem;
382
+ padding: 0.5rem 0;
383
+ }
384
+ .item-table__head--scrollable {
385
+ width: max-content;
386
+ min-width: 100%;
387
+ }
Binary file