@usefidel/design-system 0.14.0 → 0.16.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/index.js CHANGED
@@ -838,17 +838,402 @@ var breakpoints = {
838
838
  /** Tablets, narrow windows. */
839
839
  lg: 900
840
840
  };
841
+
842
+ // src/components/Avatar/Avatar.tsx
843
+ import { useState as useState5 } from "react";
844
+ import { jsx as jsx11 } from "react/jsx-runtime";
845
+ function initialsFor(name) {
846
+ const words = name.trim().split(/[\s._@-]+/).filter(Boolean);
847
+ if (words.length === 0) return "?";
848
+ if (words.length === 1) return words[0].slice(0, 2).toUpperCase();
849
+ return (words[0][0] + words[1][0]).toUpperCase();
850
+ }
851
+ function Avatar({ name, src, size = "medium", className, ...rest }) {
852
+ const [failedSrc, setFailedSrc] = useState5(null);
853
+ const showImage = !!src && src !== failedSrc;
854
+ const classes = ["fds-avatar", `fds-avatar--${size}`, className].filter(Boolean).join(" ");
855
+ return /* @__PURE__ */ jsx11("span", { className: classes, role: "img", "aria-label": name, ...rest, children: showImage ? /* @__PURE__ */ jsx11("img", { className: "fds-avatar__img", src, alt: "", onError: () => setFailedSrc(src) }) : /* @__PURE__ */ jsx11("span", { "aria-hidden": "true", children: initialsFor(name) }) });
856
+ }
857
+
858
+ // src/components/SectionHeader/SectionHeader.tsx
859
+ import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
860
+ function SectionHeader({
861
+ title,
862
+ description,
863
+ as: Heading = "h2",
864
+ titleId,
865
+ className,
866
+ ...rest
867
+ }) {
868
+ const classes = ["fds-section-header", className].filter(Boolean).join(" ");
869
+ return /* @__PURE__ */ jsxs10("div", { className: classes, ...rest, children: [
870
+ /* @__PURE__ */ jsx12(Heading, { id: titleId, className: "fds-section-header__title", children: title }),
871
+ description != null && /* @__PURE__ */ jsx12("p", { className: "fds-section-header__description", children: description })
872
+ ] });
873
+ }
874
+
875
+ // src/components/Split/Split.tsx
876
+ import { jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
877
+ function Split({
878
+ aside,
879
+ children,
880
+ asideWidth = "280px",
881
+ sticky = false,
882
+ collapseBelow = "lg",
883
+ className,
884
+ style,
885
+ ...rest
886
+ }) {
887
+ const classes = [
888
+ "fds-split",
889
+ `fds-split--collapse-${collapseBelow}`,
890
+ sticky && "fds-split--sticky",
891
+ className
892
+ ].filter(Boolean).join(" ");
893
+ return /* @__PURE__ */ jsxs11(
894
+ "div",
895
+ {
896
+ className: classes,
897
+ style: { "--fds-split-aside": asideWidth, ...style },
898
+ ...rest,
899
+ children: [
900
+ /* @__PURE__ */ jsx13("div", { className: "fds-split__aside", children: aside }),
901
+ /* @__PURE__ */ jsx13("div", { className: "fds-split__main", children })
902
+ ]
903
+ }
904
+ );
905
+ }
906
+
907
+ // src/components/Badge/Badge.tsx
908
+ import { jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
909
+ function Badge({ tone = "neutral", icon, className, children, ...rest }) {
910
+ const classes = ["fds-badge", `fds-badge--${tone}`, className].filter(Boolean).join(" ");
911
+ return /* @__PURE__ */ jsxs12("span", { className: classes, ...rest, children: [
912
+ icon != null && /* @__PURE__ */ jsx14("span", { className: "fds-badge__icon", "aria-hidden": "true", children: icon }),
913
+ children
914
+ ] });
915
+ }
916
+
917
+ // src/components/Tag/Tag.tsx
918
+ import { jsx as jsx15, jsxs as jsxs13 } from "react/jsx-runtime";
919
+ function Tag({ dot, className, children, ...rest }) {
920
+ return /* @__PURE__ */ jsxs13("span", { className: ["fds-tag", className].filter(Boolean).join(" "), ...rest, children: [
921
+ dot && /* @__PURE__ */ jsx15("span", { className: `fds-dot fds-dot--${dot}`, "aria-hidden": "true" }),
922
+ children
923
+ ] });
924
+ }
925
+
926
+ // src/components/MultiSelect/MultiSelect.tsx
927
+ import { useEffect as useEffect4, useId as useId6, useRef as useRef5, useState as useState6 } from "react";
928
+ import { jsx as jsx16, jsxs as jsxs14 } from "react/jsx-runtime";
929
+ function ChevronIcon2() {
930
+ return /* @__PURE__ */ jsx16("svg", { viewBox: "0 0 20 20", width: "20", height: "20", fill: "none", "aria-hidden": "true", className: "fds-multiselect__chevronIcon", children: /* @__PURE__ */ jsx16("path", { d: "M6 8l4 4 4-4", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round", strokeLinejoin: "round" }) });
931
+ }
932
+ function CheckIcon2() {
933
+ return /* @__PURE__ */ jsx16("svg", { viewBox: "0 0 12 12", width: "12", height: "12", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx16("path", { d: "M10 3L4.5 8.5 2 6", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) });
934
+ }
935
+ function MultiSelect({
936
+ label,
937
+ options,
938
+ value,
939
+ onChange,
940
+ disabled = false,
941
+ selectAllLabel = "Select all",
942
+ clearLabel = "Clear",
943
+ id,
944
+ className
945
+ }) {
946
+ const genId = useId6();
947
+ const triggerId = id ?? genId;
948
+ const listId = `${genId}-list`;
949
+ const rootRef = useRef5(null);
950
+ const triggerRef = useRef5(null);
951
+ const listRef = useRef5(null);
952
+ const [open, setOpen] = useState6(false);
953
+ const [activeIndex, setActiveIndex] = useState6(0);
954
+ const selected = new Set(value);
955
+ const enabled = options.filter((o) => !o.disabled);
956
+ useEffect4(() => {
957
+ if (!open) return;
958
+ const onDown = (e) => {
959
+ if (rootRef.current && !rootRef.current.contains(e.target)) setOpen(false);
960
+ };
961
+ document.addEventListener("mousedown", onDown);
962
+ const raf = requestAnimationFrame(() => listRef.current?.focus());
963
+ return () => {
964
+ document.removeEventListener("mousedown", onDown);
965
+ cancelAnimationFrame(raf);
966
+ };
967
+ }, [open]);
968
+ const close = () => {
969
+ setOpen(false);
970
+ triggerRef.current?.focus();
971
+ };
972
+ const toggle = (o) => {
973
+ if (!o || o.disabled) return;
974
+ const next = selected.has(o.value) ? value.filter((v) => v !== o.value) : options.filter((x) => x.value === o.value || selected.has(x.value)).map((x) => x.value);
975
+ onChange(next);
976
+ };
977
+ const onTriggerKeyDown = (e) => {
978
+ if (e.key === "ArrowDown" || e.key === "ArrowUp" || e.key === "Enter" || e.key === " ") {
979
+ e.preventDefault();
980
+ setActiveIndex(0);
981
+ setOpen(true);
982
+ }
983
+ };
984
+ const onListKeyDown = (e) => {
985
+ const last = options.length - 1;
986
+ if (e.key === "ArrowDown") {
987
+ e.preventDefault();
988
+ setActiveIndex((i) => i >= last ? 0 : i + 1);
989
+ } else if (e.key === "ArrowUp") {
990
+ e.preventDefault();
991
+ setActiveIndex((i) => i <= 0 ? last : i - 1);
992
+ } else if (e.key === "Home") {
993
+ e.preventDefault();
994
+ setActiveIndex(0);
995
+ } else if (e.key === "End") {
996
+ e.preventDefault();
997
+ setActiveIndex(last);
998
+ } else if (e.key === "Enter" || e.key === " ") {
999
+ e.preventDefault();
1000
+ toggle(options[activeIndex]);
1001
+ } else if (e.key === "Escape" || e.key === "Tab") {
1002
+ if (e.key === "Escape") e.preventDefault();
1003
+ close();
1004
+ }
1005
+ };
1006
+ const triggerClasses = ["fds-multiselect__trigger", open ? "fds-multiselect__trigger--open" : ""].filter(Boolean).join(" ");
1007
+ return /* @__PURE__ */ jsxs14("div", { ref: rootRef, className: ["fds-multiselect", className].filter(Boolean).join(" "), children: [
1008
+ /* @__PURE__ */ jsxs14(
1009
+ "button",
1010
+ {
1011
+ type: "button",
1012
+ id: triggerId,
1013
+ ref: triggerRef,
1014
+ className: triggerClasses,
1015
+ disabled,
1016
+ "aria-haspopup": "listbox",
1017
+ "aria-expanded": open,
1018
+ "aria-controls": open ? listId : void 0,
1019
+ onClick: () => setOpen((o) => !o),
1020
+ onKeyDown: onTriggerKeyDown,
1021
+ children: [
1022
+ /* @__PURE__ */ jsx16("span", { className: "fds-multiselect__label", children: label }),
1023
+ value.length > 0 && /* @__PURE__ */ jsxs14("span", { className: "fds-multiselect__count", "aria-label": `${value.length} of ${options.length} selected`, children: [
1024
+ value.length,
1025
+ "/",
1026
+ options.length
1027
+ ] }),
1028
+ /* @__PURE__ */ jsx16("span", { className: `fds-multiselect__chevron${open ? " fds-multiselect__chevron--open" : ""}`, children: /* @__PURE__ */ jsx16(ChevronIcon2, {}) })
1029
+ ]
1030
+ }
1031
+ ),
1032
+ open && /* @__PURE__ */ jsxs14("div", { className: "fds-multiselect__popover", children: [
1033
+ /* @__PURE__ */ jsx16(
1034
+ "div",
1035
+ {
1036
+ ref: listRef,
1037
+ id: listId,
1038
+ role: "listbox",
1039
+ "aria-multiselectable": "true",
1040
+ "aria-label": label,
1041
+ tabIndex: 0,
1042
+ className: "fds-multiselect__list",
1043
+ "aria-activedescendant": `${listId}-opt-${activeIndex}`,
1044
+ onKeyDown: onListKeyDown,
1045
+ children: options.map((o, i) => {
1046
+ const checked = selected.has(o.value);
1047
+ return /* @__PURE__ */ jsxs14(
1048
+ "div",
1049
+ {
1050
+ id: `${listId}-opt-${i}`,
1051
+ role: "option",
1052
+ "aria-selected": checked,
1053
+ "aria-disabled": o.disabled || void 0,
1054
+ className: [
1055
+ "fds-multiselect__option",
1056
+ i === activeIndex ? "fds-multiselect__option--active" : "",
1057
+ o.disabled ? "fds-multiselect__option--disabled" : ""
1058
+ ].filter(Boolean).join(" "),
1059
+ onMouseEnter: () => setActiveIndex(i),
1060
+ onMouseDown: (e) => e.preventDefault(),
1061
+ onClick: () => toggle(o),
1062
+ children: [
1063
+ /* @__PURE__ */ jsx16("span", { className: `fds-multiselect__box${checked ? " fds-multiselect__box--checked" : ""}`, "aria-hidden": "true", children: checked && /* @__PURE__ */ jsx16(CheckIcon2, {}) }),
1064
+ o.dot && /* @__PURE__ */ jsx16("span", { className: `fds-dot fds-dot--${o.dot}`, "aria-hidden": "true" }),
1065
+ /* @__PURE__ */ jsx16("span", { className: "fds-multiselect__optLabel", children: o.label }),
1066
+ o.count != null && /* @__PURE__ */ jsx16("span", { className: "fds-multiselect__optCount", children: o.count })
1067
+ ]
1068
+ },
1069
+ o.value
1070
+ );
1071
+ })
1072
+ }
1073
+ ),
1074
+ /* @__PURE__ */ jsxs14("div", { className: "fds-multiselect__footer", children: [
1075
+ /* @__PURE__ */ jsx16("button", { type: "button", className: "fds-multiselect__action", onClick: () => onChange(enabled.map((o) => o.value)), children: selectAllLabel }),
1076
+ /* @__PURE__ */ jsx16("button", { type: "button", className: "fds-multiselect__action", onClick: () => onChange([]), children: clearLabel })
1077
+ ] })
1078
+ ] })
1079
+ ] });
1080
+ }
1081
+
1082
+ // src/components/SearchInput/SearchInput.tsx
1083
+ import { forwardRef as forwardRef3 } from "react";
1084
+ import { jsx as jsx17, jsxs as jsxs15 } from "react/jsx-runtime";
1085
+ var SearchInput = forwardRef3(function SearchInput2({ shortcut, containerClassName, className, value, ...rest }, ref) {
1086
+ const empty = value === void 0 || value === "";
1087
+ return /* @__PURE__ */ jsxs15("div", { className: ["fds-search", containerClassName].filter(Boolean).join(" "), children: [
1088
+ /* @__PURE__ */ jsxs15("svg", { className: "fds-search__icon", viewBox: "0 0 24 24", width: "20", height: "20", fill: "none", "aria-hidden": "true", children: [
1089
+ /* @__PURE__ */ jsx17("circle", { cx: "11", cy: "11", r: "8", stroke: "currentColor", strokeWidth: "2" }),
1090
+ /* @__PURE__ */ jsx17("path", { d: "m21 21-4.3-4.3", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round" })
1091
+ ] }),
1092
+ /* @__PURE__ */ jsx17("input", { ref, type: "search", value, className: ["fds-search__input", className].filter(Boolean).join(" "), ...rest }),
1093
+ shortcut && empty && /* @__PURE__ */ jsx17("kbd", { className: "fds-search__kbd", "aria-hidden": "true", children: shortcut })
1094
+ ] });
1095
+ });
1096
+
1097
+ // src/components/Pagination/Pagination.tsx
1098
+ import { jsx as jsx18, jsxs as jsxs16 } from "react/jsx-runtime";
1099
+ function pageItems(page, pageCount) {
1100
+ if (pageCount <= 7) return Array.from({ length: pageCount }, (_, i) => i + 1);
1101
+ const start = Math.max(2, Math.min(page - 1, pageCount - 4));
1102
+ const end = Math.min(pageCount - 1, Math.max(page + 1, 5));
1103
+ const items = [1];
1104
+ if (start > 2) items.push(null);
1105
+ for (let p = start; p <= end; p++) items.push(p);
1106
+ if (end < pageCount - 1) items.push(null);
1107
+ items.push(pageCount);
1108
+ return items;
1109
+ }
1110
+ function Arrow2({ dir }) {
1111
+ return /* @__PURE__ */ jsx18("svg", { viewBox: "0 0 20 20", width: "20", height: "20", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx18("path", { d: dir === "prev" ? "M12 15l-5-5 5-5" : "M8 5l5 5-5 5", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round", strokeLinejoin: "round" }) });
1112
+ }
1113
+ function Pagination({ page, pageCount, onPageChange, total, pageSize, variant = "full", className }) {
1114
+ if (pageCount <= 1) return null;
1115
+ const go = (p) => {
1116
+ if (p >= 1 && p <= pageCount && p !== page) onPageChange(p);
1117
+ };
1118
+ const showRange = variant === "full" && total != null && pageSize != null;
1119
+ const from = (page - 1) * (pageSize ?? 0) + 1;
1120
+ const to = Math.min(page * (pageSize ?? 0), total ?? 0);
1121
+ return /* @__PURE__ */ jsxs16("nav", { className: ["fds-pagination", `fds-pagination--${variant}`, className].filter(Boolean).join(" "), "aria-label": "Pagination", children: [
1122
+ showRange && /* @__PURE__ */ jsxs16("p", { className: "fds-pagination__range", children: [
1123
+ "Showing ",
1124
+ from,
1125
+ "\u2013",
1126
+ to,
1127
+ " of ",
1128
+ total
1129
+ ] }),
1130
+ /* @__PURE__ */ jsxs16("div", { className: "fds-pagination__controls", children: [
1131
+ /* @__PURE__ */ jsx18("button", { type: "button", className: "fds-pagination__arrow", onClick: () => go(page - 1), disabled: page <= 1, "aria-label": "Previous page", children: /* @__PURE__ */ jsx18(Arrow2, { dir: "prev" }) }),
1132
+ variant === "compact" ? /* @__PURE__ */ jsxs16("span", { className: "fds-pagination__status", "aria-live": "polite", children: [
1133
+ "Page ",
1134
+ page,
1135
+ " of ",
1136
+ pageCount
1137
+ ] }) : pageItems(page, pageCount).map(
1138
+ (p, i) => p === null ? /* @__PURE__ */ jsx18("span", { className: "fds-pagination__gap", "aria-hidden": "true", children: "\u2026" }, `gap-${i}`) : /* @__PURE__ */ jsx18(
1139
+ "button",
1140
+ {
1141
+ type: "button",
1142
+ className: `fds-pagination__page${p === page ? " fds-pagination__page--current" : ""}`,
1143
+ "aria-current": p === page ? "page" : void 0,
1144
+ "aria-label": `Page ${p}`,
1145
+ onClick: () => go(p),
1146
+ children: p
1147
+ },
1148
+ p
1149
+ )
1150
+ ),
1151
+ /* @__PURE__ */ jsx18("button", { type: "button", className: "fds-pagination__arrow", onClick: () => go(page + 1), disabled: page >= pageCount, "aria-label": "Next page", children: /* @__PURE__ */ jsx18(Arrow2, { dir: "next" }) })
1152
+ ] })
1153
+ ] });
1154
+ }
1155
+
1156
+ // src/components/EmptyState/EmptyState.tsx
1157
+ import { jsx as jsx19, jsxs as jsxs17 } from "react/jsx-runtime";
1158
+ function EmptyState({ illustration, title, description, action, footer, className, ...rest }) {
1159
+ return /* @__PURE__ */ jsxs17("div", { className: ["fds-empty", className].filter(Boolean).join(" "), ...rest, children: [
1160
+ illustration != null && /* @__PURE__ */ jsx19("div", { className: "fds-empty__illustration", children: illustration }),
1161
+ /* @__PURE__ */ jsxs17("div", { className: "fds-empty__text", children: [
1162
+ /* @__PURE__ */ jsx19("h2", { className: "fds-empty__title", children: title }),
1163
+ description != null && /* @__PURE__ */ jsx19("p", { className: "fds-empty__description", children: description })
1164
+ ] }),
1165
+ (action != null || footer != null) && /* @__PURE__ */ jsxs17("div", { className: "fds-empty__actions", children: [
1166
+ action,
1167
+ footer != null && /* @__PURE__ */ jsx19("div", { className: "fds-empty__footer", children: footer })
1168
+ ] })
1169
+ ] });
1170
+ }
1171
+ function StackedCardsIllustration({ className }) {
1172
+ return /* @__PURE__ */ jsxs17("div", { className: ["fds-stacked-cards", className].filter(Boolean).join(" "), "aria-hidden": "true", children: [
1173
+ /* @__PURE__ */ jsx19("div", { className: "fds-stacked-cards__card fds-stacked-cards__card--back" }),
1174
+ /* @__PURE__ */ jsxs17("div", { className: "fds-stacked-cards__card fds-stacked-cards__card--front", children: [
1175
+ /* @__PURE__ */ jsxs17("div", { className: "fds-stacked-cards__lines", children: [
1176
+ /* @__PURE__ */ jsx19("span", {}),
1177
+ /* @__PURE__ */ jsx19("span", {}),
1178
+ /* @__PURE__ */ jsx19("span", {})
1179
+ ] }),
1180
+ /* @__PURE__ */ jsxs17("svg", { width: "18", height: "18", viewBox: "0 0 18 18", fill: "none", className: "fds-stacked-cards__status", children: [
1181
+ /* @__PURE__ */ jsx19("rect", { x: "0.75", y: "0.75", width: "16.5", height: "16.5", rx: "8.25", stroke: "currentColor", strokeWidth: "1.5" }),
1182
+ /* @__PURE__ */ jsx19("circle", { cx: "9", cy: "9", r: "3", className: "fds-stacked-cards__statusDot" })
1183
+ ] })
1184
+ ] }),
1185
+ /* @__PURE__ */ jsx19("div", { className: "fds-stacked-cards__badge", children: /* @__PURE__ */ jsxs17("svg", { viewBox: "0 0 24 24", width: "20", height: "20", fill: "none", children: [
1186
+ /* @__PURE__ */ jsx19("circle", { cx: "11", cy: "11", r: "8", stroke: "currentColor", strokeWidth: "2" }),
1187
+ /* @__PURE__ */ jsx19("path", { d: "m21 21-4.3-4.3", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round" })
1188
+ ] }) })
1189
+ ] });
1190
+ }
1191
+
1192
+ // src/components/Skeleton/Skeleton.tsx
1193
+ import { jsx as jsx20 } from "react/jsx-runtime";
1194
+ var px = (v) => typeof v === "number" ? `${v}px` : v;
1195
+ function Skeleton({ shape = "rect", width, height, lines = 1, className, style, ...rest }) {
1196
+ const classes = ["fds-skeleton", `fds-skeleton--${shape}`, className].filter(Boolean).join(" ");
1197
+ if (shape === "text" && lines > 1) {
1198
+ return /* @__PURE__ */ jsx20("span", { className: "fds-skeleton-lines", style: { width: px(width) }, "aria-hidden": "true", children: Array.from({ length: lines }, (_, i) => /* @__PURE__ */ jsx20(
1199
+ "span",
1200
+ {
1201
+ className: classes,
1202
+ style: { height: px(height), width: i === lines - 1 ? "60%" : void 0, ...style },
1203
+ ...rest
1204
+ },
1205
+ i
1206
+ )) });
1207
+ }
1208
+ const size = shape === "circle" ? { width: px(width ?? height ?? 16), height: px(height ?? width ?? 16) } : { width: px(width), height: px(height) };
1209
+ return /* @__PURE__ */ jsx20("span", { className: classes, style: { ...size, ...style }, "aria-hidden": "true", ...rest });
1210
+ }
1211
+ function SkeletonGroup({ label = "Loading", className, children, ...rest }) {
1212
+ return /* @__PURE__ */ jsx20("div", { role: "status", "aria-busy": "true", "aria-label": label, className, ...rest, children });
1213
+ }
841
1214
  export {
842
1215
  AppNav,
1216
+ Avatar,
1217
+ Badge,
843
1218
  Button,
844
1219
  DiffPropertyList,
845
1220
  DiffSection,
1221
+ EmptyState,
846
1222
  MismatchCard,
847
1223
  Modal,
1224
+ MultiSelect,
1225
+ Pagination,
1226
+ SearchInput,
1227
+ SectionHeader,
848
1228
  SegmentedControl,
849
1229
  Select,
850
1230
  SettingsSection,
1231
+ Skeleton,
1232
+ SkeletonGroup,
1233
+ Split,
1234
+ StackedCardsIllustration,
851
1235
  TabNav,
1236
+ Tag,
852
1237
  Toggle,
853
1238
  breakpoints
854
1239
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usefidel/design-system",
3
- "version": "0.14.0",
3
+ "version": "0.16.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",