@usefidel/design-system 0.2.0 → 0.3.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.
@@ -10,6 +10,6 @@
10
10
  "webapp.css"
11
11
  ],
12
12
  "storybookUrl": null,
13
- "packageVersion": "0.2.0",
14
- "resolvedSha": "c78cdac9bf6848d4e37053d17dd1bf15ded0aa40"
13
+ "packageVersion": "0.3.0",
14
+ "resolvedSha": "e4915ceec1dba185cff3ba11756fee2676b48c5d"
15
15
  }
package/dist/index.cjs CHANGED
@@ -20,7 +20,9 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
- Button: () => Button
23
+ Button: () => Button,
24
+ DiffSection: () => DiffSection,
25
+ MismatchCard: () => MismatchCard
24
26
  });
25
27
  module.exports = __toCommonJS(index_exports);
26
28
 
@@ -48,7 +50,173 @@ function Button({
48
50
  rightIcon != null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "fds-button__icon", "aria-hidden": "true", children: rightIcon })
49
51
  ] });
50
52
  }
53
+
54
+ // src/components/MismatchCard/MismatchCard.tsx
55
+ var import_react = require("react");
56
+ var import_jsx_runtime2 = require("react/jsx-runtime");
57
+ function MismatchCard({
58
+ title,
59
+ properties,
60
+ collapsedCount = 2,
61
+ defaultExpanded = false,
62
+ className,
63
+ ...rest
64
+ }) {
65
+ const [expanded, setExpanded] = (0, import_react.useState)(defaultExpanded);
66
+ const canToggle = properties.length > collapsedCount;
67
+ const visible = expanded || !canToggle ? properties : properties.slice(0, collapsedCount);
68
+ const classes = ["fds-mismatch-card", className].filter(Boolean).join(" ");
69
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: classes, ...rest, children: [
70
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "fds-mismatch-card__content", children: [
71
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "fds-mismatch-card__title", title, children: title }),
72
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "fds-mismatch-card__rows", children: visible.map((prop, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "fds-mismatch-card__row", children: [
73
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "fds-mismatch-card__label", children: prop.label }),
74
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "fds-mismatch-card__values", children: [
75
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "fds-mismatch-card__live", children: prop.live }),
76
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "fds-mismatch-card__design", children: prop.design })
77
+ ] })
78
+ ] }, `${prop.label}-${i}`)) })
79
+ ] }),
80
+ canToggle && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
81
+ "button",
82
+ {
83
+ type: "button",
84
+ className: "fds-mismatch-card__toggle",
85
+ "aria-expanded": expanded,
86
+ onClick: () => setExpanded((v) => !v),
87
+ children: [
88
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Chevron, { direction: expanded ? "up" : "down" }),
89
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "fds-mismatch-card__toggle-label", children: expanded ? "Show less" : "Show more" })
90
+ ]
91
+ }
92
+ )
93
+ ] });
94
+ }
95
+ function Chevron({ direction }) {
96
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
97
+ "svg",
98
+ {
99
+ className: `fds-mismatch-card__chevron fds-mismatch-card__chevron--${direction}`,
100
+ width: "20",
101
+ height: "20",
102
+ viewBox: "0 0 20 20",
103
+ fill: "none",
104
+ "aria-hidden": "true",
105
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
106
+ "path",
107
+ {
108
+ d: "M6 8.5 10 12l4-3.5",
109
+ stroke: "currentColor",
110
+ strokeWidth: "1.6",
111
+ strokeLinecap: "round",
112
+ strokeLinejoin: "round"
113
+ }
114
+ )
115
+ }
116
+ );
117
+ }
118
+
119
+ // src/components/DiffSection/DiffSection.tsx
120
+ var import_react2 = require("react");
121
+ var import_jsx_runtime3 = require("react/jsx-runtime");
122
+ function plural(n, one, many) {
123
+ return `${n} ${n === 1 ? one : many}`;
124
+ }
125
+ function DiffSection({
126
+ number,
127
+ name,
128
+ mismatchCount,
129
+ elements,
130
+ defaultExpanded = false,
131
+ className,
132
+ ...rest
133
+ }) {
134
+ const [expanded, setExpanded] = (0, import_react2.useState)(defaultExpanded);
135
+ const count = mismatchCount ?? elements.reduce((sum, el) => sum + el.properties.length, 0);
136
+ const classes = ["fds-diff-section", className].filter(Boolean).join(" ");
137
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: classes, "data-expanded": expanded ? "true" : void 0, ...rest, children: [
138
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
139
+ "button",
140
+ {
141
+ type: "button",
142
+ className: "fds-diff-section__header",
143
+ "aria-expanded": expanded,
144
+ onClick: () => setExpanded((v) => !v),
145
+ children: [
146
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "fds-diff-section__head-left", children: [
147
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Chevron2, { open: expanded }),
148
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "fds-diff-section__number", children: number }),
149
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "fds-diff-section__name", children: name })
150
+ ] }),
151
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "fds-diff-section__count", children: plural(count, "mismatch", "mismatches") })
152
+ ]
153
+ }
154
+ ),
155
+ expanded && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "fds-diff-section__body", children: elements.map((el, ei) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "fds-diff-section__element", children: [
156
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "fds-diff-section__element-head", children: [
157
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "fds-diff-section__element-name", children: el.name }),
158
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "fds-diff-section__element-count", children: plural(el.properties.length, "property", "properties") })
159
+ ] }),
160
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "fds-diff-section__lines", children: el.properties.map((line, li) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "fds-diff-section__line", children: [
161
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "fds-diff-section__prop", children: line.property }),
162
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "fds-diff-section__delta", children: [
163
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "fds-diff-section__live", children: line.live }),
164
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Arrow, {}),
165
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "fds-diff-section__design", children: line.design })
166
+ ] })
167
+ ] }, `${line.property}-${li}`)) })
168
+ ] }, `${el.name}-${ei}`)) })
169
+ ] });
170
+ }
171
+ function Chevron2({ open }) {
172
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
173
+ "svg",
174
+ {
175
+ className: `fds-diff-section__chevron${open ? " fds-diff-section__chevron--open" : ""}`,
176
+ width: "12",
177
+ height: "12",
178
+ viewBox: "0 0 12 12",
179
+ fill: "none",
180
+ "aria-hidden": "true",
181
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
182
+ "path",
183
+ {
184
+ d: "M4.5 3 8 6l-3.5 3",
185
+ stroke: "currentColor",
186
+ strokeWidth: "1.3",
187
+ strokeLinecap: "round",
188
+ strokeLinejoin: "round"
189
+ }
190
+ )
191
+ }
192
+ );
193
+ }
194
+ function Arrow() {
195
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
196
+ "svg",
197
+ {
198
+ className: "fds-diff-section__arrow",
199
+ width: "10",
200
+ height: "10",
201
+ viewBox: "0 0 10 10",
202
+ fill: "none",
203
+ "aria-hidden": "true",
204
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
205
+ "path",
206
+ {
207
+ d: "M1.5 5h6.5M5.5 2.5 8 5 5.5 7.5",
208
+ stroke: "currentColor",
209
+ strokeWidth: "1",
210
+ strokeLinecap: "round",
211
+ strokeLinejoin: "round"
212
+ }
213
+ )
214
+ }
215
+ );
216
+ }
51
217
  // Annotate the CommonJS export names for ESM import in node:
52
218
  0 && (module.exports = {
53
- Button
219
+ Button,
220
+ DiffSection,
221
+ MismatchCard
54
222
  });
package/dist/index.css CHANGED
@@ -123,3 +123,253 @@
123
123
  color: #a3a3a3;
124
124
  cursor: not-allowed;
125
125
  }
126
+
127
+ /* src/components/MismatchCard/mismatch-card.css */
128
+ .fds-mismatch-card {
129
+ box-sizing: border-box;
130
+ display: flex;
131
+ flex-direction: column;
132
+ align-items: center;
133
+ gap: 14px;
134
+ width: max-content;
135
+ max-width: 368px;
136
+ padding: var(--spacing-6);
137
+ background: rgba(255, 255, 255, 0.4);
138
+ backdrop-filter: blur(24px);
139
+ -webkit-backdrop-filter: blur(24px);
140
+ border: 2px solid var(--color-accent);
141
+ border-radius: var(--radius-lg);
142
+ box-shadow: 0 15px 35px 0 rgba(151, 151, 151, 0.2);
143
+ font-family: var(--font-body);
144
+ overflow: clip;
145
+ }
146
+ .fds-mismatch-card *,
147
+ .fds-mismatch-card *::before,
148
+ .fds-mismatch-card *::after {
149
+ box-sizing: border-box;
150
+ }
151
+ .fds-mismatch-card__content {
152
+ display: flex;
153
+ flex-direction: column;
154
+ align-items: flex-start;
155
+ gap: var(--spacing-5);
156
+ width: 320px;
157
+ }
158
+ .fds-mismatch-card__title {
159
+ margin: 0;
160
+ width: 100%;
161
+ font-family: var(--font-display);
162
+ font-weight: var(--font-weight-semibold);
163
+ font-size: 16px;
164
+ line-height: 24px;
165
+ letter-spacing: -0.0288px;
166
+ color: var(--color-text);
167
+ white-space: nowrap;
168
+ overflow: hidden;
169
+ text-overflow: ellipsis;
170
+ }
171
+ .fds-mismatch-card__rows {
172
+ display: flex;
173
+ flex-direction: column;
174
+ align-items: flex-start;
175
+ gap: var(--spacing-3);
176
+ width: 100%;
177
+ }
178
+ .fds-mismatch-card__row {
179
+ display: flex;
180
+ flex-direction: column;
181
+ align-items: flex-start;
182
+ gap: var(--spacing-2);
183
+ width: 100%;
184
+ border-bottom: 1px solid var(--color-border-strong);
185
+ }
186
+ .fds-mismatch-card__label {
187
+ width: 100%;
188
+ font-weight: var(--font-weight-medium);
189
+ font-size: 14px;
190
+ line-height: 22px;
191
+ color: var(--color-text-secondary);
192
+ }
193
+ .fds-mismatch-card__values {
194
+ display: flex;
195
+ justify-content: space-between;
196
+ width: 100%;
197
+ padding: var(--spacing-1) 0;
198
+ font-weight: var(--font-weight-medium);
199
+ font-size: 14px;
200
+ line-height: 22px;
201
+ white-space: nowrap;
202
+ }
203
+ .fds-mismatch-card__live {
204
+ color: var(--color-actual-text);
205
+ }
206
+ .fds-mismatch-card__design {
207
+ color: #262626;
208
+ }
209
+ .fds-mismatch-card__toggle {
210
+ display: flex;
211
+ align-items: center;
212
+ gap: 2px;
213
+ width: 320px;
214
+ padding: var(--spacing-2) 0;
215
+ border: 0;
216
+ background: none;
217
+ border-radius: var(--radius-md);
218
+ cursor: pointer;
219
+ font-family: var(--font-display);
220
+ font-weight: var(--font-weight-semibold);
221
+ font-size: 14px;
222
+ line-height: 20px;
223
+ color: var(--color-text-secondary);
224
+ }
225
+ .fds-mismatch-card__chevron {
226
+ flex-shrink: 0;
227
+ color: currentColor;
228
+ }
229
+ .fds-mismatch-card__chevron--up {
230
+ transform: rotate(180deg);
231
+ }
232
+ .fds-mismatch-card__toggle-label {
233
+ white-space: nowrap;
234
+ }
235
+
236
+ /* src/components/DiffSection/diff-section.css */
237
+ .fds-diff-section {
238
+ box-sizing: border-box;
239
+ display: flex;
240
+ flex-direction: column;
241
+ align-items: stretch;
242
+ width: 100%;
243
+ background: var(--color-surface-raised);
244
+ font-family: var(--font-body);
245
+ }
246
+ .fds-diff-section[data-expanded=true] {
247
+ gap: var(--spacing-3);
248
+ padding-bottom: 14px;
249
+ }
250
+ .fds-diff-section *,
251
+ .fds-diff-section *::before,
252
+ .fds-diff-section *::after {
253
+ box-sizing: border-box;
254
+ }
255
+ .fds-diff-section__header {
256
+ display: flex;
257
+ align-items: center;
258
+ justify-content: space-between;
259
+ width: 100%;
260
+ padding: var(--spacing-4) var(--spacing-4) var(--spacing-4) var(--spacing-6);
261
+ border: 0;
262
+ border-bottom: 1px solid var(--color-border);
263
+ background: #fafafa;
264
+ cursor: pointer;
265
+ text-align: left;
266
+ font-family: inherit;
267
+ }
268
+ .fds-diff-section[data-expanded=true] .fds-diff-section__header {
269
+ border-bottom-color: var(--color-border-light);
270
+ }
271
+ .fds-diff-section__head-left {
272
+ display: flex;
273
+ align-items: center;
274
+ gap: var(--spacing-2);
275
+ }
276
+ .fds-diff-section__chevron {
277
+ flex-shrink: 0;
278
+ color: var(--color-text-secondary);
279
+ transition: transform 150ms ease;
280
+ }
281
+ .fds-diff-section__chevron--open {
282
+ transform: rotate(90deg);
283
+ }
284
+ .fds-diff-section__number {
285
+ font-family: var(--font-mono);
286
+ font-weight: var(--font-weight-regular);
287
+ font-size: 12px;
288
+ line-height: 18px;
289
+ letter-spacing: -0.24px;
290
+ color: var(--color-text-secondary);
291
+ }
292
+ .fds-diff-section__name {
293
+ font-weight: var(--font-weight-medium);
294
+ font-size: 14px;
295
+ line-height: 22px;
296
+ color: var(--color-text);
297
+ }
298
+ .fds-diff-section__count {
299
+ font-weight: var(--font-weight-regular);
300
+ font-size: 14px;
301
+ line-height: 22px;
302
+ color: var(--color-text-secondary);
303
+ white-space: nowrap;
304
+ }
305
+ .fds-diff-section__body {
306
+ display: flex;
307
+ flex-direction: column;
308
+ gap: var(--spacing-3);
309
+ }
310
+ .fds-diff-section__element {
311
+ display: flex;
312
+ flex-direction: column;
313
+ gap: 14px;
314
+ padding: 6px var(--spacing-5) 6px var(--spacing-6);
315
+ }
316
+ .fds-diff-section__element-head {
317
+ display: flex;
318
+ align-items: center;
319
+ gap: var(--spacing-2);
320
+ }
321
+ .fds-diff-section__element-name {
322
+ font-weight: var(--font-weight-medium);
323
+ font-size: 14px;
324
+ line-height: 22px;
325
+ color: var(--color-text);
326
+ }
327
+ .fds-diff-section__element-count {
328
+ font-weight: var(--font-weight-regular);
329
+ font-size: 12px;
330
+ line-height: 18px;
331
+ color: var(--color-text-secondary);
332
+ }
333
+ .fds-diff-section__lines {
334
+ display: flex;
335
+ flex-direction: column;
336
+ padding-left: 18px;
337
+ border-left: 1px solid var(--color-border);
338
+ margin-left: 2px;
339
+ }
340
+ .fds-diff-section__line {
341
+ display: flex;
342
+ align-items: center;
343
+ justify-content: space-between;
344
+ width: 100%;
345
+ }
346
+ .fds-diff-section__prop {
347
+ font-family: var(--font-mono);
348
+ font-weight: var(--font-weight-regular);
349
+ font-size: 14px;
350
+ line-height: 22px;
351
+ letter-spacing: -0.28px;
352
+ color: var(--color-text-secondary);
353
+ white-space: nowrap;
354
+ }
355
+ .fds-diff-section__delta {
356
+ display: flex;
357
+ align-items: center;
358
+ gap: var(--spacing-1);
359
+ font-family: var(--font-mono);
360
+ font-weight: var(--font-weight-medium);
361
+ font-size: 14px;
362
+ line-height: 22px;
363
+ letter-spacing: -0.28px;
364
+ white-space: nowrap;
365
+ }
366
+ .fds-diff-section__live {
367
+ color: #ef4444;
368
+ }
369
+ .fds-diff-section__arrow {
370
+ flex-shrink: 0;
371
+ color: var(--color-text-muted);
372
+ }
373
+ .fds-diff-section__design {
374
+ color: #1e8902;
375
+ }
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as react from 'react';
2
- import { ButtonHTMLAttributes, ReactNode } from 'react';
2
+ import { ButtonHTMLAttributes, ReactNode, HTMLAttributes } from 'react';
3
3
 
4
4
  type ButtonVariant = 'primary' | 'outlined' | 'text' | 'neutral';
5
5
  type ButtonSize = 'x-small' | 'small' | 'medium' | 'big';
@@ -20,4 +20,84 @@ interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
20
20
  */
21
21
  declare function Button({ variant, size, leftIcon, rightIcon, type, className, children, ...rest }: ButtonProps): react.JSX.Element;
22
22
 
23
- export { Button, type ButtonProps, type ButtonSize, type ButtonVariant };
23
+ interface MismatchProperty {
24
+ /** Property label, e.g. "color", "font-size". */
25
+ label: string;
26
+ /**
27
+ * Value on the live page — what actually shipped. Rendered in the
28
+ * live/error register (red). Left column, per the Live → Design framing.
29
+ */
30
+ live: string;
31
+ /**
32
+ * Value in the design — the intended target. Rendered in the design
33
+ * register (neutral). Right column.
34
+ */
35
+ design: string;
36
+ }
37
+ interface MismatchCardProps extends HTMLAttributes<HTMLDivElement> {
38
+ /** Element title (its name or text). Single line — truncates with ellipsis. */
39
+ title: string;
40
+ /** The property mismatches to show. */
41
+ properties: MismatchProperty[];
42
+ /**
43
+ * How many rows are visible before the "Show more" toggle appears.
44
+ * @default 2
45
+ */
46
+ collapsedCount?: number;
47
+ /** Render expanded on first paint. @default false */
48
+ defaultExpanded?: boolean;
49
+ }
50
+ /**
51
+ * Fidel design-system MismatchCard — the card shown when hovering a
52
+ * highlighted mismatch region on a validation screenshot.
53
+ *
54
+ * Presentational only: it knows nothing about the diff taxonomy. Callers map
55
+ * their own data (e.g. a ValidationDiff) onto `title` + `properties`.
56
+ *
57
+ * Framing is Live → Design (founder decision 2026-04-24): the left value is
58
+ * what shipped (live), the right value is the design target. When there are
59
+ * more than `collapsedCount` properties, a Show more / Show less toggle
60
+ * reveals the rest.
61
+ */
62
+ declare function MismatchCard({ title, properties, collapsedCount, defaultExpanded, className, ...rest }: MismatchCardProps): react.JSX.Element;
63
+
64
+ interface DiffPropertyLine {
65
+ /** Property name, e.g. "font-size". Rendered in mono. */
66
+ property: string;
67
+ /** Value on the live page — what shipped. Left of the arrow (error register). */
68
+ live: string;
69
+ /** Value in the design — the target. Right of the arrow (success register). */
70
+ design: string;
71
+ }
72
+ interface DiffElement {
73
+ /** Element name, e.g. `"Pricing"`. */
74
+ name: string;
75
+ /** Property mismatches for this element. */
76
+ properties: DiffPropertyLine[];
77
+ }
78
+ interface DiffSectionProps extends HTMLAttributes<HTMLDivElement> {
79
+ /** Ordinal shown before the name, e.g. "01". Caller controls padding. */
80
+ number: string;
81
+ /** Section name, e.g. "Nav". */
82
+ name: string;
83
+ /**
84
+ * Count shown on the right of the header. Defaults to the total number of
85
+ * property lines across all elements.
86
+ */
87
+ mismatchCount?: number;
88
+ /** Elements in this section, each with its own property mismatches. */
89
+ elements: DiffElement[];
90
+ /** Render expanded on first paint. @default false */
91
+ defaultExpanded?: boolean;
92
+ }
93
+ /**
94
+ * Fidel design-system DiffSection — a numbered, collapsible section in the
95
+ * mismatch drawer. Collapsed it shows just the header (`01 Nav · 2
96
+ * mismatches`); expanded it lists each element and its property deltas.
97
+ *
98
+ * Presentational only — no diff taxonomy. Framing is Live → Design (founder
99
+ * decision 2026-04-24): each delta reads `live → design`.
100
+ */
101
+ declare function DiffSection({ number, name, mismatchCount, elements, defaultExpanded, className, ...rest }: DiffSectionProps): react.JSX.Element;
102
+
103
+ export { Button, type ButtonProps, type ButtonSize, type ButtonVariant, type DiffElement, type DiffPropertyLine, DiffSection, type DiffSectionProps, MismatchCard, type MismatchCardProps, type MismatchProperty };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as react from 'react';
2
- import { ButtonHTMLAttributes, ReactNode } from 'react';
2
+ import { ButtonHTMLAttributes, ReactNode, HTMLAttributes } from 'react';
3
3
 
4
4
  type ButtonVariant = 'primary' | 'outlined' | 'text' | 'neutral';
5
5
  type ButtonSize = 'x-small' | 'small' | 'medium' | 'big';
@@ -20,4 +20,84 @@ interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
20
20
  */
21
21
  declare function Button({ variant, size, leftIcon, rightIcon, type, className, children, ...rest }: ButtonProps): react.JSX.Element;
22
22
 
23
- export { Button, type ButtonProps, type ButtonSize, type ButtonVariant };
23
+ interface MismatchProperty {
24
+ /** Property label, e.g. "color", "font-size". */
25
+ label: string;
26
+ /**
27
+ * Value on the live page — what actually shipped. Rendered in the
28
+ * live/error register (red). Left column, per the Live → Design framing.
29
+ */
30
+ live: string;
31
+ /**
32
+ * Value in the design — the intended target. Rendered in the design
33
+ * register (neutral). Right column.
34
+ */
35
+ design: string;
36
+ }
37
+ interface MismatchCardProps extends HTMLAttributes<HTMLDivElement> {
38
+ /** Element title (its name or text). Single line — truncates with ellipsis. */
39
+ title: string;
40
+ /** The property mismatches to show. */
41
+ properties: MismatchProperty[];
42
+ /**
43
+ * How many rows are visible before the "Show more" toggle appears.
44
+ * @default 2
45
+ */
46
+ collapsedCount?: number;
47
+ /** Render expanded on first paint. @default false */
48
+ defaultExpanded?: boolean;
49
+ }
50
+ /**
51
+ * Fidel design-system MismatchCard — the card shown when hovering a
52
+ * highlighted mismatch region on a validation screenshot.
53
+ *
54
+ * Presentational only: it knows nothing about the diff taxonomy. Callers map
55
+ * their own data (e.g. a ValidationDiff) onto `title` + `properties`.
56
+ *
57
+ * Framing is Live → Design (founder decision 2026-04-24): the left value is
58
+ * what shipped (live), the right value is the design target. When there are
59
+ * more than `collapsedCount` properties, a Show more / Show less toggle
60
+ * reveals the rest.
61
+ */
62
+ declare function MismatchCard({ title, properties, collapsedCount, defaultExpanded, className, ...rest }: MismatchCardProps): react.JSX.Element;
63
+
64
+ interface DiffPropertyLine {
65
+ /** Property name, e.g. "font-size". Rendered in mono. */
66
+ property: string;
67
+ /** Value on the live page — what shipped. Left of the arrow (error register). */
68
+ live: string;
69
+ /** Value in the design — the target. Right of the arrow (success register). */
70
+ design: string;
71
+ }
72
+ interface DiffElement {
73
+ /** Element name, e.g. `"Pricing"`. */
74
+ name: string;
75
+ /** Property mismatches for this element. */
76
+ properties: DiffPropertyLine[];
77
+ }
78
+ interface DiffSectionProps extends HTMLAttributes<HTMLDivElement> {
79
+ /** Ordinal shown before the name, e.g. "01". Caller controls padding. */
80
+ number: string;
81
+ /** Section name, e.g. "Nav". */
82
+ name: string;
83
+ /**
84
+ * Count shown on the right of the header. Defaults to the total number of
85
+ * property lines across all elements.
86
+ */
87
+ mismatchCount?: number;
88
+ /** Elements in this section, each with its own property mismatches. */
89
+ elements: DiffElement[];
90
+ /** Render expanded on first paint. @default false */
91
+ defaultExpanded?: boolean;
92
+ }
93
+ /**
94
+ * Fidel design-system DiffSection — a numbered, collapsible section in the
95
+ * mismatch drawer. Collapsed it shows just the header (`01 Nav · 2
96
+ * mismatches`); expanded it lists each element and its property deltas.
97
+ *
98
+ * Presentational only — no diff taxonomy. Framing is Live → Design (founder
99
+ * decision 2026-04-24): each delta reads `live → design`.
100
+ */
101
+ declare function DiffSection({ number, name, mismatchCount, elements, defaultExpanded, className, ...rest }: DiffSectionProps): react.JSX.Element;
102
+
103
+ export { Button, type ButtonProps, type ButtonSize, type ButtonVariant, type DiffElement, type DiffPropertyLine, DiffSection, type DiffSectionProps, MismatchCard, type MismatchCardProps, type MismatchProperty };
package/dist/index.js CHANGED
@@ -22,6 +22,172 @@ function Button({
22
22
  rightIcon != null && /* @__PURE__ */ jsx("span", { className: "fds-button__icon", "aria-hidden": "true", children: rightIcon })
23
23
  ] });
24
24
  }
25
+
26
+ // src/components/MismatchCard/MismatchCard.tsx
27
+ import { useState } from "react";
28
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
29
+ function MismatchCard({
30
+ title,
31
+ properties,
32
+ collapsedCount = 2,
33
+ defaultExpanded = false,
34
+ className,
35
+ ...rest
36
+ }) {
37
+ const [expanded, setExpanded] = useState(defaultExpanded);
38
+ const canToggle = properties.length > collapsedCount;
39
+ const visible = expanded || !canToggle ? properties : properties.slice(0, collapsedCount);
40
+ const classes = ["fds-mismatch-card", className].filter(Boolean).join(" ");
41
+ return /* @__PURE__ */ jsxs2("div", { className: classes, ...rest, children: [
42
+ /* @__PURE__ */ jsxs2("div", { className: "fds-mismatch-card__content", children: [
43
+ /* @__PURE__ */ jsx2("p", { className: "fds-mismatch-card__title", title, children: title }),
44
+ /* @__PURE__ */ jsx2("div", { className: "fds-mismatch-card__rows", children: visible.map((prop, i) => /* @__PURE__ */ jsxs2("div", { className: "fds-mismatch-card__row", children: [
45
+ /* @__PURE__ */ jsx2("span", { className: "fds-mismatch-card__label", children: prop.label }),
46
+ /* @__PURE__ */ jsxs2("div", { className: "fds-mismatch-card__values", children: [
47
+ /* @__PURE__ */ jsx2("span", { className: "fds-mismatch-card__live", children: prop.live }),
48
+ /* @__PURE__ */ jsx2("span", { className: "fds-mismatch-card__design", children: prop.design })
49
+ ] })
50
+ ] }, `${prop.label}-${i}`)) })
51
+ ] }),
52
+ canToggle && /* @__PURE__ */ jsxs2(
53
+ "button",
54
+ {
55
+ type: "button",
56
+ className: "fds-mismatch-card__toggle",
57
+ "aria-expanded": expanded,
58
+ onClick: () => setExpanded((v) => !v),
59
+ children: [
60
+ /* @__PURE__ */ jsx2(Chevron, { direction: expanded ? "up" : "down" }),
61
+ /* @__PURE__ */ jsx2("span", { className: "fds-mismatch-card__toggle-label", children: expanded ? "Show less" : "Show more" })
62
+ ]
63
+ }
64
+ )
65
+ ] });
66
+ }
67
+ function Chevron({ direction }) {
68
+ return /* @__PURE__ */ jsx2(
69
+ "svg",
70
+ {
71
+ className: `fds-mismatch-card__chevron fds-mismatch-card__chevron--${direction}`,
72
+ width: "20",
73
+ height: "20",
74
+ viewBox: "0 0 20 20",
75
+ fill: "none",
76
+ "aria-hidden": "true",
77
+ children: /* @__PURE__ */ jsx2(
78
+ "path",
79
+ {
80
+ d: "M6 8.5 10 12l4-3.5",
81
+ stroke: "currentColor",
82
+ strokeWidth: "1.6",
83
+ strokeLinecap: "round",
84
+ strokeLinejoin: "round"
85
+ }
86
+ )
87
+ }
88
+ );
89
+ }
90
+
91
+ // src/components/DiffSection/DiffSection.tsx
92
+ import { useState as useState2 } from "react";
93
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
94
+ function plural(n, one, many) {
95
+ return `${n} ${n === 1 ? one : many}`;
96
+ }
97
+ function DiffSection({
98
+ number,
99
+ name,
100
+ mismatchCount,
101
+ elements,
102
+ defaultExpanded = false,
103
+ className,
104
+ ...rest
105
+ }) {
106
+ const [expanded, setExpanded] = useState2(defaultExpanded);
107
+ const count = mismatchCount ?? elements.reduce((sum, el) => sum + el.properties.length, 0);
108
+ const classes = ["fds-diff-section", className].filter(Boolean).join(" ");
109
+ return /* @__PURE__ */ jsxs3("div", { className: classes, "data-expanded": expanded ? "true" : void 0, ...rest, children: [
110
+ /* @__PURE__ */ jsxs3(
111
+ "button",
112
+ {
113
+ type: "button",
114
+ className: "fds-diff-section__header",
115
+ "aria-expanded": expanded,
116
+ onClick: () => setExpanded((v) => !v),
117
+ children: [
118
+ /* @__PURE__ */ jsxs3("span", { className: "fds-diff-section__head-left", children: [
119
+ /* @__PURE__ */ jsx3(Chevron2, { open: expanded }),
120
+ /* @__PURE__ */ jsx3("span", { className: "fds-diff-section__number", children: number }),
121
+ /* @__PURE__ */ jsx3("span", { className: "fds-diff-section__name", children: name })
122
+ ] }),
123
+ /* @__PURE__ */ jsx3("span", { className: "fds-diff-section__count", children: plural(count, "mismatch", "mismatches") })
124
+ ]
125
+ }
126
+ ),
127
+ expanded && /* @__PURE__ */ jsx3("div", { className: "fds-diff-section__body", children: elements.map((el, ei) => /* @__PURE__ */ jsxs3("div", { className: "fds-diff-section__element", children: [
128
+ /* @__PURE__ */ jsxs3("div", { className: "fds-diff-section__element-head", children: [
129
+ /* @__PURE__ */ jsx3("span", { className: "fds-diff-section__element-name", children: el.name }),
130
+ /* @__PURE__ */ jsx3("span", { className: "fds-diff-section__element-count", children: plural(el.properties.length, "property", "properties") })
131
+ ] }),
132
+ /* @__PURE__ */ jsx3("div", { className: "fds-diff-section__lines", children: el.properties.map((line, li) => /* @__PURE__ */ jsxs3("div", { className: "fds-diff-section__line", children: [
133
+ /* @__PURE__ */ jsx3("span", { className: "fds-diff-section__prop", children: line.property }),
134
+ /* @__PURE__ */ jsxs3("span", { className: "fds-diff-section__delta", children: [
135
+ /* @__PURE__ */ jsx3("span", { className: "fds-diff-section__live", children: line.live }),
136
+ /* @__PURE__ */ jsx3(Arrow, {}),
137
+ /* @__PURE__ */ jsx3("span", { className: "fds-diff-section__design", children: line.design })
138
+ ] })
139
+ ] }, `${line.property}-${li}`)) })
140
+ ] }, `${el.name}-${ei}`)) })
141
+ ] });
142
+ }
143
+ function Chevron2({ open }) {
144
+ return /* @__PURE__ */ jsx3(
145
+ "svg",
146
+ {
147
+ className: `fds-diff-section__chevron${open ? " fds-diff-section__chevron--open" : ""}`,
148
+ width: "12",
149
+ height: "12",
150
+ viewBox: "0 0 12 12",
151
+ fill: "none",
152
+ "aria-hidden": "true",
153
+ children: /* @__PURE__ */ jsx3(
154
+ "path",
155
+ {
156
+ d: "M4.5 3 8 6l-3.5 3",
157
+ stroke: "currentColor",
158
+ strokeWidth: "1.3",
159
+ strokeLinecap: "round",
160
+ strokeLinejoin: "round"
161
+ }
162
+ )
163
+ }
164
+ );
165
+ }
166
+ function Arrow() {
167
+ return /* @__PURE__ */ jsx3(
168
+ "svg",
169
+ {
170
+ className: "fds-diff-section__arrow",
171
+ width: "10",
172
+ height: "10",
173
+ viewBox: "0 0 10 10",
174
+ fill: "none",
175
+ "aria-hidden": "true",
176
+ children: /* @__PURE__ */ jsx3(
177
+ "path",
178
+ {
179
+ d: "M1.5 5h6.5M5.5 2.5 8 5 5.5 7.5",
180
+ stroke: "currentColor",
181
+ strokeWidth: "1",
182
+ strokeLinecap: "round",
183
+ strokeLinejoin: "round"
184
+ }
185
+ )
186
+ }
187
+ );
188
+ }
25
189
  export {
26
- Button
190
+ Button,
191
+ DiffSection,
192
+ MismatchCard
27
193
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@usefidel/design-system",
3
- "version": "0.2.0",
4
- "description": "Fidel brand design tokens + React component library — DTCG token source of truth, generated per-surface CSS + Tailwind-v4 parser-compatibility output, and on-brand React components.",
3
+ "version": "0.3.0",
4
+ "description": "Fidel brand design tokens + React component library — DTCG token source of truth, generated per-surface CSS + Tailwind-v4 parser-compatibility output, and on-brand React components (Button, MismatchCard, DiffSection).",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
7
7
  "module": "./dist/index.js",