@usefidel/design-system 0.7.0 → 0.9.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/dist/admin.css CHANGED
@@ -1,8 +1,8 @@
1
1
  /* GENERATED by @usefidel/design-system — do not edit. Surface: admin. */
2
2
  :root {
3
3
  --color-bg: #ffffff;
4
- --color-surface: #fff7ed;
5
- --color-surface-hover: #ffedd5;
4
+ --color-surface: #f5f5f5;
5
+ --color-surface-hover: #ececec;
6
6
  --color-surface-raised: #ffffff;
7
7
  --color-text: #171717;
8
8
  --color-text-secondary: #525252;
@@ -47,11 +47,14 @@
47
47
  --font-display: 'Rethink Sans', system-ui, -apple-system, sans-serif;
48
48
  --font-body: 'Rethink Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
49
49
  --font-mono: 'JetBrains Mono', 'SF Mono', 'Fira Code', 'Cascadia Code', monospace;
50
- --radius-xs: 4px;
51
- --radius-sm: 6px;
52
- --radius-md: 8px;
53
- --radius-lg: 12px;
54
- --radius-xl: 16px;
50
+ --radius-none: 0px;
51
+ --radius-xs: 2px;
52
+ --radius-sm: 4px;
53
+ --radius-md: 6px;
54
+ --radius-lg: 8px;
55
+ --radius-xl: 12px;
56
+ --radius-2xl: 16px;
57
+ --radius-3xl: 24px;
55
58
  --radius-pill: 9999px;
56
59
  --radius-full: 9999px;
57
60
  --spacing-1: 4px;
@@ -47,11 +47,14 @@
47
47
  --font-display: 'Rethink Sans', system-ui, -apple-system, sans-serif;
48
48
  --font-body: 'Rethink Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
49
49
  --font-mono: 'JetBrains Mono', 'SF Mono', 'Fira Code', 'Cascadia Code', monospace;
50
- --radius-xs: 4px;
51
- --radius-sm: 6px;
52
- --radius-md: 8px;
53
- --radius-lg: 12px;
54
- --radius-xl: 16px;
50
+ --radius-none: 0px;
51
+ --radius-xs: 2px;
52
+ --radius-sm: 4px;
53
+ --radius-md: 6px;
54
+ --radius-lg: 8px;
55
+ --radius-xl: 12px;
56
+ --radius-2xl: 16px;
57
+ --radius-3xl: 24px;
55
58
  --radius-pill: 20px;
56
59
  --radius-full: 9999px;
57
60
  --spacing-1: 4px;
@@ -10,6 +10,6 @@
10
10
  "webapp.css"
11
11
  ],
12
12
  "storybookUrl": null,
13
- "packageVersion": "0.7.0",
14
- "resolvedSha": "e51d3196d9780b87d0fd4f00db40506a6288ab8c"
13
+ "packageVersion": "0.9.0",
14
+ "resolvedSha": "e41a4c4ba174007088f26caf33a5d2c8ddd43094"
15
15
  }
package/dist/icons.cjs ADDED
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __copyProps = (to, from, except, desc) => {
7
+ if (from && typeof from === "object" || typeof from === "function") {
8
+ for (let key of __getOwnPropNames(from))
9
+ if (!__hasOwnProp.call(to, key) && key !== except)
10
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
11
+ }
12
+ return to;
13
+ };
14
+ var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
15
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
16
+
17
+ // src/icons.ts
18
+ var icons_exports = {};
19
+ module.exports = __toCommonJS(icons_exports);
20
+ __reExport(icons_exports, require("lucide-react"), module.exports);
21
+ // Annotate the CommonJS export names for ESM import in node:
22
+ 0 && (module.exports = {
23
+ ...require("lucide-react")
24
+ });
@@ -0,0 +1 @@
1
+ export * from 'lucide-react';
@@ -0,0 +1 @@
1
+ export * from 'lucide-react';
package/dist/icons.js ADDED
@@ -0,0 +1,2 @@
1
+ // src/icons.ts
2
+ export * from "lucide-react";
package/dist/index.cjs CHANGED
@@ -23,7 +23,9 @@ __export(index_exports, {
23
23
  Button: () => Button,
24
24
  DiffPropertyList: () => DiffPropertyList,
25
25
  DiffSection: () => DiffSection,
26
- MismatchCard: () => MismatchCard
26
+ MismatchCard: () => MismatchCard,
27
+ Select: () => Select,
28
+ Toggle: () => Toggle
27
29
  });
28
30
  module.exports = __toCommonJS(index_exports);
29
31
 
@@ -282,10 +284,274 @@ function Arrow() {
282
284
  }
283
285
  );
284
286
  }
287
+
288
+ // src/components/Select/Select.tsx
289
+ var import_react3 = require("react");
290
+ var import_jsx_runtime4 = require("react/jsx-runtime");
291
+ function ChevronIcon() {
292
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("svg", { viewBox: "0 0 20 20", width: "18", height: "18", fill: "none", "aria-hidden": "true", className: "fds-select__chevronIcon", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M6 8l4 4 4-4", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round", strokeLinejoin: "round" }) });
293
+ }
294
+ function CheckIcon() {
295
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("svg", { viewBox: "0 0 20 20", width: "18", height: "18", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M4.5 10.5l3 3 8-8.5", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round" }) });
296
+ }
297
+ var Select = (0, import_react3.forwardRef)(function Select2({
298
+ options,
299
+ value,
300
+ onChange,
301
+ placeholder = "Select\u2026",
302
+ label,
303
+ hint,
304
+ error,
305
+ disabled = false,
306
+ searchable = false,
307
+ searchPlaceholder = "Search\u2026",
308
+ leadingIcon,
309
+ id,
310
+ name,
311
+ className,
312
+ containerClassName
313
+ }, ref) {
314
+ const genId = (0, import_react3.useId)();
315
+ const triggerId = id ?? genId;
316
+ const listId = `${genId}-list`;
317
+ const msgId = `${genId}-msg`;
318
+ const rootRef = (0, import_react3.useRef)(null);
319
+ const triggerRef = (0, import_react3.useRef)(null);
320
+ const listRef = (0, import_react3.useRef)(null);
321
+ const searchRef = (0, import_react3.useRef)(null);
322
+ (0, import_react3.useImperativeHandle)(ref, () => triggerRef.current, []);
323
+ const [open, setOpen] = (0, import_react3.useState)(false);
324
+ const [query, setQuery] = (0, import_react3.useState)("");
325
+ const [activeIndex, setActiveIndex] = (0, import_react3.useState)(-1);
326
+ const selected = options.find((o) => o.value === value) ?? null;
327
+ const filtered = (0, import_react3.useMemo)(() => {
328
+ if (!searchable || !query.trim()) return options;
329
+ const q = query.trim().toLowerCase();
330
+ return options.filter(
331
+ (o) => o.label.toLowerCase().includes(q) || (o.secondary?.toLowerCase().includes(q) ?? false)
332
+ );
333
+ }, [options, searchable, query]);
334
+ const hasMessage = Boolean(error ?? hint);
335
+ const firstEnabled = (list) => list.findIndex((o) => !o.disabled);
336
+ (0, import_react3.useEffect)(() => {
337
+ if (!open) return;
338
+ const onDown = (e) => {
339
+ if (rootRef.current && !rootRef.current.contains(e.target)) setOpen(false);
340
+ };
341
+ document.addEventListener("mousedown", onDown);
342
+ return () => document.removeEventListener("mousedown", onDown);
343
+ }, [open]);
344
+ (0, import_react3.useEffect)(() => {
345
+ if (!open) {
346
+ setQuery("");
347
+ return;
348
+ }
349
+ const selIdx = filtered.findIndex((o) => o.value === value);
350
+ setActiveIndex(selIdx >= 0 ? selIdx : firstEnabled(filtered));
351
+ const raf = requestAnimationFrame(() => {
352
+ if (searchable) searchRef.current?.focus();
353
+ else listRef.current?.focus();
354
+ });
355
+ return () => cancelAnimationFrame(raf);
356
+ }, [open]);
357
+ const close = () => {
358
+ setOpen(false);
359
+ triggerRef.current?.focus();
360
+ };
361
+ const move = (dir) => {
362
+ setActiveIndex((prev) => {
363
+ if (filtered.length === 0) return -1;
364
+ let i = prev;
365
+ for (let k = 0; k < filtered.length; k++) {
366
+ i = (i + dir + filtered.length) % filtered.length;
367
+ if (!filtered[i].disabled) return i;
368
+ }
369
+ return prev;
370
+ });
371
+ };
372
+ const choose = (i) => {
373
+ const o = filtered[i];
374
+ if (o && !o.disabled) {
375
+ onChange?.(o.value);
376
+ close();
377
+ }
378
+ };
379
+ const onKeyDown = (e) => {
380
+ if (!open) {
381
+ if (e.key === "ArrowDown" || e.key === "ArrowUp" || e.key === "Enter" || e.key === " ") {
382
+ e.preventDefault();
383
+ setOpen(true);
384
+ }
385
+ return;
386
+ }
387
+ if (e.key === "ArrowDown") {
388
+ e.preventDefault();
389
+ move(1);
390
+ } else if (e.key === "ArrowUp") {
391
+ e.preventDefault();
392
+ move(-1);
393
+ } else if (e.key === "Home") {
394
+ e.preventDefault();
395
+ setActiveIndex(firstEnabled(filtered));
396
+ } else if (e.key === "End") {
397
+ e.preventDefault();
398
+ for (let i = filtered.length - 1; i >= 0; i--) {
399
+ if (!filtered[i].disabled) {
400
+ setActiveIndex(i);
401
+ break;
402
+ }
403
+ }
404
+ } else if (e.key === "Enter") {
405
+ e.preventDefault();
406
+ choose(activeIndex);
407
+ } else if (e.key === "Escape") {
408
+ e.preventDefault();
409
+ close();
410
+ }
411
+ };
412
+ const triggerClasses = [
413
+ "fds-select__trigger",
414
+ error ? "fds-select__trigger--error" : "",
415
+ open ? "fds-select__trigger--open" : "",
416
+ className
417
+ ].filter(Boolean).join(" ");
418
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
419
+ "div",
420
+ {
421
+ ref: rootRef,
422
+ className: ["fds-select-field", containerClassName].filter(Boolean).join(" "),
423
+ children: [
424
+ label != null && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("label", { htmlFor: triggerId, className: "fds-select__label", children: label }),
425
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "fds-select", children: [
426
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
427
+ "button",
428
+ {
429
+ type: "button",
430
+ id: triggerId,
431
+ ref: triggerRef,
432
+ className: triggerClasses,
433
+ disabled,
434
+ "aria-haspopup": "listbox",
435
+ "aria-expanded": open,
436
+ "aria-controls": open ? listId : void 0,
437
+ "aria-invalid": error ? true : void 0,
438
+ "aria-describedby": hasMessage ? msgId : void 0,
439
+ onClick: () => !disabled && setOpen((o) => !o),
440
+ onKeyDown,
441
+ children: [
442
+ (selected?.avatar ?? leadingIcon) != null && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "fds-select__lead", children: selected?.avatar ?? leadingIcon }),
443
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
444
+ "span",
445
+ {
446
+ className: `fds-select__value${selected ? "" : " fds-select__value--placeholder"}`,
447
+ children: selected ? selected.label : placeholder
448
+ }
449
+ ),
450
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "fds-select__chevron", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ChevronIcon, {}) })
451
+ ]
452
+ }
453
+ ),
454
+ open && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "fds-select__popover", children: [
455
+ searchable && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "fds-select__search", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
456
+ "input",
457
+ {
458
+ ref: searchRef,
459
+ className: "fds-select__searchInput",
460
+ placeholder: searchPlaceholder,
461
+ value: query,
462
+ onChange: (e) => setQuery(e.target.value),
463
+ onKeyDown,
464
+ role: "combobox",
465
+ "aria-expanded": true,
466
+ "aria-controls": listId,
467
+ "aria-autocomplete": "list"
468
+ }
469
+ ) }),
470
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
471
+ "div",
472
+ {
473
+ ref: listRef,
474
+ id: listId,
475
+ role: "listbox",
476
+ tabIndex: searchable ? -1 : 0,
477
+ className: "fds-select__list",
478
+ "aria-activedescendant": activeIndex >= 0 ? `${listId}-opt-${activeIndex}` : void 0,
479
+ onKeyDown: searchable ? void 0 : onKeyDown,
480
+ children: [
481
+ filtered.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "fds-select__empty", children: "No results" }),
482
+ filtered.map((o, i) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
483
+ "div",
484
+ {
485
+ id: `${listId}-opt-${i}`,
486
+ role: "option",
487
+ "aria-selected": o.value === value,
488
+ "aria-disabled": o.disabled || void 0,
489
+ className: [
490
+ "fds-select__option",
491
+ i === activeIndex ? "fds-select__option--active" : "",
492
+ o.disabled ? "fds-select__option--disabled" : ""
493
+ ].filter(Boolean).join(" "),
494
+ onMouseEnter: () => !o.disabled && setActiveIndex(i),
495
+ onMouseDown: (e) => e.preventDefault(),
496
+ onClick: () => choose(i),
497
+ children: [
498
+ o.avatar != null && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "fds-select__optAvatar", children: o.avatar }),
499
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "fds-select__optText", children: [
500
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "fds-select__optLabel", children: o.label }),
501
+ o.secondary != null && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "fds-select__optSecondary", children: o.secondary })
502
+ ] }),
503
+ o.value === value && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "fds-select__check", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(CheckIcon, {}) })
504
+ ]
505
+ },
506
+ o.value
507
+ ))
508
+ ]
509
+ }
510
+ )
511
+ ] })
512
+ ] }),
513
+ hasMessage && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { id: msgId, className: error ? "fds-select__error" : "fds-select__hint", children: error ?? hint }),
514
+ name != null && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("input", { type: "hidden", name, value: value ?? "" })
515
+ ]
516
+ }
517
+ );
518
+ });
519
+
520
+ // src/components/Toggle/Toggle.tsx
521
+ var import_react4 = require("react");
522
+ var import_jsx_runtime5 = require("react/jsx-runtime");
523
+ var Toggle = (0, import_react4.forwardRef)(function Toggle2({ size = "medium", label, description, className, id, disabled, ...rest }, ref) {
524
+ const generatedId = (0, import_react4.useId)();
525
+ const inputId = id ?? generatedId;
526
+ const classes = ["fds-toggle", `fds-toggle--${size}`, className].filter(Boolean).join(" ");
527
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: classes, "data-disabled": disabled ? "" : void 0, children: [
528
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
529
+ "input",
530
+ {
531
+ ...rest,
532
+ id: inputId,
533
+ ref,
534
+ type: "checkbox",
535
+ role: "switch",
536
+ disabled,
537
+ className: "fds-toggle__input"
538
+ }
539
+ ),
540
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("label", { htmlFor: inputId, className: "fds-toggle__control", children: [
541
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "fds-toggle__track", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "fds-toggle__thumb" }) }),
542
+ (label != null || description != null) && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "fds-toggle__text", children: [
543
+ label != null && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "fds-toggle__label", children: label }),
544
+ description != null && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "fds-toggle__description", children: description })
545
+ ] })
546
+ ] })
547
+ ] });
548
+ });
285
549
  // Annotate the CommonJS export names for ESM import in node:
286
550
  0 && (module.exports = {
287
551
  Button,
288
552
  DiffPropertyList,
289
553
  DiffSection,
290
- MismatchCard
554
+ MismatchCard,
555
+ Select,
556
+ Toggle
291
557
  });
package/dist/index.css CHANGED
@@ -416,3 +416,285 @@
416
416
  .fds-diff-section__design {
417
417
  color: #1e8902;
418
418
  }
419
+
420
+ /* src/components/Select/select.css */
421
+ .fds-select-field {
422
+ display: flex;
423
+ flex-direction: column;
424
+ gap: var(--spacing-2, 8px);
425
+ width: 100%;
426
+ }
427
+ .fds-select__label {
428
+ font-family: var(--font-display, "Rethink Sans", system-ui, sans-serif);
429
+ font-size: var(--font-size-label-sm, 14px);
430
+ line-height: var(--line-height-label-sm, 22px);
431
+ font-weight: var(--font-weight-medium, 500);
432
+ color: var(--color-text, #171717);
433
+ }
434
+ .fds-select {
435
+ position: relative;
436
+ width: 100%;
437
+ }
438
+ .fds-select__trigger {
439
+ display: flex;
440
+ align-items: center;
441
+ gap: var(--spacing-2, 8px);
442
+ width: 100%;
443
+ min-height: 44px;
444
+ padding: 0 var(--spacing-3, 12px);
445
+ background: var(--color-surface-raised, #ffffff);
446
+ border: 1px solid var(--color-border-strong, #d4d4d4);
447
+ border-radius: var(--radius-md, 6px);
448
+ font-family: var(--font-body, "Rethink Sans", system-ui, sans-serif);
449
+ font-size: var(--font-size-text-md, 16px);
450
+ color: var(--color-text, #171717);
451
+ cursor: pointer;
452
+ text-align: left;
453
+ transition: border-color var(--duration-instant, 80ms) ease, box-shadow var(--duration-instant, 80ms) ease;
454
+ }
455
+ .fds-select__trigger:focus-visible,
456
+ .fds-select__trigger--open {
457
+ outline: none;
458
+ border-color: var(--color-accent, #ea580c);
459
+ box-shadow: inset 0 0 0 1px var(--color-accent, #ea580c);
460
+ }
461
+ .fds-select__trigger--error {
462
+ border-color: var(--color-error, #b91c1c);
463
+ }
464
+ .fds-select__trigger--error.fds-select__trigger--open,
465
+ .fds-select__trigger--error:focus-visible {
466
+ box-shadow: inset 0 0 0 1px var(--color-error, #b91c1c);
467
+ }
468
+ .fds-select__trigger:disabled {
469
+ background: var(--color-surface-muted, #fafafa);
470
+ color: var(--color-text-muted, #a3a3a3);
471
+ cursor: not-allowed;
472
+ }
473
+ .fds-select__lead {
474
+ display: inline-flex;
475
+ align-items: center;
476
+ flex-shrink: 0;
477
+ }
478
+ .fds-select__value {
479
+ flex: 1 1 auto;
480
+ min-width: 0;
481
+ overflow: hidden;
482
+ text-overflow: ellipsis;
483
+ white-space: nowrap;
484
+ }
485
+ .fds-select__value--placeholder {
486
+ color: var(--color-text-muted, #a3a3a3);
487
+ }
488
+ .fds-select__chevron {
489
+ display: inline-flex;
490
+ align-items: center;
491
+ flex-shrink: 0;
492
+ color: var(--color-text-muted, #a3a3a3);
493
+ }
494
+ .fds-select__popover {
495
+ position: absolute;
496
+ top: calc(100% + 4px);
497
+ left: 0;
498
+ right: 0;
499
+ z-index: 50;
500
+ background: var(--color-surface-raised, #ffffff);
501
+ border: 1px solid var(--color-border, #e5e5e5);
502
+ border-radius: var(--radius-md, 6px);
503
+ box-shadow: var(--shadow-lg, 0 4px 8px rgba(0, 0, 0, 0.06), 0 16px 40px rgba(0, 0, 0, 0.1));
504
+ overflow: hidden;
505
+ }
506
+ .fds-select__search {
507
+ padding: var(--spacing-2, 8px);
508
+ border-bottom: 1px solid var(--color-border-light, #f5f5f5);
509
+ }
510
+ .fds-select__searchInput {
511
+ width: 100%;
512
+ padding: 8px var(--spacing-3, 12px);
513
+ border: 1px solid var(--color-border-strong, #d4d4d4);
514
+ border-radius: var(--radius-sm, 4px);
515
+ font-family: var(--font-body, "Rethink Sans", system-ui, sans-serif);
516
+ font-size: var(--font-size-text-sm, 14px);
517
+ color: var(--color-text, #171717);
518
+ }
519
+ .fds-select__searchInput:focus {
520
+ outline: none;
521
+ border-color: var(--color-accent, #ea580c);
522
+ box-shadow: inset 0 0 0 1px var(--color-accent, #ea580c);
523
+ }
524
+ .fds-select__list {
525
+ max-height: 280px;
526
+ overflow-y: auto;
527
+ padding: var(--spacing-1, 4px);
528
+ }
529
+ .fds-select__list:focus {
530
+ outline: none;
531
+ }
532
+ .fds-select__empty {
533
+ padding: var(--spacing-3, 12px);
534
+ font-family: var(--font-body, "Rethink Sans", system-ui, sans-serif);
535
+ font-size: var(--font-size-text-sm, 14px);
536
+ color: var(--color-text-muted, #a3a3a3);
537
+ text-align: center;
538
+ }
539
+ .fds-select__option {
540
+ display: flex;
541
+ align-items: center;
542
+ gap: var(--spacing-2, 8px);
543
+ padding: 8px var(--spacing-2, 8px);
544
+ border-radius: var(--radius-sm, 4px);
545
+ cursor: pointer;
546
+ }
547
+ .fds-select__option--active {
548
+ background: var(--color-surface, #fff7ed);
549
+ }
550
+ .fds-select__option--disabled {
551
+ opacity: 0.5;
552
+ cursor: not-allowed;
553
+ }
554
+ .fds-select__optAvatar {
555
+ display: inline-flex;
556
+ align-items: center;
557
+ flex-shrink: 0;
558
+ color: var(--color-text-muted, #a3a3a3);
559
+ }
560
+ .fds-select__optText {
561
+ display: inline-flex;
562
+ align-items: baseline;
563
+ gap: 6px;
564
+ min-width: 0;
565
+ flex: 1 1 auto;
566
+ }
567
+ .fds-select__optLabel {
568
+ font-family: var(--font-body, "Rethink Sans", system-ui, sans-serif);
569
+ font-size: var(--font-size-text-sm, 14px);
570
+ color: var(--color-text, #171717);
571
+ white-space: nowrap;
572
+ overflow: hidden;
573
+ text-overflow: ellipsis;
574
+ }
575
+ .fds-select__optSecondary {
576
+ font-family: var(--font-body, "Rethink Sans", system-ui, sans-serif);
577
+ font-size: var(--font-size-text-sm, 14px);
578
+ color: var(--color-text-muted, #a3a3a3);
579
+ white-space: nowrap;
580
+ }
581
+ .fds-select__check {
582
+ display: inline-flex;
583
+ align-items: center;
584
+ flex-shrink: 0;
585
+ color: var(--color-accent, #ea580c);
586
+ }
587
+ .fds-select__hint,
588
+ .fds-select__error {
589
+ font-family: var(--font-body, "Rethink Sans", system-ui, sans-serif);
590
+ font-size: var(--font-size-text-xs, 12px);
591
+ line-height: var(--line-height-text-xs, 18px);
592
+ }
593
+ .fds-select__hint {
594
+ color: var(--color-text-muted, #a3a3a3);
595
+ }
596
+ .fds-select__error {
597
+ color: var(--color-error, #b91c1c);
598
+ }
599
+
600
+ /* src/components/Toggle/toggle.css */
601
+ .fds-toggle {
602
+ display: inline-flex;
603
+ position: relative;
604
+ }
605
+ .fds-toggle__input {
606
+ position: absolute;
607
+ top: 0;
608
+ left: 0;
609
+ width: 100%;
610
+ height: 100%;
611
+ margin: 0;
612
+ opacity: 0;
613
+ cursor: pointer;
614
+ z-index: 1;
615
+ }
616
+ .fds-toggle__input:disabled {
617
+ cursor: not-allowed;
618
+ }
619
+ .fds-toggle__control {
620
+ display: inline-flex;
621
+ align-items: flex-start;
622
+ gap: var(--spacing-2, 8px);
623
+ cursor: pointer;
624
+ }
625
+ .fds-toggle__input:disabled ~ .fds-toggle__control {
626
+ cursor: not-allowed;
627
+ }
628
+ .fds-toggle__track {
629
+ position: relative;
630
+ display: inline-block;
631
+ flex-shrink: 0;
632
+ background-color: var(--color-border-strong, #d4d4d4);
633
+ border-radius: var(--radius-pill, 9999px);
634
+ transition: background-color var(--duration-quick, 150ms) var(--ease-out, ease);
635
+ }
636
+ .fds-toggle__thumb {
637
+ position: absolute;
638
+ top: 50%;
639
+ transform: translateY(-50%);
640
+ background-color: #ffffff;
641
+ border-radius: var(--radius-full, 9999px);
642
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.15);
643
+ transition: left var(--duration-quick, 150ms) var(--ease-out, ease);
644
+ }
645
+ .fds-toggle__input:checked ~ .fds-toggle__control .fds-toggle__track {
646
+ background-color: var(--color-accent, #ea580c);
647
+ }
648
+ .fds-toggle__input:focus-visible ~ .fds-toggle__control .fds-toggle__track {
649
+ outline: 2px solid var(--color-accent, #ea580c);
650
+ outline-offset: 2px;
651
+ }
652
+ .fds-toggle[data-disabled] .fds-toggle__track {
653
+ opacity: 0.5;
654
+ }
655
+ .fds-toggle[data-disabled] .fds-toggle__label,
656
+ .fds-toggle[data-disabled] .fds-toggle__description {
657
+ color: var(--color-text-muted, #a3a3a3);
658
+ }
659
+ .fds-toggle--medium .fds-toggle__track {
660
+ width: 44px;
661
+ height: 24px;
662
+ }
663
+ .fds-toggle--medium .fds-toggle__thumb {
664
+ width: 20px;
665
+ height: 20px;
666
+ left: 2px;
667
+ }
668
+ .fds-toggle--medium .fds-toggle__input:checked ~ .fds-toggle__control .fds-toggle__thumb {
669
+ left: 22px;
670
+ }
671
+ .fds-toggle--small .fds-toggle__track {
672
+ width: 36px;
673
+ height: 20px;
674
+ }
675
+ .fds-toggle--small .fds-toggle__thumb {
676
+ width: 16px;
677
+ height: 16px;
678
+ left: 2px;
679
+ }
680
+ .fds-toggle--small .fds-toggle__input:checked ~ .fds-toggle__control .fds-toggle__thumb {
681
+ left: 18px;
682
+ }
683
+ .fds-toggle__text {
684
+ display: inline-flex;
685
+ flex-direction: column;
686
+ gap: 2px;
687
+ }
688
+ .fds-toggle__label {
689
+ font-family: var(--font-body, "Rethink Sans", system-ui, sans-serif);
690
+ font-size: var(--font-size-text-sm, 14px);
691
+ line-height: var(--line-height-text-sm, 22px);
692
+ font-weight: var(--font-weight-medium, 500);
693
+ color: var(--color-text, #171717);
694
+ }
695
+ .fds-toggle__description {
696
+ font-family: var(--font-body, "Rethink Sans", system-ui, sans-serif);
697
+ font-size: var(--font-size-text-sm, 14px);
698
+ line-height: var(--line-height-text-sm, 22px);
699
+ color: var(--color-text-secondary, #525252);
700
+ }
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as react from 'react';
2
- import { ButtonHTMLAttributes, ReactNode, HTMLAttributes } from 'react';
2
+ import { ButtonHTMLAttributes, ReactNode, HTMLAttributes, InputHTMLAttributes } from 'react';
3
3
 
4
4
  type ButtonVariant = 'primary' | 'outlined' | 'text' | 'neutral';
5
5
  type ButtonSize = 'x-small' | 'small' | 'medium' | 'big';
@@ -177,4 +177,59 @@ interface DiffPropertyListProps extends HTMLAttributes<HTMLDivElement> {
177
177
  */
178
178
  declare function DiffPropertyList({ properties, className, ...rest }: DiffPropertyListProps): react.JSX.Element;
179
179
 
180
- export { Button, type ButtonProps, type ButtonSize, type ButtonVariant, type DiffElement, type DiffPropertyLine, DiffPropertyList, type DiffPropertyListProps, DiffSection, type DiffSectionProps, MismatchCard, type MismatchCardProps, type MismatchProperty };
180
+ interface SelectOption {
181
+ value: string;
182
+ label: string;
183
+ /** Secondary line, e.g. an @handle. */
184
+ secondary?: string;
185
+ /** Leading node (avatar / icon) shown in the option and, when selected, the trigger. */
186
+ avatar?: ReactNode;
187
+ disabled?: boolean;
188
+ }
189
+ interface SelectProps {
190
+ options: SelectOption[];
191
+ value?: string | null;
192
+ onChange?: (value: string) => void;
193
+ placeholder?: string;
194
+ label?: ReactNode;
195
+ hint?: ReactNode;
196
+ error?: ReactNode;
197
+ disabled?: boolean;
198
+ /** Show a filter input at the top of the list. */
199
+ searchable?: boolean;
200
+ searchPlaceholder?: string;
201
+ /** Leading node in the trigger; a selected option's `avatar` takes precedence. */
202
+ leadingIcon?: ReactNode;
203
+ id?: string;
204
+ /** When set, renders a hidden input so the value posts with a form. */
205
+ name?: string;
206
+ className?: string;
207
+ containerClassName?: string;
208
+ }
209
+ /**
210
+ * Fidel design-system Select — a listbox (optionally searchable) with avatar +
211
+ * secondary-line options and a checkmark on the selected row. Controlled via
212
+ * `value`/`onChange`. Keyboard: type/Enter/Space/↓ open; ↑/↓/Home/End move;
213
+ * Enter selects; Esc closes; click-outside closes. Measured from Figma "Fidel
214
+ * Library" Select (node 3521:5948).
215
+ */
216
+ declare const Select: react.ForwardRefExoticComponent<SelectProps & react.RefAttributes<HTMLButtonElement>>;
217
+
218
+ type ToggleSize = 'small' | 'medium';
219
+ interface ToggleProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'type' | 'size'> {
220
+ /** Size step. @default 'medium' */
221
+ size?: ToggleSize;
222
+ /** Text beside the switch. */
223
+ label?: ReactNode;
224
+ /** Secondary line under the label. */
225
+ description?: ReactNode;
226
+ }
227
+ /**
228
+ * Fidel design-system Toggle (switch). Renders a native checkbox with
229
+ * `role="switch"`; on/off/disabled are handled in CSS, so all standard input
230
+ * attributes (checked, defaultChecked, onChange, disabled, aria-*, …) pass
231
+ * through. Measured from Figma "Fidel Library" Toggle (node 3528:8473).
232
+ */
233
+ declare const Toggle: react.ForwardRefExoticComponent<ToggleProps & react.RefAttributes<HTMLInputElement>>;
234
+
235
+ export { Button, type ButtonProps, type ButtonSize, type ButtonVariant, type DiffElement, type DiffPropertyLine, DiffPropertyList, type DiffPropertyListProps, DiffSection, type DiffSectionProps, MismatchCard, type MismatchCardProps, type MismatchProperty, Select, type SelectOption, type SelectProps, Toggle, type ToggleProps, type ToggleSize };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as react from 'react';
2
- import { ButtonHTMLAttributes, ReactNode, HTMLAttributes } from 'react';
2
+ import { ButtonHTMLAttributes, ReactNode, HTMLAttributes, InputHTMLAttributes } from 'react';
3
3
 
4
4
  type ButtonVariant = 'primary' | 'outlined' | 'text' | 'neutral';
5
5
  type ButtonSize = 'x-small' | 'small' | 'medium' | 'big';
@@ -177,4 +177,59 @@ interface DiffPropertyListProps extends HTMLAttributes<HTMLDivElement> {
177
177
  */
178
178
  declare function DiffPropertyList({ properties, className, ...rest }: DiffPropertyListProps): react.JSX.Element;
179
179
 
180
- export { Button, type ButtonProps, type ButtonSize, type ButtonVariant, type DiffElement, type DiffPropertyLine, DiffPropertyList, type DiffPropertyListProps, DiffSection, type DiffSectionProps, MismatchCard, type MismatchCardProps, type MismatchProperty };
180
+ interface SelectOption {
181
+ value: string;
182
+ label: string;
183
+ /** Secondary line, e.g. an @handle. */
184
+ secondary?: string;
185
+ /** Leading node (avatar / icon) shown in the option and, when selected, the trigger. */
186
+ avatar?: ReactNode;
187
+ disabled?: boolean;
188
+ }
189
+ interface SelectProps {
190
+ options: SelectOption[];
191
+ value?: string | null;
192
+ onChange?: (value: string) => void;
193
+ placeholder?: string;
194
+ label?: ReactNode;
195
+ hint?: ReactNode;
196
+ error?: ReactNode;
197
+ disabled?: boolean;
198
+ /** Show a filter input at the top of the list. */
199
+ searchable?: boolean;
200
+ searchPlaceholder?: string;
201
+ /** Leading node in the trigger; a selected option's `avatar` takes precedence. */
202
+ leadingIcon?: ReactNode;
203
+ id?: string;
204
+ /** When set, renders a hidden input so the value posts with a form. */
205
+ name?: string;
206
+ className?: string;
207
+ containerClassName?: string;
208
+ }
209
+ /**
210
+ * Fidel design-system Select — a listbox (optionally searchable) with avatar +
211
+ * secondary-line options and a checkmark on the selected row. Controlled via
212
+ * `value`/`onChange`. Keyboard: type/Enter/Space/↓ open; ↑/↓/Home/End move;
213
+ * Enter selects; Esc closes; click-outside closes. Measured from Figma "Fidel
214
+ * Library" Select (node 3521:5948).
215
+ */
216
+ declare const Select: react.ForwardRefExoticComponent<SelectProps & react.RefAttributes<HTMLButtonElement>>;
217
+
218
+ type ToggleSize = 'small' | 'medium';
219
+ interface ToggleProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'type' | 'size'> {
220
+ /** Size step. @default 'medium' */
221
+ size?: ToggleSize;
222
+ /** Text beside the switch. */
223
+ label?: ReactNode;
224
+ /** Secondary line under the label. */
225
+ description?: ReactNode;
226
+ }
227
+ /**
228
+ * Fidel design-system Toggle (switch). Renders a native checkbox with
229
+ * `role="switch"`; on/off/disabled are handled in CSS, so all standard input
230
+ * attributes (checked, defaultChecked, onChange, disabled, aria-*, …) pass
231
+ * through. Measured from Figma "Fidel Library" Toggle (node 3528:8473).
232
+ */
233
+ declare const Toggle: react.ForwardRefExoticComponent<ToggleProps & react.RefAttributes<HTMLInputElement>>;
234
+
235
+ export { Button, type ButtonProps, type ButtonSize, type ButtonVariant, type DiffElement, type DiffPropertyLine, DiffPropertyList, type DiffPropertyListProps, DiffSection, type DiffSectionProps, MismatchCard, type MismatchCardProps, type MismatchProperty, Select, type SelectOption, type SelectProps, Toggle, type ToggleProps, type ToggleSize };
package/dist/index.js CHANGED
@@ -253,9 +253,281 @@ function Arrow() {
253
253
  }
254
254
  );
255
255
  }
256
+
257
+ // src/components/Select/Select.tsx
258
+ import {
259
+ forwardRef,
260
+ useEffect,
261
+ useId,
262
+ useImperativeHandle,
263
+ useMemo,
264
+ useRef,
265
+ useState as useState3
266
+ } from "react";
267
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
268
+ function ChevronIcon() {
269
+ return /* @__PURE__ */ jsx4("svg", { viewBox: "0 0 20 20", width: "18", height: "18", fill: "none", "aria-hidden": "true", className: "fds-select__chevronIcon", children: /* @__PURE__ */ jsx4("path", { d: "M6 8l4 4 4-4", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round", strokeLinejoin: "round" }) });
270
+ }
271
+ function CheckIcon() {
272
+ return /* @__PURE__ */ jsx4("svg", { viewBox: "0 0 20 20", width: "18", height: "18", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx4("path", { d: "M4.5 10.5l3 3 8-8.5", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round" }) });
273
+ }
274
+ var Select = forwardRef(function Select2({
275
+ options,
276
+ value,
277
+ onChange,
278
+ placeholder = "Select\u2026",
279
+ label,
280
+ hint,
281
+ error,
282
+ disabled = false,
283
+ searchable = false,
284
+ searchPlaceholder = "Search\u2026",
285
+ leadingIcon,
286
+ id,
287
+ name,
288
+ className,
289
+ containerClassName
290
+ }, ref) {
291
+ const genId = useId();
292
+ const triggerId = id ?? genId;
293
+ const listId = `${genId}-list`;
294
+ const msgId = `${genId}-msg`;
295
+ const rootRef = useRef(null);
296
+ const triggerRef = useRef(null);
297
+ const listRef = useRef(null);
298
+ const searchRef = useRef(null);
299
+ useImperativeHandle(ref, () => triggerRef.current, []);
300
+ const [open, setOpen] = useState3(false);
301
+ const [query, setQuery] = useState3("");
302
+ const [activeIndex, setActiveIndex] = useState3(-1);
303
+ const selected = options.find((o) => o.value === value) ?? null;
304
+ const filtered = useMemo(() => {
305
+ if (!searchable || !query.trim()) return options;
306
+ const q = query.trim().toLowerCase();
307
+ return options.filter(
308
+ (o) => o.label.toLowerCase().includes(q) || (o.secondary?.toLowerCase().includes(q) ?? false)
309
+ );
310
+ }, [options, searchable, query]);
311
+ const hasMessage = Boolean(error ?? hint);
312
+ const firstEnabled = (list) => list.findIndex((o) => !o.disabled);
313
+ useEffect(() => {
314
+ if (!open) return;
315
+ const onDown = (e) => {
316
+ if (rootRef.current && !rootRef.current.contains(e.target)) setOpen(false);
317
+ };
318
+ document.addEventListener("mousedown", onDown);
319
+ return () => document.removeEventListener("mousedown", onDown);
320
+ }, [open]);
321
+ useEffect(() => {
322
+ if (!open) {
323
+ setQuery("");
324
+ return;
325
+ }
326
+ const selIdx = filtered.findIndex((o) => o.value === value);
327
+ setActiveIndex(selIdx >= 0 ? selIdx : firstEnabled(filtered));
328
+ const raf = requestAnimationFrame(() => {
329
+ if (searchable) searchRef.current?.focus();
330
+ else listRef.current?.focus();
331
+ });
332
+ return () => cancelAnimationFrame(raf);
333
+ }, [open]);
334
+ const close = () => {
335
+ setOpen(false);
336
+ triggerRef.current?.focus();
337
+ };
338
+ const move = (dir) => {
339
+ setActiveIndex((prev) => {
340
+ if (filtered.length === 0) return -1;
341
+ let i = prev;
342
+ for (let k = 0; k < filtered.length; k++) {
343
+ i = (i + dir + filtered.length) % filtered.length;
344
+ if (!filtered[i].disabled) return i;
345
+ }
346
+ return prev;
347
+ });
348
+ };
349
+ const choose = (i) => {
350
+ const o = filtered[i];
351
+ if (o && !o.disabled) {
352
+ onChange?.(o.value);
353
+ close();
354
+ }
355
+ };
356
+ const onKeyDown = (e) => {
357
+ if (!open) {
358
+ if (e.key === "ArrowDown" || e.key === "ArrowUp" || e.key === "Enter" || e.key === " ") {
359
+ e.preventDefault();
360
+ setOpen(true);
361
+ }
362
+ return;
363
+ }
364
+ if (e.key === "ArrowDown") {
365
+ e.preventDefault();
366
+ move(1);
367
+ } else if (e.key === "ArrowUp") {
368
+ e.preventDefault();
369
+ move(-1);
370
+ } else if (e.key === "Home") {
371
+ e.preventDefault();
372
+ setActiveIndex(firstEnabled(filtered));
373
+ } else if (e.key === "End") {
374
+ e.preventDefault();
375
+ for (let i = filtered.length - 1; i >= 0; i--) {
376
+ if (!filtered[i].disabled) {
377
+ setActiveIndex(i);
378
+ break;
379
+ }
380
+ }
381
+ } else if (e.key === "Enter") {
382
+ e.preventDefault();
383
+ choose(activeIndex);
384
+ } else if (e.key === "Escape") {
385
+ e.preventDefault();
386
+ close();
387
+ }
388
+ };
389
+ const triggerClasses = [
390
+ "fds-select__trigger",
391
+ error ? "fds-select__trigger--error" : "",
392
+ open ? "fds-select__trigger--open" : "",
393
+ className
394
+ ].filter(Boolean).join(" ");
395
+ return /* @__PURE__ */ jsxs4(
396
+ "div",
397
+ {
398
+ ref: rootRef,
399
+ className: ["fds-select-field", containerClassName].filter(Boolean).join(" "),
400
+ children: [
401
+ label != null && /* @__PURE__ */ jsx4("label", { htmlFor: triggerId, className: "fds-select__label", children: label }),
402
+ /* @__PURE__ */ jsxs4("div", { className: "fds-select", children: [
403
+ /* @__PURE__ */ jsxs4(
404
+ "button",
405
+ {
406
+ type: "button",
407
+ id: triggerId,
408
+ ref: triggerRef,
409
+ className: triggerClasses,
410
+ disabled,
411
+ "aria-haspopup": "listbox",
412
+ "aria-expanded": open,
413
+ "aria-controls": open ? listId : void 0,
414
+ "aria-invalid": error ? true : void 0,
415
+ "aria-describedby": hasMessage ? msgId : void 0,
416
+ onClick: () => !disabled && setOpen((o) => !o),
417
+ onKeyDown,
418
+ children: [
419
+ (selected?.avatar ?? leadingIcon) != null && /* @__PURE__ */ jsx4("span", { className: "fds-select__lead", children: selected?.avatar ?? leadingIcon }),
420
+ /* @__PURE__ */ jsx4(
421
+ "span",
422
+ {
423
+ className: `fds-select__value${selected ? "" : " fds-select__value--placeholder"}`,
424
+ children: selected ? selected.label : placeholder
425
+ }
426
+ ),
427
+ /* @__PURE__ */ jsx4("span", { className: "fds-select__chevron", children: /* @__PURE__ */ jsx4(ChevronIcon, {}) })
428
+ ]
429
+ }
430
+ ),
431
+ open && /* @__PURE__ */ jsxs4("div", { className: "fds-select__popover", children: [
432
+ searchable && /* @__PURE__ */ jsx4("div", { className: "fds-select__search", children: /* @__PURE__ */ jsx4(
433
+ "input",
434
+ {
435
+ ref: searchRef,
436
+ className: "fds-select__searchInput",
437
+ placeholder: searchPlaceholder,
438
+ value: query,
439
+ onChange: (e) => setQuery(e.target.value),
440
+ onKeyDown,
441
+ role: "combobox",
442
+ "aria-expanded": true,
443
+ "aria-controls": listId,
444
+ "aria-autocomplete": "list"
445
+ }
446
+ ) }),
447
+ /* @__PURE__ */ jsxs4(
448
+ "div",
449
+ {
450
+ ref: listRef,
451
+ id: listId,
452
+ role: "listbox",
453
+ tabIndex: searchable ? -1 : 0,
454
+ className: "fds-select__list",
455
+ "aria-activedescendant": activeIndex >= 0 ? `${listId}-opt-${activeIndex}` : void 0,
456
+ onKeyDown: searchable ? void 0 : onKeyDown,
457
+ children: [
458
+ filtered.length === 0 && /* @__PURE__ */ jsx4("div", { className: "fds-select__empty", children: "No results" }),
459
+ filtered.map((o, i) => /* @__PURE__ */ jsxs4(
460
+ "div",
461
+ {
462
+ id: `${listId}-opt-${i}`,
463
+ role: "option",
464
+ "aria-selected": o.value === value,
465
+ "aria-disabled": o.disabled || void 0,
466
+ className: [
467
+ "fds-select__option",
468
+ i === activeIndex ? "fds-select__option--active" : "",
469
+ o.disabled ? "fds-select__option--disabled" : ""
470
+ ].filter(Boolean).join(" "),
471
+ onMouseEnter: () => !o.disabled && setActiveIndex(i),
472
+ onMouseDown: (e) => e.preventDefault(),
473
+ onClick: () => choose(i),
474
+ children: [
475
+ o.avatar != null && /* @__PURE__ */ jsx4("span", { className: "fds-select__optAvatar", children: o.avatar }),
476
+ /* @__PURE__ */ jsxs4("span", { className: "fds-select__optText", children: [
477
+ /* @__PURE__ */ jsx4("span", { className: "fds-select__optLabel", children: o.label }),
478
+ o.secondary != null && /* @__PURE__ */ jsx4("span", { className: "fds-select__optSecondary", children: o.secondary })
479
+ ] }),
480
+ o.value === value && /* @__PURE__ */ jsx4("span", { className: "fds-select__check", children: /* @__PURE__ */ jsx4(CheckIcon, {}) })
481
+ ]
482
+ },
483
+ o.value
484
+ ))
485
+ ]
486
+ }
487
+ )
488
+ ] })
489
+ ] }),
490
+ hasMessage && /* @__PURE__ */ jsx4("p", { id: msgId, className: error ? "fds-select__error" : "fds-select__hint", children: error ?? hint }),
491
+ name != null && /* @__PURE__ */ jsx4("input", { type: "hidden", name, value: value ?? "" })
492
+ ]
493
+ }
494
+ );
495
+ });
496
+
497
+ // src/components/Toggle/Toggle.tsx
498
+ import { forwardRef as forwardRef2, useId as useId2 } from "react";
499
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
500
+ var Toggle = forwardRef2(function Toggle2({ size = "medium", label, description, className, id, disabled, ...rest }, ref) {
501
+ const generatedId = useId2();
502
+ const inputId = id ?? generatedId;
503
+ const classes = ["fds-toggle", `fds-toggle--${size}`, className].filter(Boolean).join(" ");
504
+ return /* @__PURE__ */ jsxs5("div", { className: classes, "data-disabled": disabled ? "" : void 0, children: [
505
+ /* @__PURE__ */ jsx5(
506
+ "input",
507
+ {
508
+ ...rest,
509
+ id: inputId,
510
+ ref,
511
+ type: "checkbox",
512
+ role: "switch",
513
+ disabled,
514
+ className: "fds-toggle__input"
515
+ }
516
+ ),
517
+ /* @__PURE__ */ jsxs5("label", { htmlFor: inputId, className: "fds-toggle__control", children: [
518
+ /* @__PURE__ */ jsx5("span", { className: "fds-toggle__track", "aria-hidden": "true", children: /* @__PURE__ */ jsx5("span", { className: "fds-toggle__thumb" }) }),
519
+ (label != null || description != null) && /* @__PURE__ */ jsxs5("span", { className: "fds-toggle__text", children: [
520
+ label != null && /* @__PURE__ */ jsx5("span", { className: "fds-toggle__label", children: label }),
521
+ description != null && /* @__PURE__ */ jsx5("span", { className: "fds-toggle__description", children: description })
522
+ ] })
523
+ ] })
524
+ ] });
525
+ });
256
526
  export {
257
527
  Button,
258
528
  DiffPropertyList,
259
529
  DiffSection,
260
- MismatchCard
530
+ MismatchCard,
531
+ Select,
532
+ Toggle
261
533
  };
package/dist/landing.css CHANGED
@@ -1,8 +1,8 @@
1
1
  /* GENERATED by @usefidel/design-system — do not edit. Surface: landing. */
2
2
  :root {
3
3
  --color-bg: #ffffff;
4
- --color-surface: #fff7ed;
5
- --color-surface-hover: #ffedd5;
4
+ --color-surface: #f5f5f5;
5
+ --color-surface-hover: #ececec;
6
6
  --color-surface-raised: #ffffff;
7
7
  --color-text: #171717;
8
8
  --color-text-secondary: #525252;
@@ -47,11 +47,14 @@
47
47
  --font-display: 'Rethink Sans', system-ui, -apple-system, sans-serif;
48
48
  --font-body: 'Rethink Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
49
49
  --font-mono: 'JetBrains Mono', 'SF Mono', 'Fira Code', 'Cascadia Code', monospace;
50
- --radius-xs: 4px;
51
- --radius-sm: 6px;
52
- --radius-md: 8px;
53
- --radius-lg: 12px;
54
- --radius-xl: 16px;
50
+ --radius-none: 0px;
51
+ --radius-xs: 2px;
52
+ --radius-sm: 4px;
53
+ --radius-md: 6px;
54
+ --radius-lg: 8px;
55
+ --radius-xl: 12px;
56
+ --radius-2xl: 16px;
57
+ --radius-3xl: 24px;
55
58
  --radius-pill: 9999px;
56
59
  --radius-full: 9999px;
57
60
  --spacing-1: 4px;
package/dist/theme.css CHANGED
@@ -3,8 +3,8 @@
3
3
  NOT a source of truth. Do not edit — regenerate from tokens/tokens.json. */
4
4
  @theme {
5
5
  --color-bg: #ffffff;
6
- --color-surface: #fff7ed;
7
- --color-surface-hover: #ffedd5;
6
+ --color-surface: #f5f5f5;
7
+ --color-surface-hover: #ececec;
8
8
  --color-surface-raised: #ffffff;
9
9
  --color-text: #171717;
10
10
  --color-text-secondary: #525252;
@@ -49,11 +49,14 @@
49
49
  --font-display: 'Rethink Sans', system-ui, -apple-system, sans-serif;
50
50
  --font-body: 'Rethink Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
51
51
  --font-mono: 'JetBrains Mono', 'SF Mono', 'Fira Code', 'Cascadia Code', monospace;
52
- --radius-xs: 4px;
53
- --radius-sm: 6px;
54
- --radius-md: 8px;
55
- --radius-lg: 12px;
56
- --radius-xl: 16px;
52
+ --radius-none: 0px;
53
+ --radius-xs: 2px;
54
+ --radius-sm: 4px;
55
+ --radius-md: 6px;
56
+ --radius-lg: 8px;
57
+ --radius-xl: 12px;
58
+ --radius-2xl: 16px;
59
+ --radius-3xl: 24px;
57
60
  --radius-pill: 9999px;
58
61
  --radius-full: 9999px;
59
62
  --spacing-1: 4px;
package/dist/webapp.css CHANGED
@@ -1,8 +1,8 @@
1
1
  /* GENERATED by @usefidel/design-system — do not edit. Surface: webapp. */
2
2
  :root {
3
3
  --color-bg: #ffffff;
4
- --color-surface: #fff7ed;
5
- --color-surface-hover: #ffedd5;
4
+ --color-surface: #f5f5f5;
5
+ --color-surface-hover: #ececec;
6
6
  --color-surface-raised: #ffffff;
7
7
  --color-text: #171717;
8
8
  --color-text-secondary: #525252;
@@ -47,11 +47,14 @@
47
47
  --font-display: 'Rethink Sans', system-ui, -apple-system, sans-serif;
48
48
  --font-body: 'Rethink Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
49
49
  --font-mono: 'JetBrains Mono', 'SF Mono', 'Fira Code', 'Cascadia Code', monospace;
50
- --radius-xs: 4px;
51
- --radius-sm: 6px;
52
- --radius-md: 8px;
53
- --radius-lg: 12px;
54
- --radius-xl: 16px;
50
+ --radius-none: 0px;
51
+ --radius-xs: 2px;
52
+ --radius-sm: 4px;
53
+ --radius-md: 6px;
54
+ --radius-lg: 8px;
55
+ --radius-xl: 12px;
56
+ --radius-2xl: 16px;
57
+ --radius-3xl: 24px;
55
58
  --radius-pill: 9999px;
56
59
  --radius-full: 9999px;
57
60
  --spacing-1: 4px;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usefidel/design-system",
3
- "version": "0.7.0",
3
+ "version": "0.9.0",
4
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",
@@ -16,6 +16,11 @@
16
16
  "require": "./dist/index.cjs"
17
17
  },
18
18
  "./styles.css": "./dist/index.css",
19
+ "./icons": {
20
+ "types": "./dist/icons.d.ts",
21
+ "import": "./dist/icons.js",
22
+ "require": "./dist/icons.cjs"
23
+ },
19
24
  "./dist/*": "./dist/*"
20
25
  },
21
26
  "publishConfig": {
@@ -51,5 +56,8 @@
51
56
  "style-dictionary": "^4.3.0",
52
57
  "tsup": "^8.3.5",
53
58
  "typescript": "^5.6.3"
59
+ },
60
+ "dependencies": {
61
+ "lucide-react": "^1.33.0"
54
62
  }
55
63
  }
@@ -5,7 +5,7 @@
5
5
  "$type": "color"
6
6
  },
7
7
  "surface": {
8
- "$value": "#fff7ed",
8
+ "$value": "#f5f5f5",
9
9
  "$type": "color",
10
10
  "$extensions": {
11
11
  "fidel": {
@@ -17,7 +17,7 @@
17
17
  }
18
18
  },
19
19
  "surface-hover": {
20
- "$value": "#ffedd5",
20
+ "$value": "#ececec",
21
21
  "$type": "color",
22
22
  "$extensions": {
23
23
  "fidel": {
@@ -264,26 +264,38 @@
264
264
  }
265
265
  },
266
266
  "radius": {
267
+ "none": {
268
+ "$value": "0px",
269
+ "$type": "dimension"
270
+ },
267
271
  "xs": {
268
- "$value": "4px",
272
+ "$value": "2px",
269
273
  "$type": "dimension"
270
274
  },
271
275
  "sm": {
272
- "$value": "6px",
276
+ "$value": "4px",
273
277
  "$type": "dimension"
274
278
  },
275
279
  "md": {
276
- "$value": "8px",
280
+ "$value": "6px",
277
281
  "$type": "dimension"
278
282
  },
279
283
  "lg": {
280
- "$value": "12px",
284
+ "$value": "8px",
281
285
  "$type": "dimension"
282
286
  },
283
287
  "xl": {
288
+ "$value": "12px",
289
+ "$type": "dimension"
290
+ },
291
+ "2xl": {
284
292
  "$value": "16px",
285
293
  "$type": "dimension"
286
294
  },
295
+ "3xl": {
296
+ "$value": "24px",
297
+ "$type": "dimension"
298
+ },
287
299
  "pill": {
288
300
  "$value": "9999px",
289
301
  "$type": "dimension",