@motir/design-system 0.6.0 → 0.8.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 CHANGED
@@ -109,6 +109,71 @@ here, per the ADR's explicit delegation to this subtask:
109
109
  - The token CSS ships as **`theme.css`** (the ADR §2 name; the card's provisional
110
110
  `tokens.css` was reconciled to this).
111
111
 
112
+ ## Rendering a mock
113
+
114
+ `@motir/design-system/mock` renders the package's OWN parts into ONE
115
+ self-contained `.mock.html`, so a design mock is the parts as they actually
116
+ render rather than a hand-copied stylesheet. It is **Node-only** (it reads files
117
+ and uses `react-dom/server`), which is why it lives on its own subpath and never
118
+ on the main entry.
119
+
120
+ ```ts
121
+ renderMock({
122
+ title: string,
123
+ panels: { label: string; element: ReactElement }[],
124
+ axes: { styleId: string; paletteId: string; typeId: string },
125
+ theme?: 'light' | 'dark', // data-theme, default 'light'
126
+ fontCss?: string, // inlined verbatim (e.g. @font-face with data: URLs)
127
+ }): Promise<string>
128
+ ```
129
+
130
+ - Each panel is rendered with `renderToStaticMarkup` into a labelled board
131
+ section, and `<html>` carries `data-style` / `data-palette` / `data-type` /
132
+ `data-theme`.
133
+ - Each axis id is checked against the package's registries. An unknown or
134
+ missing id falls back to that axis's default (the type axis to the pairing
135
+ the resolved style uses) and the fallback is named in an HTML comment at the
136
+ top of the document. It never throws.
137
+ - The CSS is **compiled**, not copied: Tailwind v4 (the `tailwindcss` peer's
138
+ `compile()` API) runs over the rendered markup with this package's
139
+ `theme.css` as input, and the result is inlined in one `<style>`.
140
+ - The document makes no network request: no stylesheet link, no script, no
141
+ remote `@import`. Fonts fall back to the stacks `theme.css` declares unless
142
+ you pass `fontCss`.
143
+
144
+ A script in a consuming repository (run it with `pnpm tsx`):
145
+
146
+ ```tsx
147
+ // scripts/render-settings-mock.tsx
148
+ import { mkdir, writeFile } from 'node:fs/promises';
149
+ import { Button, Card, Pill } from '@motir/design-system';
150
+ import { renderMock } from '@motir/design-system/mock';
151
+
152
+ const palette = process.argv[2] ?? 'amethyst';
153
+
154
+ const html = await renderMock({
155
+ title: 'Project settings — save bar',
156
+ axes: { styleId: 'warm-editorial', paletteId: palette, typeId: 'motir' },
157
+ panels: [
158
+ {
159
+ label: 'Unsaved changes',
160
+ element: (
161
+ <Card>
162
+ <Pill status="in-progress">Unsaved</Pill>
163
+ <Button variant="primary">Save changes</Button>
164
+ </Card>
165
+ ),
166
+ },
167
+ ],
168
+ });
169
+
170
+ await mkdir('design/settings', { recursive: true });
171
+ await writeFile('design/settings/save-bar.mock.html', html);
172
+ ```
173
+
174
+ Run it again with another palette id (`pnpm tsx scripts/render-settings-mock.tsx cobalt`)
175
+ and the same parts recolour.
176
+
112
177
  ## Development
113
178
 
114
179
  ```bash
@@ -37,6 +37,15 @@ interface ErrorStateProps extends HTMLAttributes<HTMLDivElement> {
37
37
  * (motir-core wires it from its `common.retry`).
38
38
  */
39
39
  retryLabel?: string;
40
+ /**
41
+ * The retry is IN FLIGHT: the button takes `Button`'s `loading` state
42
+ * (Spinner, `disabled`, `aria-busy`) and shows `retryPendingLabel`. For a
43
+ * retry that re-requests something slow — a route segment's server render
44
+ * (motir-core's error boundaries, MOTIR-6855 · design MOTIR-6854 panel 1a).
45
+ */
46
+ retryPending?: boolean;
47
+ /** Label while `retryPending`. Defaults to `retryLabel` — i18n, as above, is the app's. */
48
+ retryPendingLabel?: string;
40
49
  }
41
50
  declare const ErrorState: react.ForwardRefExoticComponent<ErrorStateProps & react.RefAttributes<HTMLDivElement>>;
42
51
 
@@ -137,7 +137,17 @@ var Card = forwardRef(function Card2({ tint, clickable, header, footer, classNam
137
137
  }
138
138
  );
139
139
  });
140
- var ErrorState = forwardRef(function ErrorState2({ title, description, error, retry, className, retryLabel = "Try again", ...rest }, ref) {
140
+ var ErrorState = forwardRef(function ErrorState2({
141
+ title,
142
+ description,
143
+ error,
144
+ retry,
145
+ className,
146
+ retryLabel = "Try again",
147
+ retryPending = false,
148
+ retryPendingLabel,
149
+ ...rest
150
+ }, ref) {
141
151
  const showErrorDetail = error && process.env.NODE_ENV !== "production";
142
152
  return /* @__PURE__ */ jsxs(
143
153
  Card,
@@ -158,7 +168,7 @@ var ErrorState = forwardRef(function ErrorState2({ title, description, error, re
158
168
  /* @__PURE__ */ jsx("h2", { className: "font-serif text-xl text-(--el-text)", children: title }),
159
169
  description ? /* @__PURE__ */ jsx("p", { className: "text-(--el-text-muted) mt-(--spacing-sm) max-w-prose font-sans text-sm", children: description }) : null,
160
170
  showErrorDetail ? /* @__PURE__ */ jsx("pre", { className: "bg-(--el-surface) text-(--el-text-secondary) mt-(--spacing-md) max-w-full overflow-x-auto rounded-(--radius-control) px-(--spacing-tooltip-x) py-(--spacing-tooltip-y) text-left font-mono text-xs", children: error.message }) : null,
161
- retry ? /* @__PURE__ */ jsx("div", { className: "mt-(--spacing-md)", children: /* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: retry, children: retryLabel }) }) : null
171
+ retry ? /* @__PURE__ */ jsx("div", { className: "mt-(--spacing-md)", children: /* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: retry, loading: retryPending, children: retryPending ? retryPendingLabel ?? retryLabel : retryLabel }) }) : null
162
172
  ]
163
173
  }
164
174
  );
@@ -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/ErrorState.tsx"],"names":["Spinner","cva","forwardRef","Button","jsx","Card","jsxs","ErrorState"],"mappings":";;;;;;;;AAcO,SAAS,MAAM,MAAA,EAA8B;AAClD,EAAA,OAAO,OAAA,CAAQ,IAAA,CAAK,MAAM,CAAC,CAAA;AAC7B;ACHA,IAAM,eAAA,GAAkB,GAAA;AAAA,EACtB,4EAAA;AAAA,EACA;AAAA,IACE,QAAA,EAAU;AAAA,MACR,IAAA,EAAM;AAAA,QACJ,EAAA,EAAI,wBAAA;AAAA,QACJ,EAAA,EAAI,kBAAA;AAAA,QACJ,EAAA,EAAI;AAAA;AACN,KACF;AAAA,IACA,eAAA,EAAiB,EAAE,IAAA,EAAM,IAAA;AAAK;AAElC,CAAA;AAKO,IAAM,OAAA,GAAU,UAAA,CAA0C,SAASA,QAAAA,CACxE,EAAE,WAAW,IAAA,EAAM,GAAG,IAAA,EAAK,EAC3B,GAAA,EACA;AACA,EAAA,uBACE,GAAA;AAAA,IAAC,MAAA;AAAA,IAAA;AAAA,MACC,GAAA;AAAA,MACA,IAAA,EAAK,QAAA;AAAA,MACL,YAAA,EAAY,IAAA,CAAK,YAAY,CAAA,IAAK,SAAA;AAAA,MAClC,WAAW,EAAA,CAAG,eAAA,CAAgB,EAAE,IAAA,EAAM,GAAG,SAAS,CAAA;AAAA,MACjD,GAAG;AAAA;AAAA,GACN;AAEJ,CAAC,CAAA;ACtBM,IAAM,cAAA,GAAiBC,GAAAA;AAAA,EAC5B,EAAA;AAAA;AAAA,IAEE,+CAAA;AAAA;AAAA,IAEA,4CAAA;AAAA;AAAA,IAEA,wBAAA;AAAA;AAAA,IAEA,6FAAA;AAAA,IACA,+BAAA;AAAA,IACA,0JAAA;AAAA,IACA;AAAA,GACF;AAAA,EACA;AAAA,IACE,QAAA,EAAU;AAAA,MACR,OAAA,EAAS;AAAA,QACP,OAAA,EAAS,2DAAA;AAAA,QACT,SAAA,EACE,4FAAA;AAAA,QACF,KAAA,EAAO,yDAAA;AAAA,QACP,MAAA,EAAQ;AAAA,OACV;AAAA,MACA,IAAA,EAAM;AAAA,QACJ,EAAA,EAAI,kCAAA;AAAA,QACJ,EAAA,EAAI,0CAAA;AAAA,QACJ,EAAA,EAAI;AAAA;AACN,KACF;AAAA,IACA,eAAA,EAAiB;AAAA,MACf,OAAA,EAAS,SAAA;AAAA,MACT,IAAA,EAAM;AAAA;AACR;AAEJ,CAAA;AAYO,IAAM,SAASC,UAAAA,CAA2C,SAASC,OAAAA,CACxE,EAAE,SAAS,IAAA,EAAM,OAAA,EAAS,QAAA,EAAU,SAAA,EAAW,UAAU,SAAA,EAAW,QAAA,EAAU,GAAG,IAAA,IACjF,GAAA,EACA;AACA,EAAA,uBACE,IAAA;AAAA,IAAC,QAAA;AAAA,IAAA;AAAA,MACC,GAAA;AAAA,MACA,IAAA,EAAK,QAAA;AAAA,MAIL,gBAAc,OAAA,IAAW,SAAA;AAAA,MACzB,SAAA,EAAW,GAAG,cAAA,CAAe,EAAE,SAAS,IAAA,EAAM,GAAG,SAAS,CAAA;AAAA,MAC1D,UAAU,QAAA,IAAY,OAAA;AAAA,MACtB,aAAW,OAAA,IAAW,MAAA;AAAA,MACrB,GAAG,IAAA;AAAA,MAEH,QAAA,EAAA;AAAA,QAAA,OAAA,mBACCC,IAAC,OAAA,EAAA,EAAQ,IAAA,EAAM,SAAS,IAAA,GAAO,IAAA,GAAO,MAAM,aAAA,EAAW,IAAA,EAAC,IACtD,QAAA,mBACFA,IAAC,MAAA,EAAA,EAAK,aAAA,EAAW,MAAC,SAAA,EAAU,aAAA,EACzB,oBACH,CAAA,GACE,IAAA;AAAA,wBACJA,GAAAA,CAAC,MAAA,EAAA,EAAM,QAAA,EAAS,CAAA;AAAA,QACf,CAAC,OAAA,IAAW,SAAA,mBACXA,GAAAA,CAAC,MAAA,EAAA,EAAK,aAAA,EAAW,IAAA,EAAC,SAAA,EAAU,aAAA,EACzB,QAAA,EAAA,SAAA,EACH,CAAA,GACE;AAAA;AAAA;AAAA,GACN;AAEJ,CAAC,CAAA;AC/ED,IAAM,YAAA,GAAeH,GAAAA;AAAA,EACnB,EAAA;AAAA,IACE,qDAAA;AAAA,IACA,4BAAA;AAAA,IACA;AAAA,GACF;AAAA,EACA;AAAA,IACE,QAAA,EAAU;AAAA,MACR,IAAA,EAAM;AAAA,QACJ,IAAA,EAAM,gBAAA;AAAA,QACN,KAAA,EAAO,yCAAA;AAAA,QACP,IAAA,EAAM,wCAAA;AAAA,QACN,IAAA,EAAM,wCAAA;AAAA,QACN,QAAA,EAAU,4CAAA;AAAA,QACV,GAAA,EAAK,uCAAA;AAAA,QACL,MAAA,EAAQ;AAAA,OACV;AAAA,MACA,SAAA,EAAW;AAAA,QACT,IAAA,EAAM,sMAAA;AAAA,QACN,KAAA,EAAO;AAAA;AACT,KACF;AAAA,IACA,eAAA,EAAiB,EAAE,IAAA,EAAM,MAAA,EAAQ,WAAW,KAAA;AAAM;AAEtD,CAAA;AASO,IAAM,IAAA,GAAOC,UAAAA,CAAsC,SAASG,KAAAA,CACjE,EAAE,IAAA,EAAM,SAAA,EAAW,MAAA,EAAQ,MAAA,EAAQ,SAAA,EAAW,QAAA,EAAU,GAAG,IAAA,IAC3D,GAAA,EACA;AACA,EAAA,uBACEC,IAAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,GAAA;AAAA,MACA,SAAA,EAAW,GAAG,YAAA,CAAa,EAAE,MAAM,SAAA,EAAW,GAAG,SAAS,CAAA;AAAA,MAI1D,WAAA,EAAU,EAAA;AAAA,MAKT,GAAI,QAAQ,IAAA,KAAS,MAAA,GAAS,EAAC,GAAI,EAAE,gBAAgB,MAAA,EAAO;AAAA,MAC5D,GAAI,YAAY,EAAE,QAAA,EAAU,GAAG,IAAA,EAAM,QAAA,KAAa,EAAC;AAAA,MACnD,GAAG,IAAA;AAAA,MAQH,QAAA,EAAA;AAAA,QAAA,MAAA,mBACCF,IAAC,KAAA,EAAA,EAAI,iBAAA,EAAgB,SAAQ,SAAA,EAAU,mBAAA,EACpC,kBACH,CAAA,GACE,IAAA;AAAA,wBACJA,GAAAA,CAAC,KAAA,EAAA,EAAK,QAAA,EAAS,CAAA;AAAA,QACd,yBACCA,GAAAA;AAAA,UAAC,KAAA;AAAA,UAAA;AAAA,YACC,iBAAA,EAAgB,MAAA;AAAA,YAChB,SAAA,EAAU,mEAAA;AAAA,YAET,QAAA,EAAA;AAAA;AAAA,SACH,GACE;AAAA;AAAA;AAAA,GACN;AAEJ,CAAC,CAAA;ACnDM,IAAM,UAAA,GAAaF,UAAAA,CAA4C,SAASK,WAAAA,CAC7E,EAAE,KAAA,EAAO,WAAA,EAAa,KAAA,EAAO,KAAA,EAAO,WAAW,UAAA,GAAa,WAAA,EAAa,GAAG,IAAA,IAC5E,GAAA,EACA;AACA,EAAA,MAAM,eAAA,GAAkB,KAAA,IAAS,OAAA,CAAQ,GAAA,CAAI,QAAA,KAAa,YAAA;AAC1D,EAAA,uBACED,IAAAA;AAAA,IAAC,IAAA;AAAA,IAAA;AAAA,MACC,GAAA;AAAA,MACA,IAAA,EAAK,OAAA;AAAA,MACL,SAAA,EAAW,EAAA,CAAG,wCAAA,EAA0C,SAAS,CAAA;AAAA,MAChE,GAAG,IAAA;AAAA,MAEJ,QAAA,EAAA;AAAA,wBAAAF,GAAAA;AAAA,UAAC,KAAA;AAAA,UAAA;AAAA,YACC,SAAA,EAAU,qEAAA;AAAA,YACV,KAAA,EAAO,EAAE,KAAA,EAAO,kBAAA,EAAmB;AAAA,YAEnC,0BAAAA,GAAAA,CAAC,aAAA,EAAA,EAAc,SAAA,EAAU,WAAA,EAAY,eAAW,IAAA,EAAC;AAAA;AAAA,SACnD;AAAA,wBACAA,GAAAA,CAAC,IAAA,EAAA,EAAG,SAAA,EAAU,uCAAuC,QAAA,EAAA,KAAA,EAAM,CAAA;AAAA,QAC1D,8BACCA,GAAAA,CAAC,OAAE,SAAA,EAAU,wEAAA,EACV,uBACH,CAAA,GACE,IAAA;AAAA,QACH,eAAA,mBACCA,GAAAA,CAAC,KAAA,EAAA,EAAI,WAAU,oMAAA,EACZ,QAAA,EAAA,KAAA,CAAM,SACT,CAAA,GACE,IAAA;AAAA,QACH,KAAA,mBACCA,GAAAA,CAAC,KAAA,EAAA,EAAI,WAAU,mBAAA,EACb,QAAA,kBAAAA,GAAAA,CAAC,MAAA,EAAA,EAAO,SAAQ,WAAA,EAAY,OAAA,EAAS,KAAA,EAClC,QAAA,EAAA,UAAA,EACH,GACF,CAAA,GACE;AAAA;AAAA;AAAA,GACN;AAEJ,CAAC","file":"ErrorState.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 { AlertTriangle } from 'lucide-react';\nimport { Button } from './Button';\nimport { Card } from './Card';\nimport { cn } from '../../utils/cn';\n\n/**\n * ErrorState — composed pattern for \"something failed\" screens.\n *\n * Use this whenever a request, mutation, or background job has failed in a\n * way the user needs to acknowledge or recover from. Prefer this over a\n * silent log line whenever the failure prevents the user from progressing.\n *\n * Has `role=\"alert\"` so screen readers announce the failure when it's\n * inserted into the DOM. The `error.message` debug detail renders only in\n * non-production builds (Next.js statically replaces `process.env.NODE_ENV`\n * at build time, so the block is dead-code-eliminated in production).\n *\n * @example\n * <ErrorState\n * title=\"Couldn't load workspace\"\n * description=\"We couldn't reach the server. Check your connection and try again.\"\n * error={err}\n * retry={() => refetch()}\n * />\n */\nexport interface ErrorStateProps extends HTMLAttributes<HTMLDivElement> {\n /** Short, plain-language summary of what failed. Required. */\n title: string;\n /** Recovery hint or context. Avoid restating the title. */\n description?: string;\n /** Real Error object — rendered as a mono code block in dev only. */\n error?: Error;\n /** Callback for the \"Try again\" button; omit to hide the button. */\n retry?: () => void;\n /**\n * Label for the retry button. Defaults to `'Try again'`. i18n is the consuming\n * app's concern — the design-system package is framework-agnostic (no\n * `next-intl` dependency), so a localized app passes the translated string\n * (motir-core wires it from its `common.retry`).\n */\n retryLabel?: string;\n}\n\nexport const ErrorState = forwardRef<HTMLDivElement, ErrorStateProps>(function ErrorState(\n { title, description, error, retry, className, retryLabel = 'Try again', ...rest },\n ref,\n) {\n const showErrorDetail = error && process.env.NODE_ENV !== 'production';\n return (\n <Card\n ref={ref}\n role=\"alert\"\n className={cn('flex flex-col items-center text-center', className)}\n {...rest}\n >\n <div\n className=\"mb-(--spacing-md) inline-flex h-12 w-12 items-center justify-center\"\n style={{ color: 'var(--el-danger)' }}\n >\n <AlertTriangle className=\"h-12 w-12\" aria-hidden />\n </div>\n <h2 className=\"font-serif text-xl text-(--el-text)\">{title}</h2>\n {description ? (\n <p className=\"text-(--el-text-muted) mt-(--spacing-sm) max-w-prose font-sans text-sm\">\n {description}\n </p>\n ) : null}\n {showErrorDetail ? (\n <pre className=\"bg-(--el-surface) text-(--el-text-secondary) mt-(--spacing-md) max-w-full overflow-x-auto rounded-(--radius-control) px-(--spacing-tooltip-x) py-(--spacing-tooltip-y) text-left font-mono text-xs\">\n {error.message}\n </pre>\n ) : null}\n {retry ? (\n <div className=\"mt-(--spacing-md)\">\n <Button variant=\"secondary\" onClick={retry}>\n {retryLabel}\n </Button>\n </div>\n ) : null}\n </Card>\n );\n});\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/ErrorState.tsx"],"names":["Spinner","cva","forwardRef","Button","jsx","Card","jsxs","ErrorState"],"mappings":"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","file":"ErrorState.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 { AlertTriangle } from 'lucide-react';\nimport { Button } from './Button';\nimport { Card } from './Card';\nimport { cn } from '../../utils/cn';\n\n/**\n * ErrorState — composed pattern for \"something failed\" screens.\n *\n * Use this whenever a request, mutation, or background job has failed in a\n * way the user needs to acknowledge or recover from. Prefer this over a\n * silent log line whenever the failure prevents the user from progressing.\n *\n * Has `role=\"alert\"` so screen readers announce the failure when it's\n * inserted into the DOM. The `error.message` debug detail renders only in\n * non-production builds (Next.js statically replaces `process.env.NODE_ENV`\n * at build time, so the block is dead-code-eliminated in production).\n *\n * @example\n * <ErrorState\n * title=\"Couldn't load workspace\"\n * description=\"We couldn't reach the server. Check your connection and try again.\"\n * error={err}\n * retry={() => refetch()}\n * />\n */\nexport interface ErrorStateProps extends HTMLAttributes<HTMLDivElement> {\n /** Short, plain-language summary of what failed. Required. */\n title: string;\n /** Recovery hint or context. Avoid restating the title. */\n description?: string;\n /** Real Error object — rendered as a mono code block in dev only. */\n error?: Error;\n /** Callback for the \"Try again\" button; omit to hide the button. */\n retry?: () => void;\n /**\n * Label for the retry button. Defaults to `'Try again'`. i18n is the consuming\n * app's concern — the design-system package is framework-agnostic (no\n * `next-intl` dependency), so a localized app passes the translated string\n * (motir-core wires it from its `common.retry`).\n */\n retryLabel?: string;\n /**\n * The retry is IN FLIGHT: the button takes `Button`'s `loading` state\n * (Spinner, `disabled`, `aria-busy`) and shows `retryPendingLabel`. For a\n * retry that re-requests something slow — a route segment's server render\n * (motir-core's error boundaries, MOTIR-6855 · design MOTIR-6854 panel 1a).\n */\n retryPending?: boolean;\n /** Label while `retryPending`. Defaults to `retryLabel` — i18n, as above, is the app's. */\n retryPendingLabel?: string;\n}\n\nexport const ErrorState = forwardRef<HTMLDivElement, ErrorStateProps>(function ErrorState(\n {\n title,\n description,\n error,\n retry,\n className,\n retryLabel = 'Try again',\n retryPending = false,\n retryPendingLabel,\n ...rest\n },\n ref,\n) {\n const showErrorDetail = error && process.env.NODE_ENV !== 'production';\n return (\n <Card\n ref={ref}\n role=\"alert\"\n className={cn('flex flex-col items-center text-center', className)}\n {...rest}\n >\n <div\n className=\"mb-(--spacing-md) inline-flex h-12 w-12 items-center justify-center\"\n style={{ color: 'var(--el-danger)' }}\n >\n <AlertTriangle className=\"h-12 w-12\" aria-hidden />\n </div>\n <h2 className=\"font-serif text-xl text-(--el-text)\">{title}</h2>\n {description ? (\n <p className=\"text-(--el-text-muted) mt-(--spacing-sm) max-w-prose font-sans text-sm\">\n {description}\n </p>\n ) : null}\n {showErrorDetail ? (\n <pre className=\"bg-(--el-surface) text-(--el-text-secondary) mt-(--spacing-md) max-w-full overflow-x-auto rounded-(--radius-control) px-(--spacing-tooltip-x) py-(--spacing-tooltip-y) text-left font-mono text-xs\">\n {error.message}\n </pre>\n ) : null}\n {retry ? (\n <div className=\"mt-(--spacing-md)\">\n <Button variant=\"secondary\" onClick={retry} loading={retryPending}>\n {retryPending ? (retryPendingLabel ?? retryLabel) : retryLabel}\n </Button>\n </div>\n ) : null}\n </Card>\n );\n});\n"]}
@@ -0,0 +1,2 @@
1
+ export { MockAxes, MockPanel, RenderMockOptions, extractClassCandidates, renderMock } from './renderMock.js';
2
+ import 'react';