@motir/design-system 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -10
- package/dist/components/theme/StyleVignette.js +24 -4
- package/dist/components/theme/StyleVignette.js.map +1 -1
- package/dist/components/ui/Checkbox.d.ts +84 -0
- package/dist/components/ui/Checkbox.js +70 -0
- package/dist/components/ui/Checkbox.js.map +1 -0
- package/dist/components/ui/Combobox.d.ts +7 -1
- package/dist/components/ui/Combobox.js +21 -3
- package/dist/components/ui/Combobox.js.map +1 -1
- package/dist/components/ui/ErrorState.js +1 -1
- package/dist/components/ui/ErrorState.js.map +1 -1
- package/dist/components/ui/Input.d.ts +33 -0
- package/dist/components/ui/Input.js +17 -6
- package/dist/components/ui/Input.js.map +1 -1
- package/dist/components/ui/MultiSelectPicker.js +1 -1
- package/dist/components/ui/MultiSelectPicker.js.map +1 -1
- package/dist/components/ui/Pill.d.ts +7 -4
- package/dist/components/ui/Pill.js +21 -1
- package/dist/components/ui/Pill.js.map +1 -1
- package/dist/components/ui/Segmented.js +22 -3
- package/dist/components/ui/Segmented.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/specimen/TokensSpecimen.js +41 -10
- package/dist/specimen/TokensSpecimen.js.map +1 -1
- package/package.json +7 -2
- package/theme.css +753 -48
package/README.md
CHANGED
|
@@ -19,8 +19,9 @@ See the keystone decision: [`docs/decisions/design-system-package.md`](../../doc
|
|
|
19
19
|
|
|
20
20
|
- **`@motir/design-system/theme.css`** — the full token layer as a distributable
|
|
21
21
|
stylesheet: the Tailwind v4 `@theme` block, `@custom-variant dark`, the Tier-3
|
|
22
|
-
`--el-*` element tokens,
|
|
23
|
-
`[data-type]` / `[data-surface]` override + surface-material block
|
|
22
|
+
`--el-*` element tokens, every `[data-palette]` / `[data-style]` /
|
|
23
|
+
`[data-type]` / `[data-surface]` override + surface-material block, and the
|
|
24
|
+
**motion floor** below.
|
|
24
25
|
- **`@motir/design-system`** (JS entry) — the axis **registries**
|
|
25
26
|
(`STYLE_IDS` / `PALETTE_IDS` / `TYPE_IDS`, `resolve*`, `is*Id`,
|
|
26
27
|
`STYLE_DEFAULT_TYPE`), the **theme-apply API** (`buildThemeInitScript`,
|
|
@@ -57,6 +58,34 @@ Use the primitives + provider as normal React components:
|
|
|
57
58
|
import { ThemeProvider, Button, Card } from '@motir/design-system';
|
|
58
59
|
```
|
|
59
60
|
|
|
61
|
+
## Motion — the reduced-motion floor is the PACKAGE's, not the consumer's
|
|
62
|
+
|
|
63
|
+
**`theme.css` owns the `prefers-reduced-motion` guard for every animation the
|
|
64
|
+
design system ships.** A consumer imports the stylesheet and is done; it does
|
|
65
|
+
not have to know which primitives animate, and it must not have to rediscover
|
|
66
|
+
the guard per surface. Two kinds live here:
|
|
67
|
+
|
|
68
|
+
- **Style-axis motion** — the `aurora` canvas drift, the `3d-immersive` tilt and
|
|
69
|
+
menu unfold — gated inside the `[data-style]` block that introduced it,
|
|
70
|
+
because the motion belongs to that style.
|
|
71
|
+
- **The motion floor** — motion that belongs to no style and renders under every
|
|
72
|
+
axis combination. Today that is `.animate-spin`, which `Spinner` paints and
|
|
73
|
+
`<Button loading>` inherits: under `prefers-reduced-motion: reduce` it sets
|
|
74
|
+
`animation: none` and nothing else (MOTIR-3844). It is unlayered, which is what
|
|
75
|
+
makes it outrank Tailwind's `@layer utilities` copy of the same class whatever
|
|
76
|
+
the specificity or the `@import` order.
|
|
77
|
+
|
|
78
|
+
**Stop the rotation, keep the glyph.** A spinner is the only signal that a
|
|
79
|
+
control is working, so the guard stills it rather than hiding it — the ring keeps
|
|
80
|
+
its size, its border and its `role="status"`. A consumer whose only in-flight cue
|
|
81
|
+
is the rotation should pair it with a **label change**, since with the animation
|
|
82
|
+
stilled the word is the whole of the state.
|
|
83
|
+
|
|
84
|
+
**Adding an animation to this package means adding its guard here**, in the same
|
|
85
|
+
change — `tests/theme/reducedMotionSpinner.test.ts` in `motir-core` reads the
|
|
86
|
+
floor back off the COMPILED stylesheet for both consumer shapes, so a guard that
|
|
87
|
+
does not survive compilation fails there rather than in someone's browser.
|
|
88
|
+
|
|
60
89
|
## Boundary notes (how the extraction applied the ADR's rule)
|
|
61
90
|
|
|
62
91
|
The ADR's boundary rule: a file moves in **iff** its only imports are other
|
|
@@ -116,11 +145,14 @@ package-scoped git tag. There is no manual `npm publish` in the normal path.
|
|
|
116
145
|
**Dry run:** trigger the workflow from the Actions tab via **Run workflow**
|
|
117
146
|
(`workflow_dispatch`) with **dry run** checked to build + pack without publishing.
|
|
118
147
|
|
|
119
|
-
**Auth:** the publish step
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
>
|
|
148
|
+
**Auth:** the publish step uses **OIDC Trusted Publishing** — no `NPM_TOKEN` secret.
|
|
149
|
+
GitHub mints a short-lived id-token (`id-token: write`) that npm exchanges for a
|
|
150
|
+
one-time publish credential, gated by a Trusted Publisher configured on npmjs.com
|
|
151
|
+
for this package (org `moooon-B-V` / repo `motir-core` / this workflow filename).
|
|
152
|
+
npm provenance is auto-generated on a Trusted Publish (the `repository` field in
|
|
153
|
+
this `package.json` satisfies its requirement).
|
|
154
|
+
|
|
155
|
+
> The **first** publish (`0.1.0`) was cut manually (`npm publish` with an
|
|
156
|
+
> interactive OTP), because OIDC cannot bootstrap a package that does not yet exist
|
|
157
|
+
> — the Trusted Publisher config lives on the package's settings page. Every release
|
|
158
|
+
> from `0.1.1` on flows through the workflow token-free.
|
|
@@ -155,6 +155,20 @@ var pillVariants = cva(
|
|
|
155
155
|
status: {
|
|
156
156
|
planned: "bg-(--el-tint-lavender) text-(--el-text-strong) border-transparent",
|
|
157
157
|
"in-progress": "bg-(--el-tint-sky) text-(--el-text-strong) border-transparent",
|
|
158
|
+
// BUILT, waiting on checks (MOTIR-3103). The first PER-STATUS tone on
|
|
159
|
+
// this axis — the other three are per-CATEGORY, and `implemented` sits in
|
|
160
|
+
// the same `in_progress` category as In Progress, Planning and In Review,
|
|
161
|
+
// so without its own tone four statuses render one chip.
|
|
162
|
+
//
|
|
163
|
+
// It uses the recipe theme.css's own `--el-status-*` header prescribes —
|
|
164
|
+
// "a status CHIP bg = color-mix(var(--el-status-X) 14%, var(--el-surface))
|
|
165
|
+
// with --el-text-strong" — which is the same dilution the `priority`
|
|
166
|
+
// variant below already renders. The hue is not spent by any other
|
|
167
|
+
// status in any palette (measured at ΔE2000 >= 10 across all ten by
|
|
168
|
+
// tests/theme/statusHueSeparation.test.ts), and the chip additionally
|
|
169
|
+
// carries a GLYPH, because a 14% tint is a subtle mark by construction
|
|
170
|
+
// and finding #35 forbids resting a state on colour alone.
|
|
171
|
+
implemented: "bg-[color-mix(in_srgb,var(--el-status-implemented)_14%,var(--el-surface))] text-(--el-text-strong) border-transparent",
|
|
158
172
|
done: "bg-(--el-tint-mint) text-(--el-text-strong) border-transparent"
|
|
159
173
|
},
|
|
160
174
|
severity: {
|
|
@@ -183,10 +197,16 @@ var pillVariants = cva(
|
|
|
183
197
|
// through the DEDICATED `--el-role-*` tokens (MOTIR-1274 · 1266.3) so a
|
|
184
198
|
// palette can tune roles apart from the other --el-tint-* meanings; each
|
|
185
199
|
// defaults to its prior tint (zero visual change).
|
|
200
|
+
// `custom` (MOTIR-2257 · MOTIR-2485) is a project's OWN role, which has no
|
|
201
|
+
// enum value — it takes the next free tint slot (peach) because this
|
|
202
|
+
// surface had already spent lavender / sky / mint on the built-ins, so a
|
|
203
|
+
// custom chip in any of those would read as one of them. The KIND is
|
|
204
|
+
// carried in WORDS by the chip's accessible name; nothing rests on the hue.
|
|
186
205
|
memberRole: {
|
|
187
206
|
admin: "bg-(--el-role-admin) text-(--el-text-strong) border-transparent",
|
|
188
207
|
member: "bg-(--el-role-member) text-(--el-text-strong) border-transparent",
|
|
189
|
-
viewer: "bg-(--el-role-viewer) text-(--el-text-strong) border-transparent"
|
|
208
|
+
viewer: "bg-(--el-role-viewer) text-(--el-text-strong) border-transparent",
|
|
209
|
+
custom: "bg-(--el-role-custom) text-(--el-text-strong) border-transparent"
|
|
190
210
|
},
|
|
191
211
|
// Organization roles (Story 6.10 · design/org-admin): owner → lavender
|
|
192
212
|
// (the highest, brand-purple tier), admin → sky, member → mint — the hue
|
|
@@ -285,8 +305,8 @@ function StyleVignette({ styleId, palette, type, label, className }) {
|
|
|
285
305
|
/* @__PURE__ */ jsx("span", { className: "size-2.5 rounded-full bg-(--el-accent)", "aria-hidden": true }),
|
|
286
306
|
/* @__PURE__ */ jsx("span", { className: "font-serif text-[12px] font-semibold text-(--el-text)", children: "Acme" }),
|
|
287
307
|
/* @__PURE__ */ jsx("span", { className: "ml-2 rounded-(--radius-control) bg-(--el-accent) px-2 py-[3px] text-[10px] font-medium text-(--el-accent-text)", children: "Board" }),
|
|
288
|
-
/* @__PURE__ */ jsx("span", { className: "text-[10px] text-(--el-text-
|
|
289
|
-
/* @__PURE__ */ jsx("span", { className: "text-[10px] text-(--el-text-
|
|
308
|
+
/* @__PURE__ */ jsx("span", { className: "text-[10px] text-(--el-text-secondary)", children: "Backlog" }),
|
|
309
|
+
/* @__PURE__ */ jsx("span", { className: "text-[10px] text-(--el-text-secondary)", children: "Reports" }),
|
|
290
310
|
/* @__PURE__ */ jsx("span", { className: "ml-auto size-5 rounded-full bg-(--el-muted)", "aria-hidden": true })
|
|
291
311
|
]
|
|
292
312
|
}
|
|
@@ -356,7 +376,7 @@ function StyleVignette({ styleId, palette, type, label, className }) {
|
|
|
356
376
|
"div",
|
|
357
377
|
{
|
|
358
378
|
"data-surface": "input",
|
|
359
|
-
className: "flex h-(--height-input) items-center gap-2 rounded-(--radius-input) border border-(--el-border-strong) bg-(--el-page-bg) px-(--spacing-input-x) text-[11px] text-(--el-text-
|
|
379
|
+
className: "flex h-(--height-input) items-center gap-2 rounded-(--radius-input) border border-(--el-border-strong) bg-(--el-page-bg) px-(--spacing-input-x) text-[11px] text-(--el-text-secondary)",
|
|
360
380
|
children: [
|
|
361
381
|
/* @__PURE__ */ jsx(Search, { className: "size-3.5", "aria-hidden": true }),
|
|
362
382
|
/* @__PURE__ */ jsx("span", { children: "Search work items\u2026" })
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/utils/cn.ts","../../../src/components/ui/Spinner.tsx","../../../src/components/ui/Button.tsx","../../../src/components/ui/Card.tsx","../../../src/components/ui/Pill.tsx","../../../src/components/theme/StyleVignette.tsx"],"names":["Spinner","cva","forwardRef","Button","jsx","Card","jsxs","Pill"],"mappings":"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","file":"StyleVignette.js","sourcesContent":["import { clsx, type ClassValue } from 'clsx';\nimport { twMerge } from 'tailwind-merge';\n\n/**\n * Merge Tailwind class names without conflicts.\n *\n * `clsx` handles conditional inclusion (booleans, undefineds, arrays);\n * `twMerge` resolves conflicting Tailwind classes (later wins, e.g.\n * `px-2 px-4` → `px-4`). Together they are the de facto standard for\n * composing Tailwind classes in React components.\n *\n * @example\n * cn('px-2 py-1', isActive && 'bg-primary', className)\n */\nexport function cn(...inputs: ClassValue[]): string {\n return twMerge(clsx(inputs));\n}\n","import { forwardRef, type HTMLAttributes } from 'react';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { cn } from '../../utils/cn';\n\n/**\n * Spinner — pure CSS rotation indicator.\n *\n * Three sizes; works standalone or inside a Button's loading state.\n *\n * @example\n * <Spinner size=\"md\" />\n * <Spinner size=\"sm\" aria-label=\"Loading\" />\n */\nconst spinnerVariants = cva(\n 'inline-block animate-spin rounded-full border-current border-t-transparent',\n {\n variants: {\n size: {\n sm: 'h-4 w-4 border-[1.5px]',\n md: 'h-5 w-5 border-2',\n lg: 'h-8 w-8 border-[3px]',\n },\n },\n defaultVariants: { size: 'md' },\n },\n);\n\nexport interface SpinnerProps\n extends Omit<HTMLAttributes<HTMLSpanElement>, 'role'>, VariantProps<typeof spinnerVariants> {}\n\nexport const Spinner = forwardRef<HTMLSpanElement, SpinnerProps>(function Spinner(\n { className, size, ...rest },\n ref,\n) {\n return (\n <span\n ref={ref}\n role=\"status\"\n aria-label={rest['aria-label'] ?? 'Loading'}\n className={cn(spinnerVariants({ size }), className)}\n {...rest}\n />\n );\n});\n","import { forwardRef, type ButtonHTMLAttributes, type ReactNode } from 'react';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { Spinner } from './Spinner';\nimport { cn } from '../../utils/cn';\n\n/**\n * Button — primary interactive primitive.\n *\n * Variants × sizes × states. The `loading` state shows an inline Spinner\n * and disables the button. Use semantic Tailwind token classes; never\n * hardcode colors. Shape responds to `data-style` via\n * `--radius-btn` (rectangles in default, pills in `soft`).\n *\n * @example\n * <Button variant=\"primary\" size=\"md\">Save</Button>\n * <Button variant=\"ghost\" leftIcon={<Plus />} loading>Saving…</Button>\n */\n// Exported so a Next `<Link>` can render AS a styled button (an anchor styled\n// with the button variants — keeps real-link semantics for navigation) without\n// nesting a <button> inside an <a>. Used by the /ready empty state's \"View all\n// issues\" link (Subtask 7.0.6).\nexport const buttonVariants = cva(\n cn(\n // Base layout\n 'inline-flex items-center justify-center gap-2',\n // Typography\n 'font-sans text-sm font-medium leading-none',\n // Shape — semantic tokens that flip with the active style\n 'rounded-(--radius-btn)',\n // Interaction\n 'transition-[transform,background-color,border-color,color] duration-(--transition-duration)',\n 'active:scale-(--active-scale)',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--focus-ring-color) focus-visible:ring-offset-2 focus-visible:ring-offset-background',\n 'disabled:pointer-events-none disabled:opacity-50',\n ),\n {\n variants: {\n variant: {\n primary: 'bg-(--el-accent) text-(--el-accent-text) hover:opacity-90',\n secondary:\n 'bg-transparent text-(--el-text) border border-(--el-button-border) hover:bg-(--el-surface)',\n ghost: 'bg-transparent text-(--el-text) hover:bg-(--el-surface)',\n danger: 'bg-(--el-danger) text-(--el-danger-text) hover:opacity-90',\n },\n size: {\n sm: 'h-(--height-btn-sm) px-3 text-xs',\n md: 'h-(--height-btn-md) px-(--spacing-btn-x)',\n lg: 'h-(--height-btn-lg) px-6 text-base',\n },\n },\n defaultVariants: {\n variant: 'primary',\n size: 'md',\n },\n },\n);\n\nexport interface ButtonProps\n extends ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {\n /** Shows inline Spinner + disables interaction. */\n loading?: boolean;\n /** Icon rendered before the label. */\n leftIcon?: ReactNode;\n /** Icon rendered after the label. */\n rightIcon?: ReactNode;\n}\n\nexport const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(\n { variant, size, loading, leftIcon, rightIcon, disabled, className, children, ...rest },\n ref,\n) {\n return (\n <button\n ref={ref}\n type=\"button\"\n // `data-variant` is the hook the 3D / Immersive style uses to give FILLED\n // buttons physical thickness (a solid palette-derived base edge) + a\n // press-DOWN (7.3.39). Inert under every other style.\n data-variant={variant ?? 'primary'}\n className={cn(buttonVariants({ variant, size }), className)}\n disabled={disabled || loading}\n aria-busy={loading || undefined}\n {...rest}\n >\n {loading ? (\n <Spinner size={size === 'lg' ? 'md' : 'sm'} aria-hidden />\n ) : leftIcon ? (\n <span aria-hidden className=\"inline-flex\">\n {leftIcon}\n </span>\n ) : null}\n <span>{children}</span>\n {!loading && rightIcon ? (\n <span aria-hidden className=\"inline-flex\">\n {rightIcon}\n </span>\n ) : null}\n </button>\n );\n});\n","import { forwardRef, type HTMLAttributes, type ReactNode } from 'react';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { cn } from '../../utils/cn';\n\n/**\n * Card — content container with optional header/footer slots and tint variants.\n *\n * Compose with `header` / `footer` props for the common pattern, or just\n * pass children for a bare card. `clickable` makes the whole card a button\n * (use `onClick` + Enter/Space; pass `href` to render as `<a>` instead).\n *\n * Tint variants use the pastel feature tints from the palette\n * (peach/rose/mint/lavender/sky/yellow) — apply sparingly, never to\n * page-level surfaces.\n *\n * @example\n * <Card header={<h3>Title</h3>}>Body content</Card>\n * <Card tint=\"lavender\" clickable onClick={open}>Pastel card</Card>\n * <Card tint=\"mint\"><a href=\"/x\">Link wrapper</a></Card>\n */\nconst cardVariants = cva(\n cn(\n 'rounded-(--radius-card) border border-(--el-border)',\n 'p-(--spacing-card-padding)',\n 'transition-shadow duration-(--transition-duration)',\n ),\n {\n variants: {\n tint: {\n none: 'bg-(--el-card)',\n peach: 'bg-(--el-tint-peach) border-transparent',\n rose: 'bg-(--el-tint-rose) border-transparent',\n mint: 'bg-(--el-tint-mint) border-transparent',\n lavender: 'bg-(--el-tint-lavender) border-transparent',\n sky: 'bg-(--el-tint-sky) border-transparent',\n yellow: 'bg-(--el-tint-yellow) border-transparent',\n },\n clickable: {\n true: 'cursor-pointer hover:shadow-(--shadow-card) focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--focus-ring-color) focus-visible:ring-offset-2 focus-visible:ring-offset-background',\n false: '',\n },\n },\n defaultVariants: { tint: 'none', clickable: false },\n },\n);\n\nexport interface CardProps\n extends Omit<HTMLAttributes<HTMLDivElement>, 'children'>, VariantProps<typeof cardVariants> {\n header?: ReactNode;\n footer?: ReactNode;\n children?: ReactNode;\n}\n\nexport const Card = forwardRef<HTMLDivElement, CardProps>(function Card(\n { tint, clickable, header, footer, className, children, ...rest },\n ref,\n) {\n return (\n <div\n ref={ref}\n className={cn(cardVariants({ tint, clickable }), className)}\n // `data-tilt` is the hook the 3D / Immersive style's pointer-parallax\n // engine (ImmersiveTilt, 7.3.39) uses to tip this tile toward the cursor.\n // Inert for every other style + under reduced motion; harmless otherwise.\n data-tilt=\"\"\n // `data-surface` is the hook a surface-MATERIAL style (e.g. glassmorphism)\n // uses to frost this panel — see globals.css's material layer. Only the\n // default (untinted) card opts in; a pastel-tint feature card keeps its\n // opaque tint, never a frosted overlay that would wash the hue out.\n {...(tint && tint !== 'none' ? {} : { 'data-surface': 'card' })}\n {...(clickable ? { tabIndex: 0, role: 'button' } : {})}\n {...rest}\n >\n {/* `data-tilt-layer` lifts the slot onto a translateZ plane under the 3D /\n Immersive style (7.3.39) so it PARALLAXES above the card face as the\n card tilts — `front` (the header/title pops most), `back` (the footer\n sits nearer the face). Inert under every other style + reduced motion.\n The body stays on the face so arbitrary content never renders on a\n skewed Z-plane. */}\n {header ? (\n <div data-tilt-layer=\"front\" className=\"mb-(--spacing-md)\">\n {header}\n </div>\n ) : null}\n <div>{children}</div>\n {footer ? (\n <div\n data-tilt-layer=\"back\"\n className=\"border-(--el-border) mt-(--spacing-md) border-t pt-(--spacing-md)\"\n >\n {footer}\n </div>\n ) : null}\n </div>\n );\n});\n","import { forwardRef, type HTMLAttributes } from 'react';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { cn } from '../../utils/cn';\n\n/**\n * Pill — compact status or severity label. Pure span, no interaction.\n *\n * Variant axes (use exactly one):\n * - `status`: planned | in-progress | done — Motir's Subtask lifecycle states.\n * - `severity`: info | success | warning | danger — semantic UI states.\n * - `priority`: highest | high | medium | low | lowest — work-item priority, a\n * 5-step diverging ramp (`--el-priority-*` hue tinted over the surface, plus a\n * redundant direction icon). The single source of truth is `PRIORITY_META`.\n * - `memberRole`: admin | member | viewer — a project membership role\n * (Story 6.4), one hued tint per role. (Named `memberRole`, not `role`, so\n * it can't collide with the DOM `role` ARIA attribute on the span.)\n * - `orgRole`: owner | admin | member — an ORGANIZATION role (Story 6.10),\n * sitting above the workspace `memberRole`. owner → lavender (the highest,\n * brand-purple tier), admin → sky, member → mint — per the org-admin design.\n * - `tone`: neutral — a non-semantic chip (counts, metadata). Dark text on a\n * neutral surface. `private` — the epic-level-privacy \"Not public\" badge\n * (Story 6.14 · design epic-privacy panels 1/2/6b): lavender tint + charcoal\n * text, the same AA-safe recipe as `status=\"planned\"` / `memberRole=\"admin\"`.\n *\n * All tones use adaptive dark/light text (`--color-charcoal`) on a hued tint,\n * so every variant clears WCAG AA contrast in both light and dark modes\n * (the hue lives in the background, not the text — PRODECT_FINDINGS #35).\n *\n * Always uses `--radius-badge` for fully rounded ends regardless of display style.\n *\n * @example\n * <Pill status=\"in-progress\">In progress</Pill>\n * <Pill severity=\"danger\">Validation failed</Pill>\n * <Pill tone=\"neutral\">3 members</Pill>\n */\nconst pillVariants = cva(\n cn(\n 'inline-flex items-center gap-1',\n 'rounded-(--radius-badge)',\n 'px-(--spacing-chip-x) py-(--spacing-chip-y)',\n 'font-sans text-xs font-medium',\n 'border',\n ),\n {\n variants: {\n // All colored tones carry the semantic hue in the TINT BACKGROUND and use\n // adaptive dark/light TEXT (`--color-charcoal`: #37352f light, #e5e5e5\n // dark) — the same pattern `planned` always used. The previous saturated\n // hue-on-tint TEXT failed WCAG AA (≈2.5–3.8:1 light, 3.2–4.0:1 dark);\n // charcoal-on-tint clears ~10:1 in both modes (PRODECT_FINDINGS #35).\n status: {\n planned: 'bg-(--el-tint-lavender) text-(--el-text-strong) border-transparent',\n 'in-progress': 'bg-(--el-tint-sky) text-(--el-text-strong) border-transparent',\n done: 'bg-(--el-tint-mint) text-(--el-text-strong) border-transparent',\n },\n severity: {\n info: 'bg-(--el-tint-sky) text-(--el-text-strong) border-transparent',\n success: 'bg-(--el-tint-mint) text-(--el-text-strong) border-transparent',\n warning: 'bg-(--el-tint-peach) text-(--el-text-strong) border-transparent',\n danger: 'bg-(--el-tint-rose) text-(--el-text-strong) border-transparent',\n },\n // Priority chips (MOTIR-1273 · 1266.2) — a 5-step diverging ramp keyed off\n // the `--el-priority-*` hues, un-collapsing `medium` and `lowest` (both\n // were `tone=\"neutral\"` grey). Each hue is diluted to a 14% tint over\n // `--el-surface` so charcoal `--el-text-strong` clears AA in both themes\n // (the hue lives in the background — finding #35); the redundant direction\n // icon (PRIORITY_META) is the non-colour cue. `medium` (slate) and\n // `lowest` (stone) resolve to two distinct greys in every palette.\n priority: {\n highest:\n 'bg-[color-mix(in_srgb,var(--el-priority-highest)_14%,var(--el-surface))] text-(--el-text-strong) border-transparent',\n high: 'bg-[color-mix(in_srgb,var(--el-priority-high)_14%,var(--el-surface))] text-(--el-text-strong) border-transparent',\n medium:\n 'bg-[color-mix(in_srgb,var(--el-priority-medium)_14%,var(--el-surface))] text-(--el-text-strong) border-transparent',\n low: 'bg-[color-mix(in_srgb,var(--el-priority-low)_14%,var(--el-surface))] text-(--el-text-strong) border-transparent',\n lowest:\n 'bg-[color-mix(in_srgb,var(--el-priority-lowest)_14%,var(--el-surface))] text-(--el-text-strong) border-transparent',\n },\n // Project membership roles (Story 6.4 · design/projects access-members):\n // admin → lavender, member → sky, viewer → mint — the hue in the tint,\n // charcoal text (AA-safe, finding #35), same recipe as `status`. Routed\n // through the DEDICATED `--el-role-*` tokens (MOTIR-1274 · 1266.3) so a\n // palette can tune roles apart from the other --el-tint-* meanings; each\n // defaults to its prior tint (zero visual change).\n memberRole: {\n admin: 'bg-(--el-role-admin) text-(--el-text-strong) border-transparent',\n member: 'bg-(--el-role-member) text-(--el-text-strong) border-transparent',\n viewer: 'bg-(--el-role-viewer) text-(--el-text-strong) border-transparent',\n },\n // Organization roles (Story 6.10 · design/org-admin): owner → lavender\n // (the highest, brand-purple tier), admin → sky, member → mint — the hue\n // in the tint, charcoal text (AA-safe, finding #35), same recipe as above.\n // Dedicated `--el-org-role-*` tokens (MOTIR-1274 · 1266.3); zero change.\n orgRole: {\n owner: 'bg-(--el-org-role-owner) text-(--el-text-strong) border-transparent',\n admin: 'bg-(--el-org-role-admin) text-(--el-text-strong) border-transparent',\n member: 'bg-(--el-org-role-member) text-(--el-text-strong) border-transparent',\n },\n tone: {\n neutral: 'bg-(--el-chip-bg) text-(--el-text-secondary) border-(--el-chip-border)',\n // Epic-level privacy \"Not public\" badge (Story 6.14 · design\n // epic-privacy panels 1/2/6b) — lavender tint, charcoal text, the same\n // AA-safe recipe as `status=\"planned\"` / `memberRole=\"admin\"` (≈9.7:1).\n // Dedicated `--el-privacy-private` token (MOTIR-1274 · 1266.3); the\n // matching `--el-privacy-public` is defined ahead of a \"public\" badge.\n private: 'bg-(--el-privacy-private) text-(--el-text-strong) border-transparent',\n // Inactive \"Archived\" badge (MOTIR-1276 · 1266.5) — a QUIET muted fill\n // with slate ink, not a hued tint: archived is an inactive state, not a\n // severity. Dedicated `--el-archived-pill-*` tokens so a palette can tune\n // the inactive badge apart from the neutral chip; AA-safe (slate on muted\n // clears AA both themes).\n archived: 'bg-(--el-archived-pill-bg) text-(--el-archived-pill-text) border-transparent',\n },\n },\n defaultVariants: {},\n },\n);\n\nexport interface PillProps\n extends HTMLAttributes<HTMLSpanElement>, VariantProps<typeof pillVariants> {}\n\nexport const Pill = forwardRef<HTMLSpanElement, PillProps>(function Pill(\n { status, severity, priority, memberRole, orgRole, tone, className, children, ...rest },\n ref,\n) {\n return (\n <span\n ref={ref}\n className={cn(\n pillVariants({ status, severity, priority, memberRole, orgRole, tone }),\n className,\n )}\n {...rest}\n >\n {children}\n </span>\n );\n});\n","import type { ReactNode } from 'react';\nimport { BookOpen, Search, SlidersHorizontal } from 'lucide-react';\nimport { cn } from '../../utils/cn';\nimport { buttonVariants } from '../ui/Button';\nimport { Card } from '../ui/Card';\nimport { Pill } from '../ui/Pill';\nimport type { StyleId } from '../../theme/styles';\nimport type { PaletteId } from '../../theme/palettes';\nimport type { TypeId } from '../../theme/typography';\n\n/**\n * StyleVignette — the LIVE preview specimen that makes a design axis's FEEL\n * legible (Subtask 7.3.37 / MOTIR-1050).\n *\n * The fix for \"I only see typography change\": instead of a token-swatch table,\n * this is a composed, realistic MINI-SURFACE — a nav rail, a work-item card, a\n * search input, a button row, and a floating modal — rendered LIVE under a\n * style's shape/feel tokens + component overrides (and, independently, a palette\n * and a type pairing). Switching any axis re-shapes / re-skins / re-types the\n * whole vignette, so the user sees the product's feel, not a colour chip.\n *\n * ── Two modes ────────────────────────────────────────────────────────────\n * • LIVE (no axis props) — the wrapper sets NO `data-*` axis attribute, so it\n * INHERITS the active theme from `<html>` (driven by `theme-context`). Use\n * this for the appearance-settings preview and the design-step showcase: the\n * vignette re-renders automatically as the global selection changes.\n * • SCOPED (any of `styleId` / `palette` / `type`) — the wrapper carries that\n * axis attribute, so the globals.css axis block recomputes the tokens for\n * this subtree ONLY. Use this for the onboarding Style gallery (one vignette\n * per style) and the Palette step (one style re-rendered under each palette).\n *\n * Built on the `app/tokens/page.tsx` specimen pattern: real `--el-*` colour\n * tokens + element-semantic shape tokens + the `data-surface` material hooks, so\n * surface-material styles (glassmorphism's frosted glass, cybercore's glow grid)\n * apply exactly as they do in the shipped app. NEVER a Tier-0 `--color-*` or a\n * raw `rounded-*`/`p-*`/`h-*` (the colour + shape token rules in CLAUDE.md).\n *\n * Nesting caveat: a SCOPED base entry (`warm-editorial` / `motir` palette /\n * `motir` type) has no globals.css override block, so when it is nested under a\n * NON-base `<html>` it inherits that ancestor's axis rather than resetting to\n * the base. In the real consumers this never bites — onboarding runs against the\n * default `<html>`, and the appearance preview uses LIVE mode (inherits the\n * active selection it is meant to show). Full nested-base isolation (a reset\n * block or an iframe) is a noted follow-up if a future consumer needs it.\n */\n\nexport interface StyleVignetteProps {\n /** Scope the vignette to a specific style; omit to inherit the active style. */\n styleId?: StyleId;\n /** Scope the vignette to a specific palette; omit to inherit the active palette. */\n palette?: PaletteId;\n /** Scope the vignette to a specific type pairing; omit to inherit the active type. */\n type?: TypeId;\n /**\n * Accessible name for the preview region. The vignette is decorative chrome,\n * so it is announced as a single labelled image rather than a pile of\n * read-out controls.\n */\n label?: string;\n className?: string;\n}\n\n/** A muted progress bar; `accent` tints it with the active accent. */\nfunction Bar({ accent, widthPct }: { accent?: boolean; widthPct: number }) {\n return (\n <div className=\"h-[6px] w-full overflow-hidden rounded-(--radius-badge) bg-(--el-muted)\">\n <div\n className={cn(\n 'h-full rounded-(--radius-badge)',\n accent ? 'bg-(--el-accent)' : 'bg-(--el-border-strong)',\n )}\n style={{ width: `${widthPct}%` }}\n />\n </div>\n );\n}\n\n/** One row of the work-item card's mini list — a status dot + label + chip. */\nfunction ListRow({ dotVar, label, chip }: { dotVar: string; label: string; chip: string }) {\n return (\n <div className=\"flex items-center gap-2 text-[11px] text-(--el-text-secondary)\">\n <span\n className=\"size-2 shrink-0 rounded-full\"\n style={{ background: `var(${dotVar})` }}\n aria-hidden\n />\n <span className=\"truncate\">{label}</span>\n <span className=\"ml-auto shrink-0 rounded-(--radius-badge) bg-(--el-surface) px-(--spacing-chip-x) py-(--spacing-chip-y) text-[9px] font-medium text-(--el-text-secondary)\">\n {chip}\n </span>\n </div>\n );\n}\n\nexport function StyleVignette({ styleId, palette, type, label, className }: StyleVignetteProps) {\n // Only emit an axis attribute when the caller pins it; an omitted axis stays\n // inherited (LIVE mode), which is how the appearance preview follows the\n // global selection without re-implementing the theme context.\n const axisAttrs: Record<string, string> = {};\n if (styleId) axisAttrs['data-style'] = styleId;\n if (palette) axisAttrs['data-palette'] = palette;\n if (type) axisAttrs['data-type'] = type;\n\n return (\n <div\n {...axisAttrs}\n role=\"img\"\n aria-label={label ?? 'Design style preview'}\n className={cn(\n 'style-vignette relative isolate overflow-hidden',\n 'rounded-(--radius-card) border border-(--el-border) bg-(--el-surface)',\n 'shadow-(--shadow-subtle)',\n className,\n )}\n >\n {/* Material canvas: transparent for flat styles; the glassmorphism gradient\n / cybercore grid is painted here via the descendant rules in\n globals.css (a preview can't lean on the body-scoped canvas). */}\n <span className=\"sv-canvas pointer-events-none absolute inset-0 -z-10\" aria-hidden />\n\n <div className=\"flex flex-col gap-3 p-3\">\n {/* NAV — a compact rail; data-surface lets a material style frost it. */}\n <div\n data-surface=\"sidebar\"\n className=\"flex items-center gap-2 rounded-(--radius-control) bg-(--el-sidebar-bg) px-3 py-2\"\n >\n <span className=\"size-2.5 rounded-full bg-(--el-accent)\" aria-hidden />\n <span className=\"font-serif text-[12px] font-semibold text-(--el-text)\">Acme</span>\n <span className=\"ml-2 rounded-(--radius-control) bg-(--el-accent) px-2 py-[3px] text-[10px] font-medium text-(--el-accent-text)\">\n Board\n </span>\n <span className=\"text-[10px] text-(--el-text-muted)\">Backlog</span>\n <span className=\"text-[10px] text-(--el-text-muted)\">Reports</span>\n <span className=\"ml-auto size-5 rounded-full bg-(--el-muted)\" aria-hidden />\n </div>\n\n <div className=\"grid grid-cols-[1.4fr_1fr] gap-3\">\n {/* WORK-ITEM CARD — the centrepiece: type hue + serif title + status. */}\n <Card className=\"flex flex-col gap-2 !p-3\">\n <div className=\"flex items-center gap-2\">\n {/* A decorative story-type icon in its `--el-type-story` hue. The\n package can't depend on motir-core's domain `IssueTypeIcon`\n (which maps work-item kinds → glyphs), so the vignette inlines\n the same glyph + hue directly — pixel-identical, boundary-clean. */}\n <BookOpen className=\"size-4 text-(--el-type-story)\" aria-hidden />\n <span className=\"truncate font-serif text-[13px] font-semibold text-(--el-text)\">\n Ship the billing flow\n </span>\n <Pill status=\"in-progress\" className=\"ml-auto !text-[9px]\">\n In progress\n </Pill>\n </div>\n <div className=\"flex flex-col gap-1.5\">\n <ListRow dotVar=\"--el-success\" label=\"Invoice schema\" chip=\"Done\" />\n <ListRow dotVar=\"--el-warning\" label=\"Dunning emails\" chip=\"Review\" />\n <ListRow dotVar=\"--el-info\" label=\"Stripe webhook\" chip=\"To do\" />\n </div>\n <div className=\"mt-0.5 flex flex-col gap-1.5\">\n <Bar accent widthPct={66} />\n <Bar widthPct={38} />\n </div>\n </Card>\n\n {/* MODAL — a floating dialog panel; modal radius + elevation + scrim. */}\n <div className=\"relative\">\n <span\n className=\"pointer-events-none absolute -inset-1 rounded-(--radius-card) bg-black/10\"\n aria-hidden\n />\n <div\n data-surface=\"modal\"\n className=\"relative flex flex-col gap-2 rounded-(--radius-modal) border border-(--el-border) bg-(--el-page-bg) p-3 shadow-(--shadow-modal)\"\n >\n <span className=\"font-serif text-[12px] font-semibold text-(--el-text)\">\n New invoice\n </span>\n <Bar widthPct={100} />\n <Bar widthPct={72} />\n <div className=\"mt-1 flex gap-2\">\n <span\n className={cn(\n buttonVariants({ variant: 'primary', size: 'sm' }),\n '!h-7 !px-3 !text-[10px]',\n )}\n >\n Create\n </span>\n <span\n className={cn(\n buttonVariants({ variant: 'ghost', size: 'sm' }),\n '!h-7 !px-3 !text-[10px]',\n )}\n >\n Cancel\n </span>\n </div>\n </div>\n </div>\n </div>\n\n {/* INPUT — a search field; data-surface lets a material style fill it. */}\n <div\n data-surface=\"input\"\n className=\"flex h-(--height-input) items-center gap-2 rounded-(--radius-input) border border-(--el-border-strong) bg-(--el-page-bg) px-(--spacing-input-x) text-[11px] text-(--el-text-muted)\"\n >\n <Search className=\"size-3.5\" aria-hidden />\n <span>Search work items…</span>\n </div>\n\n {/* BUTTON ROW — primary + ghost, exact Button silhouettes (non-interactive). */}\n <div className=\"flex gap-2\">\n <span className={cn(buttonVariants({ variant: 'primary', size: 'sm' }), '!text-[11px]')}>\n New work item\n </span>\n <span className={cn(buttonVariants({ variant: 'ghost', size: 'sm' }), '!text-[11px]')}>\n <SlidersHorizontal className=\"size-3.5\" aria-hidden />\n Filter\n </span>\n </div>\n </div>\n </div>\n );\n}\n\nexport default StyleVignette as (props: StyleVignetteProps) => ReactNode;\n"]}
|
|
1
|
+
{"version":3,"sources":["../../../src/utils/cn.ts","../../../src/components/ui/Spinner.tsx","../../../src/components/ui/Button.tsx","../../../src/components/ui/Card.tsx","../../../src/components/ui/Pill.tsx","../../../src/components/theme/StyleVignette.tsx"],"names":["Spinner","cva","forwardRef","Button","jsx","Card","jsxs","Pill"],"mappings":"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","file":"StyleVignette.js","sourcesContent":["import { clsx, type ClassValue } from 'clsx';\nimport { twMerge } from 'tailwind-merge';\n\n/**\n * Merge Tailwind class names without conflicts.\n *\n * `clsx` handles conditional inclusion (booleans, undefineds, arrays);\n * `twMerge` resolves conflicting Tailwind classes (later wins, e.g.\n * `px-2 px-4` → `px-4`). Together they are the de facto standard for\n * composing Tailwind classes in React components.\n *\n * @example\n * cn('px-2 py-1', isActive && 'bg-primary', className)\n */\nexport function cn(...inputs: ClassValue[]): string {\n return twMerge(clsx(inputs));\n}\n","import { forwardRef, type HTMLAttributes } from 'react';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { cn } from '../../utils/cn';\n\n/**\n * Spinner — pure CSS rotation indicator.\n *\n * Three sizes; works standalone or inside a Button's loading state.\n *\n * @example\n * <Spinner size=\"md\" />\n * <Spinner size=\"sm\" aria-label=\"Loading\" />\n */\nconst spinnerVariants = cva(\n 'inline-block animate-spin rounded-full border-current border-t-transparent',\n {\n variants: {\n size: {\n sm: 'h-4 w-4 border-[1.5px]',\n md: 'h-5 w-5 border-2',\n lg: 'h-8 w-8 border-[3px]',\n },\n },\n defaultVariants: { size: 'md' },\n },\n);\n\nexport interface SpinnerProps\n extends Omit<HTMLAttributes<HTMLSpanElement>, 'role'>, VariantProps<typeof spinnerVariants> {}\n\nexport const Spinner = forwardRef<HTMLSpanElement, SpinnerProps>(function Spinner(\n { className, size, ...rest },\n ref,\n) {\n return (\n <span\n ref={ref}\n role=\"status\"\n aria-label={rest['aria-label'] ?? 'Loading'}\n className={cn(spinnerVariants({ size }), className)}\n {...rest}\n />\n );\n});\n","import { forwardRef, type ButtonHTMLAttributes, type ReactNode } from 'react';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { Spinner } from './Spinner';\nimport { cn } from '../../utils/cn';\n\n/**\n * Button — primary interactive primitive.\n *\n * Variants × sizes × states. The `loading` state shows an inline Spinner\n * and disables the button. Use semantic Tailwind token classes; never\n * hardcode colors. Shape responds to `data-style` via\n * `--radius-btn` (rectangles in default, pills in `soft`).\n *\n * @example\n * <Button variant=\"primary\" size=\"md\">Save</Button>\n * <Button variant=\"ghost\" leftIcon={<Plus />} loading>Saving…</Button>\n */\n// Exported so a Next `<Link>` can render AS a styled button (an anchor styled\n// with the button variants — keeps real-link semantics for navigation) without\n// nesting a <button> inside an <a>. Used by the /ready empty state's \"View all\n// issues\" link (Subtask 7.0.6).\nexport const buttonVariants = cva(\n cn(\n // Base layout\n 'inline-flex items-center justify-center gap-2',\n // Typography\n 'font-sans text-sm font-medium leading-none',\n // Shape — semantic tokens that flip with the active style\n 'rounded-(--radius-btn)',\n // Interaction\n 'transition-[transform,background-color,border-color,color] duration-(--transition-duration)',\n 'active:scale-(--active-scale)',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--focus-ring-color) focus-visible:ring-offset-2 focus-visible:ring-offset-background',\n 'disabled:pointer-events-none disabled:opacity-50',\n ),\n {\n variants: {\n variant: {\n primary: 'bg-(--el-accent) text-(--el-accent-text) hover:opacity-90',\n secondary:\n 'bg-transparent text-(--el-text) border border-(--el-button-border) hover:bg-(--el-surface)',\n ghost: 'bg-transparent text-(--el-text) hover:bg-(--el-surface)',\n danger: 'bg-(--el-danger) text-(--el-danger-text) hover:opacity-90',\n },\n size: {\n sm: 'h-(--height-btn-sm) px-3 text-xs',\n md: 'h-(--height-btn-md) px-(--spacing-btn-x)',\n lg: 'h-(--height-btn-lg) px-6 text-base',\n },\n },\n defaultVariants: {\n variant: 'primary',\n size: 'md',\n },\n },\n);\n\nexport interface ButtonProps\n extends ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {\n /** Shows inline Spinner + disables interaction. */\n loading?: boolean;\n /** Icon rendered before the label. */\n leftIcon?: ReactNode;\n /** Icon rendered after the label. */\n rightIcon?: ReactNode;\n}\n\nexport const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(\n { variant, size, loading, leftIcon, rightIcon, disabled, className, children, ...rest },\n ref,\n) {\n return (\n <button\n ref={ref}\n type=\"button\"\n // `data-variant` is the hook the 3D / Immersive style uses to give FILLED\n // buttons physical thickness (a solid palette-derived base edge) + a\n // press-DOWN (7.3.39). Inert under every other style.\n data-variant={variant ?? 'primary'}\n className={cn(buttonVariants({ variant, size }), className)}\n disabled={disabled || loading}\n aria-busy={loading || undefined}\n {...rest}\n >\n {loading ? (\n <Spinner size={size === 'lg' ? 'md' : 'sm'} aria-hidden />\n ) : leftIcon ? (\n <span aria-hidden className=\"inline-flex\">\n {leftIcon}\n </span>\n ) : null}\n <span>{children}</span>\n {!loading && rightIcon ? (\n <span aria-hidden className=\"inline-flex\">\n {rightIcon}\n </span>\n ) : null}\n </button>\n );\n});\n","import { forwardRef, type HTMLAttributes, type ReactNode } from 'react';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { cn } from '../../utils/cn';\n\n/**\n * Card — content container with optional header/footer slots and tint variants.\n *\n * Compose with `header` / `footer` props for the common pattern, or just\n * pass children for a bare card. `clickable` makes the whole card a button\n * (use `onClick` + Enter/Space; pass `href` to render as `<a>` instead).\n *\n * Tint variants use the pastel feature tints from the palette\n * (peach/rose/mint/lavender/sky/yellow) — apply sparingly, never to\n * page-level surfaces.\n *\n * @example\n * <Card header={<h3>Title</h3>}>Body content</Card>\n * <Card tint=\"lavender\" clickable onClick={open}>Pastel card</Card>\n * <Card tint=\"mint\"><a href=\"/x\">Link wrapper</a></Card>\n */\nconst cardVariants = cva(\n cn(\n 'rounded-(--radius-card) border border-(--el-border)',\n 'p-(--spacing-card-padding)',\n 'transition-shadow duration-(--transition-duration)',\n ),\n {\n variants: {\n tint: {\n none: 'bg-(--el-card)',\n peach: 'bg-(--el-tint-peach) border-transparent',\n rose: 'bg-(--el-tint-rose) border-transparent',\n mint: 'bg-(--el-tint-mint) border-transparent',\n lavender: 'bg-(--el-tint-lavender) border-transparent',\n sky: 'bg-(--el-tint-sky) border-transparent',\n yellow: 'bg-(--el-tint-yellow) border-transparent',\n },\n clickable: {\n true: 'cursor-pointer hover:shadow-(--shadow-card) focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--focus-ring-color) focus-visible:ring-offset-2 focus-visible:ring-offset-background',\n false: '',\n },\n },\n defaultVariants: { tint: 'none', clickable: false },\n },\n);\n\nexport interface CardProps\n extends Omit<HTMLAttributes<HTMLDivElement>, 'children'>, VariantProps<typeof cardVariants> {\n header?: ReactNode;\n footer?: ReactNode;\n children?: ReactNode;\n}\n\nexport const Card = forwardRef<HTMLDivElement, CardProps>(function Card(\n { tint, clickable, header, footer, className, children, ...rest },\n ref,\n) {\n return (\n <div\n ref={ref}\n className={cn(cardVariants({ tint, clickable }), className)}\n // `data-tilt` is the hook the 3D / Immersive style's pointer-parallax\n // engine (ImmersiveTilt, 7.3.39) uses to tip this tile toward the cursor.\n // Inert for every other style + under reduced motion; harmless otherwise.\n data-tilt=\"\"\n // `data-surface` is the hook a surface-MATERIAL style (e.g. glassmorphism)\n // uses to frost this panel — see globals.css's material layer. Only the\n // default (untinted) card opts in; a pastel-tint feature card keeps its\n // opaque tint, never a frosted overlay that would wash the hue out.\n {...(tint && tint !== 'none' ? {} : { 'data-surface': 'card' })}\n {...(clickable ? { tabIndex: 0, role: 'button' } : {})}\n {...rest}\n >\n {/* `data-tilt-layer` lifts the slot onto a translateZ plane under the 3D /\n Immersive style (7.3.39) so it PARALLAXES above the card face as the\n card tilts — `front` (the header/title pops most), `back` (the footer\n sits nearer the face). Inert under every other style + reduced motion.\n The body stays on the face so arbitrary content never renders on a\n skewed Z-plane. */}\n {header ? (\n <div data-tilt-layer=\"front\" className=\"mb-(--spacing-md)\">\n {header}\n </div>\n ) : null}\n <div>{children}</div>\n {footer ? (\n <div\n data-tilt-layer=\"back\"\n className=\"border-(--el-border) mt-(--spacing-md) border-t pt-(--spacing-md)\"\n >\n {footer}\n </div>\n ) : null}\n </div>\n );\n});\n","import { forwardRef, type HTMLAttributes } from 'react';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { cn } from '../../utils/cn';\n\n/**\n * Pill — compact status or severity label. Pure span, no interaction.\n *\n * Variant axes (use exactly one):\n * - `status`: planned | in-progress | implemented | done — Motir's Subtask\n * lifecycle states. The first three are per-CATEGORY tones; `implemented` is\n * the one PER-STATUS tone (MOTIR-3103), because it shares a category with\n * In Progress / Planning / In Review and must not share their chip.\n * - `severity`: info | success | warning | danger — semantic UI states.\n * - `priority`: highest | high | medium | low | lowest — work-item priority, a\n * 5-step diverging ramp (`--el-priority-*` hue tinted over the surface, plus a\n * redundant direction icon). The single source of truth is `PRIORITY_META`.\n * - `memberRole`: admin | member | viewer | custom — a project membership role\n * (Story 6.4), one hued tint per role. (Named `memberRole`, not `role`, so\n * it can't collide with the DOM `role` ARIA attribute on the span.)\n * - `orgRole`: owner | admin | member — an ORGANIZATION role (Story 6.10),\n * sitting above the workspace `memberRole`. owner → lavender (the highest,\n * brand-purple tier), admin → sky, member → mint — per the org-admin design.\n * - `tone`: neutral — a non-semantic chip (counts, metadata). Dark text on a\n * neutral surface. `private` — the epic-level-privacy \"Not public\" badge\n * (Story 6.14 · design epic-privacy panels 1/2/6b): lavender tint + charcoal\n * text, the same AA-safe recipe as `status=\"planned\"` / `memberRole=\"admin\"`.\n *\n * All tones use adaptive dark/light text (`--color-charcoal`) on a hued tint,\n * so every variant clears WCAG AA contrast in both light and dark modes\n * (the hue lives in the background, not the text — PRODECT_FINDINGS #35).\n *\n * Always uses `--radius-badge` for fully rounded ends regardless of display style.\n *\n * @example\n * <Pill status=\"in-progress\">In progress</Pill>\n * <Pill severity=\"danger\">Validation failed</Pill>\n * <Pill tone=\"neutral\">3 members</Pill>\n */\nconst pillVariants = cva(\n cn(\n 'inline-flex items-center gap-1',\n 'rounded-(--radius-badge)',\n 'px-(--spacing-chip-x) py-(--spacing-chip-y)',\n 'font-sans text-xs font-medium',\n 'border',\n ),\n {\n variants: {\n // All colored tones carry the semantic hue in the TINT BACKGROUND and use\n // adaptive dark/light TEXT (`--color-charcoal`: #37352f light, #e5e5e5\n // dark) — the same pattern `planned` always used. The previous saturated\n // hue-on-tint TEXT failed WCAG AA (≈2.5–3.8:1 light, 3.2–4.0:1 dark);\n // charcoal-on-tint clears ~10:1 in both modes (PRODECT_FINDINGS #35).\n status: {\n planned: 'bg-(--el-tint-lavender) text-(--el-text-strong) border-transparent',\n 'in-progress': 'bg-(--el-tint-sky) text-(--el-text-strong) border-transparent',\n // BUILT, waiting on checks (MOTIR-3103). The first PER-STATUS tone on\n // this axis — the other three are per-CATEGORY, and `implemented` sits in\n // the same `in_progress` category as In Progress, Planning and In Review,\n // so without its own tone four statuses render one chip.\n //\n // It uses the recipe theme.css's own `--el-status-*` header prescribes —\n // \"a status CHIP bg = color-mix(var(--el-status-X) 14%, var(--el-surface))\n // with --el-text-strong\" — which is the same dilution the `priority`\n // variant below already renders. The hue is not spent by any other\n // status in any palette (measured at ΔE2000 >= 10 across all ten by\n // tests/theme/statusHueSeparation.test.ts), and the chip additionally\n // carries a GLYPH, because a 14% tint is a subtle mark by construction\n // and finding #35 forbids resting a state on colour alone.\n implemented:\n 'bg-[color-mix(in_srgb,var(--el-status-implemented)_14%,var(--el-surface))] text-(--el-text-strong) border-transparent',\n done: 'bg-(--el-tint-mint) text-(--el-text-strong) border-transparent',\n },\n severity: {\n info: 'bg-(--el-tint-sky) text-(--el-text-strong) border-transparent',\n success: 'bg-(--el-tint-mint) text-(--el-text-strong) border-transparent',\n warning: 'bg-(--el-tint-peach) text-(--el-text-strong) border-transparent',\n danger: 'bg-(--el-tint-rose) text-(--el-text-strong) border-transparent',\n },\n // Priority chips (MOTIR-1273 · 1266.2) — a 5-step diverging ramp keyed off\n // the `--el-priority-*` hues, un-collapsing `medium` and `lowest` (both\n // were `tone=\"neutral\"` grey). Each hue is diluted to a 14% tint over\n // `--el-surface` so charcoal `--el-text-strong` clears AA in both themes\n // (the hue lives in the background — finding #35); the redundant direction\n // icon (PRIORITY_META) is the non-colour cue. `medium` (slate) and\n // `lowest` (stone) resolve to two distinct greys in every palette.\n priority: {\n highest:\n 'bg-[color-mix(in_srgb,var(--el-priority-highest)_14%,var(--el-surface))] text-(--el-text-strong) border-transparent',\n high: 'bg-[color-mix(in_srgb,var(--el-priority-high)_14%,var(--el-surface))] text-(--el-text-strong) border-transparent',\n medium:\n 'bg-[color-mix(in_srgb,var(--el-priority-medium)_14%,var(--el-surface))] text-(--el-text-strong) border-transparent',\n low: 'bg-[color-mix(in_srgb,var(--el-priority-low)_14%,var(--el-surface))] text-(--el-text-strong) border-transparent',\n lowest:\n 'bg-[color-mix(in_srgb,var(--el-priority-lowest)_14%,var(--el-surface))] text-(--el-text-strong) border-transparent',\n },\n // Project membership roles (Story 6.4 · design/projects access-members):\n // admin → lavender, member → sky, viewer → mint — the hue in the tint,\n // charcoal text (AA-safe, finding #35), same recipe as `status`. Routed\n // through the DEDICATED `--el-role-*` tokens (MOTIR-1274 · 1266.3) so a\n // palette can tune roles apart from the other --el-tint-* meanings; each\n // defaults to its prior tint (zero visual change).\n // `custom` (MOTIR-2257 · MOTIR-2485) is a project's OWN role, which has no\n // enum value — it takes the next free tint slot (peach) because this\n // surface had already spent lavender / sky / mint on the built-ins, so a\n // custom chip in any of those would read as one of them. The KIND is\n // carried in WORDS by the chip's accessible name; nothing rests on the hue.\n memberRole: {\n admin: 'bg-(--el-role-admin) text-(--el-text-strong) border-transparent',\n member: 'bg-(--el-role-member) text-(--el-text-strong) border-transparent',\n viewer: 'bg-(--el-role-viewer) text-(--el-text-strong) border-transparent',\n custom: 'bg-(--el-role-custom) text-(--el-text-strong) border-transparent',\n },\n // Organization roles (Story 6.10 · design/org-admin): owner → lavender\n // (the highest, brand-purple tier), admin → sky, member → mint — the hue\n // in the tint, charcoal text (AA-safe, finding #35), same recipe as above.\n // Dedicated `--el-org-role-*` tokens (MOTIR-1274 · 1266.3); zero change.\n orgRole: {\n owner: 'bg-(--el-org-role-owner) text-(--el-text-strong) border-transparent',\n admin: 'bg-(--el-org-role-admin) text-(--el-text-strong) border-transparent',\n member: 'bg-(--el-org-role-member) text-(--el-text-strong) border-transparent',\n },\n tone: {\n neutral: 'bg-(--el-chip-bg) text-(--el-text-secondary) border-(--el-chip-border)',\n // Epic-level privacy \"Not public\" badge (Story 6.14 · design\n // epic-privacy panels 1/2/6b) — lavender tint, charcoal text, the same\n // AA-safe recipe as `status=\"planned\"` / `memberRole=\"admin\"` (≈9.7:1).\n // Dedicated `--el-privacy-private` token (MOTIR-1274 · 1266.3); the\n // matching `--el-privacy-public` is defined ahead of a \"public\" badge.\n private: 'bg-(--el-privacy-private) text-(--el-text-strong) border-transparent',\n // Inactive \"Archived\" badge (MOTIR-1276 · 1266.5) — a QUIET muted fill\n // with slate ink, not a hued tint: archived is an inactive state, not a\n // severity. Dedicated `--el-archived-pill-*` tokens so a palette can tune\n // the inactive badge apart from the neutral chip; AA-safe (slate on muted\n // clears AA both themes).\n archived: 'bg-(--el-archived-pill-bg) text-(--el-archived-pill-text) border-transparent',\n },\n },\n defaultVariants: {},\n },\n);\n\nexport interface PillProps\n extends HTMLAttributes<HTMLSpanElement>, VariantProps<typeof pillVariants> {}\n\nexport const Pill = forwardRef<HTMLSpanElement, PillProps>(function Pill(\n { status, severity, priority, memberRole, orgRole, tone, className, children, ...rest },\n ref,\n) {\n return (\n <span\n ref={ref}\n className={cn(\n pillVariants({ status, severity, priority, memberRole, orgRole, tone }),\n className,\n )}\n {...rest}\n >\n {children}\n </span>\n );\n});\n","import type { ReactNode } from 'react';\nimport { BookOpen, Search, SlidersHorizontal } from 'lucide-react';\nimport { cn } from '../../utils/cn';\nimport { buttonVariants } from '../ui/Button';\nimport { Card } from '../ui/Card';\nimport { Pill } from '../ui/Pill';\nimport type { StyleId } from '../../theme/styles';\nimport type { PaletteId } from '../../theme/palettes';\nimport type { TypeId } from '../../theme/typography';\n\n/**\n * StyleVignette — the LIVE preview specimen that makes a design axis's FEEL\n * legible (Subtask 7.3.37 / MOTIR-1050).\n *\n * The fix for \"I only see typography change\": instead of a token-swatch table,\n * this is a composed, realistic MINI-SURFACE — a nav rail, a work-item card, a\n * search input, a button row, and a floating modal — rendered LIVE under a\n * style's shape/feel tokens + component overrides (and, independently, a palette\n * and a type pairing). Switching any axis re-shapes / re-skins / re-types the\n * whole vignette, so the user sees the product's feel, not a colour chip.\n *\n * ── Two modes ────────────────────────────────────────────────────────────\n * • LIVE (no axis props) — the wrapper sets NO `data-*` axis attribute, so it\n * INHERITS the active theme from `<html>` (driven by `theme-context`). Use\n * this for the appearance-settings preview and the design-step showcase: the\n * vignette re-renders automatically as the global selection changes.\n * • SCOPED (any of `styleId` / `palette` / `type`) — the wrapper carries that\n * axis attribute, so the globals.css axis block recomputes the tokens for\n * this subtree ONLY. Use this for the onboarding Style gallery (one vignette\n * per style) and the Palette step (one style re-rendered under each palette).\n *\n * Built on the `app/tokens/page.tsx` specimen pattern: real `--el-*` colour\n * tokens + element-semantic shape tokens + the `data-surface` material hooks, so\n * surface-material styles (glassmorphism's frosted glass, cybercore's glow grid)\n * apply exactly as they do in the shipped app. NEVER a Tier-0 `--color-*` or a\n * raw `rounded-*`/`p-*`/`h-*` (the colour + shape token rules in CLAUDE.md).\n *\n * Nesting caveat: a SCOPED base entry (`warm-editorial` / `motir` palette /\n * `motir` type) has no globals.css override block, so when it is nested under a\n * NON-base `<html>` it inherits that ancestor's axis rather than resetting to\n * the base. In the real consumers this never bites — onboarding runs against the\n * default `<html>`, and the appearance preview uses LIVE mode (inherits the\n * active selection it is meant to show). Full nested-base isolation (a reset\n * block or an iframe) is a noted follow-up if a future consumer needs it.\n */\n\nexport interface StyleVignetteProps {\n /** Scope the vignette to a specific style; omit to inherit the active style. */\n styleId?: StyleId;\n /** Scope the vignette to a specific palette; omit to inherit the active palette. */\n palette?: PaletteId;\n /** Scope the vignette to a specific type pairing; omit to inherit the active type. */\n type?: TypeId;\n /**\n * Accessible name for the preview region. The vignette is decorative chrome,\n * so it is announced as a single labelled image rather than a pile of\n * read-out controls.\n */\n label?: string;\n className?: string;\n}\n\n/** A muted progress bar; `accent` tints it with the active accent. */\nfunction Bar({ accent, widthPct }: { accent?: boolean; widthPct: number }) {\n return (\n <div className=\"h-[6px] w-full overflow-hidden rounded-(--radius-badge) bg-(--el-muted)\">\n <div\n className={cn(\n 'h-full rounded-(--radius-badge)',\n accent ? 'bg-(--el-accent)' : 'bg-(--el-border-strong)',\n )}\n style={{ width: `${widthPct}%` }}\n />\n </div>\n );\n}\n\n/** One row of the work-item card's mini list — a status dot + label + chip. */\nfunction ListRow({ dotVar, label, chip }: { dotVar: string; label: string; chip: string }) {\n return (\n <div className=\"flex items-center gap-2 text-[11px] text-(--el-text-secondary)\">\n <span\n className=\"size-2 shrink-0 rounded-full\"\n style={{ background: `var(${dotVar})` }}\n aria-hidden\n />\n <span className=\"truncate\">{label}</span>\n <span className=\"ml-auto shrink-0 rounded-(--radius-badge) bg-(--el-surface) px-(--spacing-chip-x) py-(--spacing-chip-y) text-[9px] font-medium text-(--el-text-secondary)\">\n {chip}\n </span>\n </div>\n );\n}\n\nexport function StyleVignette({ styleId, palette, type, label, className }: StyleVignetteProps) {\n // Only emit an axis attribute when the caller pins it; an omitted axis stays\n // inherited (LIVE mode), which is how the appearance preview follows the\n // global selection without re-implementing the theme context.\n const axisAttrs: Record<string, string> = {};\n if (styleId) axisAttrs['data-style'] = styleId;\n if (palette) axisAttrs['data-palette'] = palette;\n if (type) axisAttrs['data-type'] = type;\n\n return (\n <div\n {...axisAttrs}\n role=\"img\"\n aria-label={label ?? 'Design style preview'}\n className={cn(\n 'style-vignette relative isolate overflow-hidden',\n 'rounded-(--radius-card) border border-(--el-border) bg-(--el-surface)',\n 'shadow-(--shadow-subtle)',\n className,\n )}\n >\n {/* Material canvas: transparent for flat styles; the glassmorphism gradient\n / cybercore grid is painted here via the descendant rules in\n globals.css (a preview can't lean on the body-scoped canvas). */}\n <span className=\"sv-canvas pointer-events-none absolute inset-0 -z-10\" aria-hidden />\n\n <div className=\"flex flex-col gap-3 p-3\">\n {/* NAV — a compact rail; data-surface lets a material style frost it. */}\n <div\n data-surface=\"sidebar\"\n className=\"flex items-center gap-2 rounded-(--radius-control) bg-(--el-sidebar-bg) px-3 py-2\"\n >\n <span className=\"size-2.5 rounded-full bg-(--el-accent)\" aria-hidden />\n <span className=\"font-serif text-[12px] font-semibold text-(--el-text)\">Acme</span>\n <span className=\"ml-2 rounded-(--radius-control) bg-(--el-accent) px-2 py-[3px] text-[10px] font-medium text-(--el-accent-text)\">\n Board\n </span>\n <span className=\"text-[10px] text-(--el-text-secondary)\">Backlog</span>\n <span className=\"text-[10px] text-(--el-text-secondary)\">Reports</span>\n <span className=\"ml-auto size-5 rounded-full bg-(--el-muted)\" aria-hidden />\n </div>\n\n <div className=\"grid grid-cols-[1.4fr_1fr] gap-3\">\n {/* WORK-ITEM CARD — the centrepiece: type hue + serif title + status. */}\n <Card className=\"flex flex-col gap-2 !p-3\">\n <div className=\"flex items-center gap-2\">\n {/* A decorative story-type icon in its `--el-type-story` hue. The\n package can't depend on motir-core's domain `IssueTypeIcon`\n (which maps work-item kinds → glyphs), so the vignette inlines\n the same glyph + hue directly — pixel-identical, boundary-clean. */}\n <BookOpen className=\"size-4 text-(--el-type-story)\" aria-hidden />\n <span className=\"truncate font-serif text-[13px] font-semibold text-(--el-text)\">\n Ship the billing flow\n </span>\n <Pill status=\"in-progress\" className=\"ml-auto !text-[9px]\">\n In progress\n </Pill>\n </div>\n <div className=\"flex flex-col gap-1.5\">\n <ListRow dotVar=\"--el-success\" label=\"Invoice schema\" chip=\"Done\" />\n <ListRow dotVar=\"--el-warning\" label=\"Dunning emails\" chip=\"Review\" />\n <ListRow dotVar=\"--el-info\" label=\"Stripe webhook\" chip=\"To do\" />\n </div>\n <div className=\"mt-0.5 flex flex-col gap-1.5\">\n <Bar accent widthPct={66} />\n <Bar widthPct={38} />\n </div>\n </Card>\n\n {/* MODAL — a floating dialog panel; modal radius + elevation + scrim. */}\n <div className=\"relative\">\n <span\n className=\"pointer-events-none absolute -inset-1 rounded-(--radius-card) bg-black/10\"\n aria-hidden\n />\n <div\n data-surface=\"modal\"\n className=\"relative flex flex-col gap-2 rounded-(--radius-modal) border border-(--el-border) bg-(--el-page-bg) p-3 shadow-(--shadow-modal)\"\n >\n <span className=\"font-serif text-[12px] font-semibold text-(--el-text)\">\n New invoice\n </span>\n <Bar widthPct={100} />\n <Bar widthPct={72} />\n <div className=\"mt-1 flex gap-2\">\n <span\n className={cn(\n buttonVariants({ variant: 'primary', size: 'sm' }),\n '!h-7 !px-3 !text-[10px]',\n )}\n >\n Create\n </span>\n <span\n className={cn(\n buttonVariants({ variant: 'ghost', size: 'sm' }),\n '!h-7 !px-3 !text-[10px]',\n )}\n >\n Cancel\n </span>\n </div>\n </div>\n </div>\n </div>\n\n {/* INPUT — a search field; data-surface lets a material style fill it. */}\n <div\n data-surface=\"input\"\n className=\"flex h-(--height-input) items-center gap-2 rounded-(--radius-input) border border-(--el-border-strong) bg-(--el-page-bg) px-(--spacing-input-x) text-[11px] text-(--el-text-secondary)\"\n >\n <Search className=\"size-3.5\" aria-hidden />\n <span>Search work items…</span>\n </div>\n\n {/* BUTTON ROW — primary + ghost, exact Button silhouettes (non-interactive). */}\n <div className=\"flex gap-2\">\n <span className={cn(buttonVariants({ variant: 'primary', size: 'sm' }), '!text-[11px]')}>\n New work item\n </span>\n <span className={cn(buttonVariants({ variant: 'ghost', size: 'sm' }), '!text-[11px]')}>\n <SlidersHorizontal className=\"size-3.5\" aria-hidden />\n Filter\n </span>\n </div>\n </div>\n </div>\n );\n}\n\nexport default StyleVignette as (props: StyleVignetteProps) => ReactNode;\n"]}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Checkbox — a tick box for composing a SET (`role="checkbox"`).
|
|
5
|
+
*
|
|
6
|
+
* The design system's one new primitive for Story MOTIR-2257 (subtask
|
|
7
|
+
* MOTIR-2465), named by `design/projects/design-notes.md` § *Primitives
|
|
8
|
+
* composed* — the row **`Permission checkbox` — NEW**.
|
|
9
|
+
*
|
|
10
|
+
* ⚠️ NOT A `Switch`, AND THE DISTINCTION IS THE REASON THIS EXISTS. A switch
|
|
11
|
+
* says *this setting is on now*; a checkbox says *this is part of the set I am
|
|
12
|
+
* composing*. Twenty-eight switches on the role editor would read as
|
|
13
|
+
* twenty-eight independent settings rather than one role being composed, which
|
|
14
|
+
* is precisely the wrong mental model for the feature. The alternative the
|
|
15
|
+
* product had been living with — a bare browser tick box, unstyled, in exactly
|
|
16
|
+
* one place — would have been the first surface in Motir that visibly is not
|
|
17
|
+
* part of the design system.
|
|
18
|
+
*
|
|
19
|
+
* ⚠️ TWO STATES, NOT THREE (Yue, 2026-08-09). An earlier revision carried a
|
|
20
|
+
* `provenance` discriminator so a permission that came WITH the author's chosen
|
|
21
|
+
* base could render a grey fill, distinct from the accent fill of one they added
|
|
22
|
+
* on top. That distinction died with the stored `based_on` column: the editor
|
|
23
|
+
* still lets an author START FROM a built-in, but nothing records it, so on a
|
|
24
|
+
* re-edit there is no base to have come from and the two fills could not be told
|
|
25
|
+
* apart honestly. A permission is held or it is not — and the accessible name
|
|
26
|
+
* says which, so the state never rests on colour alone.
|
|
27
|
+
*
|
|
28
|
+
* Controlled, like `Switch`. `disabled` renders a non-interactive box rather
|
|
29
|
+
* than hiding the row — the story's server-refused, non-`enforced` keys must
|
|
30
|
+
* stay legible (and disabled text is the one place `--el-text-faint` is
|
|
31
|
+
* legitimate, since 1.4.3 exempts it).
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* <Checkbox checked={held} onChange={setHeld} label="Add comments" />
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* // A box whose meaning is not set-membership passes its own state clause.
|
|
38
|
+
* <Checkbox
|
|
39
|
+
* checked={done}
|
|
40
|
+
* onChange={setDone}
|
|
41
|
+
* label="Open the DNS panel"
|
|
42
|
+
* stateLabels={{ checked: 'Done', unchecked: 'Not done' }}
|
|
43
|
+
* />
|
|
44
|
+
*/
|
|
45
|
+
interface CheckboxProps {
|
|
46
|
+
checked: boolean;
|
|
47
|
+
onChange: (next: boolean) => void;
|
|
48
|
+
/**
|
|
49
|
+
* The control's own name — what it is a box FOR. Rendered as the accessible
|
|
50
|
+
* name together with the held/not-held clause; pass `labelVisible` to draw it
|
|
51
|
+
* beside the box as well.
|
|
52
|
+
*/
|
|
53
|
+
label: string;
|
|
54
|
+
disabled?: boolean;
|
|
55
|
+
/** Render `label` as visible text beside the box, not only as the a11y name. */
|
|
56
|
+
labelVisible?: boolean;
|
|
57
|
+
id?: string;
|
|
58
|
+
className?: string;
|
|
59
|
+
/**
|
|
60
|
+
* The state clause the accessible name carries, when *held / not held* is the
|
|
61
|
+
* wrong vocabulary for what this box composes.
|
|
62
|
+
*
|
|
63
|
+
* ⚠️ THE DEFAULT IS SET-MEMBERSHIP, AND THAT IS NOT UNIVERSAL. This primitive
|
|
64
|
+
* was built for the role editor, where a box genuinely means *this permission
|
|
65
|
+
* is part of the set I am composing* — so `checkboxStateLabel` says **Held /
|
|
66
|
+
* Not held**, and for that surface it is exactly right. A box that means
|
|
67
|
+
* *this step is finished* (`Done / Not done`) or *this option is on* reads
|
|
68
|
+
* wrongly under a screen reader with that clause, and the wording is the only
|
|
69
|
+
* thing carrying the state: the fill is colour, which 1.4.1 does not let us
|
|
70
|
+
* lean on.
|
|
71
|
+
*
|
|
72
|
+
* Optional and defaulted, so every existing call site keeps the wording it
|
|
73
|
+
* has and only a caller with different semantics has to think about it.
|
|
74
|
+
*/
|
|
75
|
+
stateLabels?: {
|
|
76
|
+
checked: string;
|
|
77
|
+
unchecked: string;
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
/** The state clause the accessible name carries — never a colour, always words. */
|
|
81
|
+
declare function checkboxStateLabel(checked: boolean): string;
|
|
82
|
+
declare function Checkbox({ checked, onChange, label, disabled, labelVisible, id, className, stateLabels, }: CheckboxProps): react_jsx_runtime.JSX.Element;
|
|
83
|
+
|
|
84
|
+
export { Checkbox, type CheckboxProps, checkboxStateLabel };
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { clsx } from 'clsx';
|
|
2
|
+
import { twMerge } from 'tailwind-merge';
|
|
3
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
4
|
+
|
|
5
|
+
// src/utils/cn.ts
|
|
6
|
+
function cn(...inputs) {
|
|
7
|
+
return twMerge(clsx(inputs));
|
|
8
|
+
}
|
|
9
|
+
function checkboxStateLabel(checked) {
|
|
10
|
+
return checked ? "Held" : "Not held";
|
|
11
|
+
}
|
|
12
|
+
function Checkbox({
|
|
13
|
+
checked,
|
|
14
|
+
onChange,
|
|
15
|
+
label,
|
|
16
|
+
disabled,
|
|
17
|
+
labelVisible,
|
|
18
|
+
id,
|
|
19
|
+
className,
|
|
20
|
+
stateLabels
|
|
21
|
+
}) {
|
|
22
|
+
const stateClause = stateLabels ? checked ? stateLabels.checked : stateLabels.unchecked : checkboxStateLabel(checked);
|
|
23
|
+
return /* @__PURE__ */ jsxs(
|
|
24
|
+
"button",
|
|
25
|
+
{
|
|
26
|
+
type: "button",
|
|
27
|
+
role: "checkbox",
|
|
28
|
+
id,
|
|
29
|
+
"aria-checked": checked,
|
|
30
|
+
"aria-disabled": disabled || void 0,
|
|
31
|
+
"aria-label": `${label}, ${stateClause}`,
|
|
32
|
+
disabled,
|
|
33
|
+
onClick: () => !disabled && onChange(!checked),
|
|
34
|
+
className: cn(
|
|
35
|
+
"inline-flex items-center gap-2 focus-visible:ring-2 focus-visible:ring-(--focus-ring-color) focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50",
|
|
36
|
+
className
|
|
37
|
+
),
|
|
38
|
+
children: [
|
|
39
|
+
/* @__PURE__ */ jsx(
|
|
40
|
+
"span",
|
|
41
|
+
{
|
|
42
|
+
"aria-hidden": true,
|
|
43
|
+
className: cn(
|
|
44
|
+
"inline-flex size-4 shrink-0 items-center justify-center rounded-(--radius-control) border transition-colors",
|
|
45
|
+
checked ? "border-(--el-accent) bg-(--el-accent) text-(--el-accent-text)" : "border-(--el-border-strong) bg-transparent text-transparent"
|
|
46
|
+
),
|
|
47
|
+
children: /* @__PURE__ */ jsx(
|
|
48
|
+
"svg",
|
|
49
|
+
{
|
|
50
|
+
viewBox: "0 0 24 24",
|
|
51
|
+
className: "size-3",
|
|
52
|
+
fill: "none",
|
|
53
|
+
stroke: "currentColor",
|
|
54
|
+
strokeWidth: "3",
|
|
55
|
+
strokeLinecap: "round",
|
|
56
|
+
strokeLinejoin: "round",
|
|
57
|
+
children: /* @__PURE__ */ jsx("path", { d: "M20 6 9 17l-5-5" })
|
|
58
|
+
}
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
),
|
|
62
|
+
labelVisible ? /* @__PURE__ */ jsx("span", { className: "font-sans text-sm text-(--el-text)", children: label }) : null
|
|
63
|
+
]
|
|
64
|
+
}
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export { Checkbox, checkboxStateLabel };
|
|
69
|
+
//# sourceMappingURL=Checkbox.js.map
|
|
70
|
+
//# sourceMappingURL=Checkbox.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/utils/cn.ts","../../../src/components/ui/Checkbox.tsx"],"names":[],"mappings":";;;;;AAcO,SAAS,MAAM,MAAA,EAA8B;AAClD,EAAA,OAAO,OAAA,CAAQ,IAAA,CAAK,MAAM,CAAC,CAAA;AAC7B;AC8DO,SAAS,mBAAmB,OAAA,EAA0B;AAC3D,EAAA,OAAO,UAAU,MAAA,GAAS,UAAA;AAC5B;AAEO,SAAS,QAAA,CAAS;AAAA,EACvB,OAAA;AAAA,EACA,QAAA;AAAA,EACA,KAAA;AAAA,EACA,QAAA;AAAA,EACA,YAAA;AAAA,EACA,EAAA;AAAA,EACA,SAAA;AAAA,EACA;AACF,CAAA,EAAkB;AAChB,EAAA,MAAM,WAAA,GAAc,cAChB,OAAA,GACE,WAAA,CAAY,UACZ,WAAA,CAAY,SAAA,GACd,mBAAmB,OAAO,CAAA;AAC9B,EAAA,uBACE,IAAA;AAAA,IAAC,QAAA;AAAA,IAAA;AAAA,MACC,IAAA,EAAK,QAAA;AAAA,MACL,IAAA,EAAK,UAAA;AAAA,MACL,EAAA;AAAA,MACA,cAAA,EAAc,OAAA;AAAA,MACd,iBAAe,QAAA,IAAY,MAAA;AAAA,MAC3B,YAAA,EAAY,CAAA,EAAG,KAAK,CAAA,EAAA,EAAK,WAAW,CAAA,CAAA;AAAA,MACpC,QAAA;AAAA,MACA,SAAS,MAAM,CAAC,QAAA,IAAY,QAAA,CAAS,CAAC,OAAO,CAAA;AAAA,MAC7C,SAAA,EAAW,EAAA;AAAA,QACT,wKAAA;AAAA,QACA;AAAA,OACF;AAAA,MAEA,QAAA,EAAA;AAAA,wBAAA,GAAA;AAAA,UAAC,MAAA;AAAA,UAAA;AAAA,YACC,aAAA,EAAW,IAAA;AAAA,YACX,SAAA,EAAW,EAAA;AAAA,cACT,6GAAA;AAAA,cACA,UACI,+DAAA,GACA;AAAA,aACN;AAAA,YAGA,QAAA,kBAAA,GAAA;AAAA,cAAC,KAAA;AAAA,cAAA;AAAA,gBACC,OAAA,EAAQ,WAAA;AAAA,gBACR,SAAA,EAAU,QAAA;AAAA,gBACV,IAAA,EAAK,MAAA;AAAA,gBACL,MAAA,EAAO,cAAA;AAAA,gBACP,WAAA,EAAY,GAAA;AAAA,gBACZ,aAAA,EAAc,OAAA;AAAA,gBACd,cAAA,EAAe,OAAA;AAAA,gBAEf,QAAA,kBAAA,GAAA,CAAC,MAAA,EAAA,EAAK,CAAA,EAAE,iBAAA,EAAkB;AAAA;AAAA;AAC5B;AAAA,SACF;AAAA,QACC,+BAAe,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,oCAAA,EAAsC,iBAAM,CAAA,GAAU;AAAA;AAAA;AAAA,GACxF;AAEJ","file":"Checkbox.js","sourcesContent":["import { clsx, type ClassValue } from 'clsx';\nimport { twMerge } from 'tailwind-merge';\n\n/**\n * Merge Tailwind class names without conflicts.\n *\n * `clsx` handles conditional inclusion (booleans, undefineds, arrays);\n * `twMerge` resolves conflicting Tailwind classes (later wins, e.g.\n * `px-2 px-4` → `px-4`). Together they are the de facto standard for\n * composing Tailwind classes in React components.\n *\n * @example\n * cn('px-2 py-1', isActive && 'bg-primary', className)\n */\nexport function cn(...inputs: ClassValue[]): string {\n return twMerge(clsx(inputs));\n}\n","import { cn } from '../../utils/cn';\n\n/**\n * Checkbox — a tick box for composing a SET (`role=\"checkbox\"`).\n *\n * The design system's one new primitive for Story MOTIR-2257 (subtask\n * MOTIR-2465), named by `design/projects/design-notes.md` § *Primitives\n * composed* — the row **`Permission checkbox` — NEW**.\n *\n * ⚠️ NOT A `Switch`, AND THE DISTINCTION IS THE REASON THIS EXISTS. A switch\n * says *this setting is on now*; a checkbox says *this is part of the set I am\n * composing*. Twenty-eight switches on the role editor would read as\n * twenty-eight independent settings rather than one role being composed, which\n * is precisely the wrong mental model for the feature. The alternative the\n * product had been living with — a bare browser tick box, unstyled, in exactly\n * one place — would have been the first surface in Motir that visibly is not\n * part of the design system.\n *\n * ⚠️ TWO STATES, NOT THREE (Yue, 2026-08-09). An earlier revision carried a\n * `provenance` discriminator so a permission that came WITH the author's chosen\n * base could render a grey fill, distinct from the accent fill of one they added\n * on top. That distinction died with the stored `based_on` column: the editor\n * still lets an author START FROM a built-in, but nothing records it, so on a\n * re-edit there is no base to have come from and the two fills could not be told\n * apart honestly. A permission is held or it is not — and the accessible name\n * says which, so the state never rests on colour alone.\n *\n * Controlled, like `Switch`. `disabled` renders a non-interactive box rather\n * than hiding the row — the story's server-refused, non-`enforced` keys must\n * stay legible (and disabled text is the one place `--el-text-faint` is\n * legitimate, since 1.4.3 exempts it).\n *\n * @example\n * <Checkbox checked={held} onChange={setHeld} label=\"Add comments\" />\n *\n * @example\n * // A box whose meaning is not set-membership passes its own state clause.\n * <Checkbox\n * checked={done}\n * onChange={setDone}\n * label=\"Open the DNS panel\"\n * stateLabels={{ checked: 'Done', unchecked: 'Not done' }}\n * />\n */\nexport interface CheckboxProps {\n checked: boolean;\n onChange: (next: boolean) => void;\n /**\n * The control's own name — what it is a box FOR. Rendered as the accessible\n * name together with the held/not-held clause; pass `labelVisible` to draw it\n * beside the box as well.\n */\n label: string;\n disabled?: boolean;\n /** Render `label` as visible text beside the box, not only as the a11y name. */\n labelVisible?: boolean;\n id?: string;\n className?: string;\n /**\n * The state clause the accessible name carries, when *held / not held* is the\n * wrong vocabulary for what this box composes.\n *\n * ⚠️ THE DEFAULT IS SET-MEMBERSHIP, AND THAT IS NOT UNIVERSAL. This primitive\n * was built for the role editor, where a box genuinely means *this permission\n * is part of the set I am composing* — so `checkboxStateLabel` says **Held /\n * Not held**, and for that surface it is exactly right. A box that means\n * *this step is finished* (`Done / Not done`) or *this option is on* reads\n * wrongly under a screen reader with that clause, and the wording is the only\n * thing carrying the state: the fill is colour, which 1.4.1 does not let us\n * lean on.\n *\n * Optional and defaulted, so every existing call site keeps the wording it\n * has and only a caller with different semantics has to think about it.\n */\n stateLabels?: { checked: string; unchecked: string };\n}\n\n/** The state clause the accessible name carries — never a colour, always words. */\nexport function checkboxStateLabel(checked: boolean): string {\n return checked ? 'Held' : 'Not held';\n}\n\nexport function Checkbox({\n checked,\n onChange,\n label,\n disabled,\n labelVisible,\n id,\n className,\n stateLabels,\n}: CheckboxProps) {\n const stateClause = stateLabels\n ? checked\n ? stateLabels.checked\n : stateLabels.unchecked\n : checkboxStateLabel(checked);\n return (\n <button\n type=\"button\"\n role=\"checkbox\"\n id={id}\n aria-checked={checked}\n aria-disabled={disabled || undefined}\n aria-label={`${label}, ${stateClause}`}\n disabled={disabled}\n onClick={() => !disabled && onChange(!checked)}\n className={cn(\n 'inline-flex items-center gap-2 focus-visible:ring-2 focus-visible:ring-(--focus-ring-color) focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50',\n className,\n )}\n >\n <span\n aria-hidden\n className={cn(\n 'inline-flex size-4 shrink-0 items-center justify-center rounded-(--radius-control) border transition-colors',\n checked\n ? 'border-(--el-accent) bg-(--el-accent) text-(--el-accent-text)'\n : 'border-(--el-border-strong) bg-transparent text-transparent',\n )}\n >\n {/* The tick. `currentColor` inherits the ink the fill above picked. */}\n <svg\n viewBox=\"0 0 24 24\"\n className=\"size-3\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"3\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <path d=\"M20 6 9 17l-5-5\" />\n </svg>\n </span>\n {labelVisible ? <span className=\"font-sans text-sm text-(--el-text)\">{label}</span> : null}\n </button>\n );\n}\n"]}
|
|
@@ -28,6 +28,10 @@ interface ComboboxOption<T extends string> {
|
|
|
28
28
|
icon?: ReactNode;
|
|
29
29
|
/** Trailing muted text (e.g. the identifier). */
|
|
30
30
|
secondary?: string;
|
|
31
|
+
/** Trailing rich content pinned to the option row's far end (e.g. a state
|
|
32
|
+
* Pill or a "Linked to …" chip in the PR-link picker, MOTIR-1596).
|
|
33
|
+
* Decorative — the accessible name is `label`; renders after `secondary`. */
|
|
34
|
+
trailing?: ReactNode;
|
|
31
35
|
/**
|
|
32
36
|
* Optional section label. When consecutive options carry different `group`
|
|
33
37
|
* values, a non-interactive header row is rendered at each transition (the
|
|
@@ -48,7 +52,9 @@ interface ComboboxProps<T extends string> {
|
|
|
48
52
|
placeholder?: string;
|
|
49
53
|
searchable?: boolean;
|
|
50
54
|
searchPlaceholder?: string;
|
|
51
|
-
|
|
55
|
+
/** Empty-listbox content. A string, or rich nodes (e.g. a "no matches" line
|
|
56
|
+
* plus a hint sub-line — use block `<span>`s, since this renders in a `<p>`). */
|
|
57
|
+
emptyText?: ReactNode;
|
|
52
58
|
loading?: boolean;
|
|
53
59
|
loadingText?: string;
|
|
54
60
|
disabled?: boolean;
|
|
@@ -12,6 +12,7 @@ function cn(...inputs) {
|
|
|
12
12
|
var useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect;
|
|
13
13
|
var subscribeNoop = () => () => {
|
|
14
14
|
};
|
|
15
|
+
var ICON_SLOT = "inline-flex shrink-0 items-center";
|
|
15
16
|
function useMounted() {
|
|
16
17
|
return useSyncExternalStore(
|
|
17
18
|
subscribeNoop,
|
|
@@ -209,7 +210,10 @@ function Combobox({
|
|
|
209
210
|
}
|
|
210
211
|
const activeId = filtered.length > 0 ? optionId(active) : void 0;
|
|
211
212
|
const hasOptions = !loading && filtered.length > 0;
|
|
212
|
-
const inDialog =
|
|
213
|
+
const [inDialog, setInDialog] = useState(false);
|
|
214
|
+
useIsomorphicLayoutEffect(() => {
|
|
215
|
+
setInDialog(!!triggerRef.current?.closest('[data-surface="modal"],[aria-modal="true"]'));
|
|
216
|
+
}, [open]);
|
|
213
217
|
const menuInner = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
214
218
|
searchable ? /* @__PURE__ */ jsx(
|
|
215
219
|
"input",
|
|
@@ -270,7 +274,7 @@ function Combobox({
|
|
|
270
274
|
isActive ? "bg-(--el-option-active-bg) text-(--el-text)" : "text-(--el-text)"
|
|
271
275
|
),
|
|
272
276
|
children: [
|
|
273
|
-
opt.icon ? /* @__PURE__ */ jsx("span", { "aria-hidden": true, children: opt.icon }) : null,
|
|
277
|
+
opt.icon ? /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: ICON_SLOT, children: opt.icon }) : null,
|
|
274
278
|
/* @__PURE__ */ jsx("span", { className: "truncate", children: opt.label }),
|
|
275
279
|
opt.secondary ? (
|
|
276
280
|
// --el-text-identifier (= --color-slate, the -secondary
|
|
@@ -279,6 +283,20 @@ function Combobox({
|
|
|
279
283
|
// sidebar-caption contrast lesson) — identifier/slate clears it.
|
|
280
284
|
/* @__PURE__ */ jsx("span", { className: "text-(--el-text-identifier) ml-auto truncate text-xs", children: opt.secondary })
|
|
281
285
|
) : null,
|
|
286
|
+
opt.trailing ? (
|
|
287
|
+
// Rich trailing content (a Pill / chip). Pinned right; takes
|
|
288
|
+
// the `ml-auto` itself when there is no `secondary` before it.
|
|
289
|
+
/* @__PURE__ */ jsx(
|
|
290
|
+
"span",
|
|
291
|
+
{
|
|
292
|
+
className: cn(
|
|
293
|
+
"flex shrink-0 items-center gap-1.5",
|
|
294
|
+
opt.secondary ? "ml-1.5" : "ml-auto"
|
|
295
|
+
),
|
|
296
|
+
children: opt.trailing
|
|
297
|
+
}
|
|
298
|
+
)
|
|
299
|
+
) : null,
|
|
282
300
|
isSelected ? (
|
|
283
301
|
// The selected-row check uses the accent (matching
|
|
284
302
|
// MultiSelectPicker) — unifies the check-icon colour across
|
|
@@ -353,7 +371,7 @@ function Combobox({
|
|
|
353
371
|
),
|
|
354
372
|
children: [
|
|
355
373
|
selected ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
356
|
-
selected.icon ? /* @__PURE__ */ jsx("span", { "aria-hidden": true, children: selected.icon }) : null,
|
|
374
|
+
selected.icon ? /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: ICON_SLOT, children: selected.icon }) : null,
|
|
357
375
|
/* @__PURE__ */ jsx("span", { className: "text-(--el-text) truncate", children: selected.label }),
|
|
358
376
|
selected.secondary ? (
|
|
359
377
|
// identifier (= slate, the -secondary weight): AA on the trigger surface at 12px (as above).
|