@sovereignfs/ui 0.21.3 → 0.23.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.
Files changed (36) hide show
  1. package/dist/Checkbox.module.css +97 -0
  2. package/dist/DragHandleRow.module.css +51 -0
  3. package/dist/FormField.module.css +5 -0
  4. package/dist/Input.module.css +10 -0
  5. package/dist/Select.module.css +9 -0
  6. package/dist/Textarea.module.css +39 -0
  7. package/dist/Toast.module.css +3 -3
  8. package/dist/Toggle.module.css +1 -1
  9. package/dist/index.d.ts +58 -5
  10. package/dist/index.js +218 -96
  11. package/dist/tokens/semantic.css +6 -0
  12. package/package.json +1 -1
  13. package/src/components/Checkbox/Checkbox.module.css +97 -0
  14. package/src/components/Checkbox/Checkbox.tsx +68 -0
  15. package/src/components/DragHandleRow/DragHandleRow.module.css +51 -0
  16. package/src/components/DragHandleRow/DragHandleRow.tsx +61 -0
  17. package/src/components/FormField/FormField.module.css +5 -0
  18. package/src/components/FormField/FormField.tsx +36 -11
  19. package/src/components/FormField/__tests__/FormField.test.tsx +83 -0
  20. package/src/components/Input/Input.module.css +10 -0
  21. package/src/components/Select/Select.module.css +9 -0
  22. package/src/components/Select/Select.tsx +10 -2
  23. package/src/components/Textarea/Textarea.module.css +39 -0
  24. package/src/components/Textarea/Textarea.tsx +15 -0
  25. package/src/components/Textarea/__tests__/Textarea.test.tsx +29 -0
  26. package/src/components/Toast/Toast.module.css +3 -3
  27. package/src/components/Toggle/Toggle.module.css +1 -1
  28. package/src/index.ts +7 -1
  29. package/src/stories/Checkbox.stories.tsx +45 -0
  30. package/src/stories/DesignSystemOverview.stories.tsx +64 -6
  31. package/src/stories/DragHandleRow.stories.tsx +41 -0
  32. package/src/stories/FormField.stories.tsx +8 -8
  33. package/src/stories/MobilePatterns.stories.tsx +4 -4
  34. package/src/stories/Textarea.stories.tsx +51 -0
  35. package/src/stories/TokenGallery.stories.tsx +2 -0
  36. package/src/tokens/semantic.css +6 -0
package/dist/index.js CHANGED
@@ -770,9 +770,9 @@ function Select({ className, size = "md", children, ...rest }) {
770
770
  return /* @__PURE__ */ jsxs23(
771
771
  "div",
772
772
  {
773
- className: [styles6.wrapper, size === "sm" ? styles6.sm : void 0].filter(Boolean).join(" "),
773
+ className: [styles6.wrapper, size === "sm" ? styles6.sm : void 0, className].filter(Boolean).join(" "),
774
774
  children: [
775
- /* @__PURE__ */ jsx34("select", { className: [styles6.select, className].filter(Boolean).join(" "), ...rest, children }),
775
+ /* @__PURE__ */ jsx34("select", { className: styles6.select, ...rest, children }),
776
776
  /* @__PURE__ */ jsx34(
777
777
  "svg",
778
778
  {
@@ -903,6 +903,14 @@ function Input({ type = "text", className, ...rest }) {
903
903
  return /* @__PURE__ */ jsx38("input", { type, className: classes, ...rest });
904
904
  }
905
905
 
906
+ // src/components/Textarea/Textarea.tsx
907
+ import styles11 from "./Textarea.module.css";
908
+ import { jsx as jsx39 } from "react/jsx-runtime";
909
+ function Textarea({ className, rows = 4, ...rest }) {
910
+ const classes = [styles11.textarea, className].filter(Boolean).join(" ");
911
+ return /* @__PURE__ */ jsx39("textarea", { rows, className: classes, ...rest });
912
+ }
913
+
906
914
  // src/components/Dialog/Dialog.tsx
907
915
  import { useEffect as useEffect2, useRef as useRef2 } from "react";
908
916
 
@@ -919,8 +927,8 @@ function unlockBodyScroll() {
919
927
  }
920
928
 
921
929
  // src/components/Dialog/Dialog.tsx
922
- import styles11 from "./Dialog.module.css";
923
- import { jsx as jsx39, jsxs as jsxs26 } from "react/jsx-runtime";
930
+ import styles12 from "./Dialog.module.css";
931
+ import { jsx as jsx40, jsxs as jsxs26 } from "react/jsx-runtime";
924
932
  var FOCUSABLE = 'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])';
925
933
  function Dialog({
926
934
  open,
@@ -979,10 +987,10 @@ function Dialog({
979
987
  return (
980
988
  // role="presentation" removes the scrim from the AT (it is purely visual).
981
989
  // e.target check lets clicks inside the panel bubble without triggering dismiss.
982
- /* @__PURE__ */ jsx39(
990
+ /* @__PURE__ */ jsx40(
983
991
  "div",
984
992
  {
985
- className: styles11.scrim,
993
+ className: styles12.scrim,
986
994
  role: "presentation",
987
995
  onClick: (e) => {
988
996
  if (e.target === e.currentTarget) onClose();
@@ -998,23 +1006,23 @@ function Dialog({
998
1006
  "aria-modal": "true",
999
1007
  "aria-label": ariaLabel ?? title,
1000
1008
  tabIndex: -1,
1001
- className: [styles11.panel, styles11[size]].join(" "),
1009
+ className: [styles12.panel, styles12[size]].join(" "),
1002
1010
  children: [
1003
- /* @__PURE__ */ jsxs26("div", { className: styles11.mobileBar, children: [
1004
- title && /* @__PURE__ */ jsx39("span", { className: styles11.mobileBarTitle, children: title }),
1005
- /* @__PURE__ */ jsx39(
1011
+ /* @__PURE__ */ jsxs26("div", { className: styles12.mobileBar, children: [
1012
+ title && /* @__PURE__ */ jsx40("span", { className: styles12.mobileBarTitle, children: title }),
1013
+ /* @__PURE__ */ jsx40(
1006
1014
  "button",
1007
1015
  {
1008
1016
  type: "button",
1009
- className: styles11.mobileBarClose,
1017
+ className: styles12.mobileBarClose,
1010
1018
  "aria-label": "Close",
1011
1019
  onClick: onClose,
1012
1020
  children: "\xD7"
1013
1021
  }
1014
1022
  )
1015
1023
  ] }),
1016
- /* @__PURE__ */ jsx39("button", { type: "button", className: styles11.close, "aria-label": "Close", onClick: onClose, children: "\xD7" }),
1017
- /* @__PURE__ */ jsx39("div", { className: styles11.content, children })
1024
+ /* @__PURE__ */ jsx40("button", { type: "button", className: styles12.close, "aria-label": "Close", onClick: onClose, children: "\xD7" }),
1025
+ /* @__PURE__ */ jsx40("div", { className: styles12.content, children })
1018
1026
  ]
1019
1027
  }
1020
1028
  )
@@ -1025,8 +1033,8 @@ function Dialog({
1025
1033
 
1026
1034
  // src/components/Drawer/Drawer.tsx
1027
1035
  import { useEffect as useEffect3, useRef as useRef3 } from "react";
1028
- import styles12 from "./Drawer.module.css";
1029
- import { jsx as jsx40 } from "react/jsx-runtime";
1036
+ import styles13 from "./Drawer.module.css";
1037
+ import { jsx as jsx41 } from "react/jsx-runtime";
1030
1038
  var FOCUSABLE2 = 'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])';
1031
1039
  function Drawer({ open, onClose, "aria-label": ariaLabel, children }) {
1032
1040
  const panelRef = useRef3(null);
@@ -1078,10 +1086,10 @@ function Drawer({ open, onClose, "aria-label": ariaLabel, children }) {
1078
1086
  return (
1079
1087
  // role="presentation" removes the scrim from the AT (it is purely visual).
1080
1088
  // e.target check lets clicks inside the panel bubble without triggering dismiss.
1081
- /* @__PURE__ */ jsx40(
1089
+ /* @__PURE__ */ jsx41(
1082
1090
  "div",
1083
1091
  {
1084
- className: styles12.scrim,
1092
+ className: styles13.scrim,
1085
1093
  role: "presentation",
1086
1094
  onClick: (e) => {
1087
1095
  if (e.target === e.currentTarget) onClose();
@@ -1089,7 +1097,7 @@ function Drawer({ open, onClose, "aria-label": ariaLabel, children }) {
1089
1097
  onKeyDown: (e) => {
1090
1098
  if (e.key === "Escape") onClose();
1091
1099
  },
1092
- children: /* @__PURE__ */ jsx40(
1100
+ children: /* @__PURE__ */ jsx41(
1093
1101
  "div",
1094
1102
  {
1095
1103
  ref: panelRef,
@@ -1097,7 +1105,7 @@ function Drawer({ open, onClose, "aria-label": ariaLabel, children }) {
1097
1105
  "aria-modal": "true",
1098
1106
  "aria-label": ariaLabel,
1099
1107
  tabIndex: -1,
1100
- className: styles12.panel,
1108
+ className: styles13.panel,
1101
1109
  children
1102
1110
  }
1103
1111
  )
@@ -1117,8 +1125,8 @@ import {
1117
1125
  useRef as useRef4,
1118
1126
  useState
1119
1127
  } from "react";
1120
- import styles13 from "./Toast.module.css";
1121
- import { jsx as jsx41, jsxs as jsxs27 } from "react/jsx-runtime";
1128
+ import styles14 from "./Toast.module.css";
1129
+ import { jsx as jsx42, jsxs as jsxs27 } from "react/jsx-runtime";
1122
1130
  var ToastContext = createContext(null);
1123
1131
  function useToast() {
1124
1132
  const ctx = useContext(ToastContext);
@@ -1158,14 +1166,14 @@ function ToastProvider({ children }) {
1158
1166
  const value = useMemo(() => ({ show, dismiss }), [show, dismiss]);
1159
1167
  return /* @__PURE__ */ jsxs27(ToastContext.Provider, { value, children: [
1160
1168
  children,
1161
- /* @__PURE__ */ jsx41(
1169
+ /* @__PURE__ */ jsx42(
1162
1170
  "ol",
1163
1171
  {
1164
- className: styles13.region,
1172
+ className: styles14.region,
1165
1173
  "aria-label": "Notifications",
1166
1174
  "aria-live": "polite",
1167
1175
  "aria-atomic": "false",
1168
- children: toasts.map((toast) => /* @__PURE__ */ jsx41(Toast, { ...toast, onDismiss: dismiss }, toast.id))
1176
+ children: toasts.map((toast) => /* @__PURE__ */ jsx42(Toast, { ...toast, onDismiss: dismiss }, toast.id))
1169
1177
  }
1170
1178
  )
1171
1179
  ] });
@@ -1186,24 +1194,24 @@ function Toast({
1186
1194
  exiting,
1187
1195
  onDismiss
1188
1196
  }) {
1189
- const categoryClass = styles13[category] ?? styles13.info;
1197
+ const categoryClass = styles14[category] ?? styles14.info;
1190
1198
  const iconName = CATEGORY_ICON[category] ?? "info";
1191
1199
  return /* @__PURE__ */ jsxs27(
1192
1200
  "li",
1193
1201
  {
1194
1202
  role: "status",
1195
- className: `${styles13.toast} ${String(categoryClass)} ${exiting ? styles13.exiting : ""}`,
1203
+ className: `${styles14.toast} ${String(categoryClass)} ${exiting ? styles14.exiting : ""}`,
1196
1204
  children: [
1197
- /* @__PURE__ */ jsx41("span", { className: styles13.icon, "aria-hidden": "true", children: /* @__PURE__ */ jsx41(Icon, { name: iconName, size: "sm", "aria-hidden": true }) }),
1198
- /* @__PURE__ */ jsxs27("div", { className: styles13.body, children: [
1199
- /* @__PURE__ */ jsx41("div", { className: styles13.title, children: title }),
1200
- message && /* @__PURE__ */ jsx41("div", { className: styles13.message, children: message })
1205
+ /* @__PURE__ */ jsx42("span", { className: styles14.icon, "aria-hidden": "true", children: /* @__PURE__ */ jsx42(Icon, { name: iconName, size: "sm", "aria-hidden": true }) }),
1206
+ /* @__PURE__ */ jsxs27("div", { className: styles14.body, children: [
1207
+ /* @__PURE__ */ jsx42("div", { className: styles14.title, children: title }),
1208
+ message && /* @__PURE__ */ jsx42("div", { className: styles14.message, children: message })
1201
1209
  ] }),
1202
- /* @__PURE__ */ jsx41(
1210
+ /* @__PURE__ */ jsx42(
1203
1211
  "button",
1204
1212
  {
1205
1213
  type: "button",
1206
- className: styles13.close,
1214
+ className: styles14.close,
1207
1215
  "aria-label": "Dismiss notification",
1208
1216
  onClick: () => onDismiss(id),
1209
1217
  children: "\u2715"
@@ -1215,12 +1223,12 @@ function Toast({
1215
1223
  }
1216
1224
 
1217
1225
  // src/components/Card/Card.tsx
1218
- import styles14 from "./Card.module.css";
1219
- import { jsx as jsx42 } from "react/jsx-runtime";
1226
+ import styles15 from "./Card.module.css";
1227
+ import { jsx as jsx43 } from "react/jsx-runtime";
1220
1228
  var paddingClass = {
1221
- sm: styles14.paddingSm,
1222
- md: styles14.paddingMd,
1223
- lg: styles14.paddingLg
1229
+ sm: styles15.paddingSm,
1230
+ md: styles15.paddingMd,
1231
+ lg: styles15.paddingLg
1224
1232
  };
1225
1233
  function Card({
1226
1234
  as: Tag = "div",
@@ -1230,118 +1238,126 @@ function Card({
1230
1238
  children,
1231
1239
  ...rest
1232
1240
  }) {
1233
- const cls = [styles14.card, paddingClass[padding], interactive && styles14.interactive, className].filter(Boolean).join(" ");
1234
- return /* @__PURE__ */ jsx42(Tag, { className: cls, ...rest, children });
1241
+ const cls = [styles15.card, paddingClass[padding], interactive && styles15.interactive, className].filter(Boolean).join(" ");
1242
+ return /* @__PURE__ */ jsx43(Tag, { className: cls, ...rest, children });
1235
1243
  }
1236
1244
 
1237
1245
  // src/components/FormField/FormField.tsx
1238
1246
  import { useId as useId2 } from "react";
1239
- import styles15 from "./FormField.module.css";
1240
- import { jsx as jsx43, jsxs as jsxs28 } from "react/jsx-runtime";
1247
+ import styles16 from "./FormField.module.css";
1248
+ import { jsx as jsx44, jsxs as jsxs28 } from "react/jsx-runtime";
1241
1249
  function FormField({
1242
1250
  label,
1243
1251
  hint,
1244
1252
  error,
1245
- htmlFor,
1253
+ id,
1246
1254
  required = false,
1247
- children,
1248
- className
1255
+ className,
1256
+ children
1249
1257
  }) {
1250
- const hintId = useId2();
1251
- const errorId = useId2();
1252
- const describedBy = [hint && hintId, error && errorId].filter(Boolean).join(" ") || void 0;
1253
- return /* @__PURE__ */ jsxs28("div", { className: [styles15.field, className].filter(Boolean).join(" "), children: [
1254
- /* @__PURE__ */ jsxs28("label", { className: styles15.label, htmlFor, children: [
1258
+ const generatedId = useId2();
1259
+ const fieldId = id ?? generatedId;
1260
+ const hintId = `${fieldId}-hint`;
1261
+ const errorId = `${fieldId}-error`;
1262
+ const describedBy = [hint && !error && hintId, error && errorId].filter(Boolean).join(" ") || void 0;
1263
+ const field = {
1264
+ id: fieldId,
1265
+ "aria-describedby": describedBy,
1266
+ "aria-invalid": error ? true : void 0,
1267
+ required: required || void 0
1268
+ };
1269
+ return /* @__PURE__ */ jsxs28("div", { className: [styles16.field, className].filter(Boolean).join(" "), children: [
1270
+ /* @__PURE__ */ jsxs28("label", { className: styles16.label, htmlFor: fieldId, children: [
1255
1271
  label,
1256
- required && /* @__PURE__ */ jsx43("span", { className: styles15.required, "aria-hidden": "true", children: "*" })
1272
+ required && /* @__PURE__ */ jsx44("span", { className: styles16.required, "aria-hidden": "true", children: "*" })
1257
1273
  ] }),
1258
- /* @__PURE__ */ jsx43("div", { "aria-describedby": describedBy, children }),
1259
- hint && !error && /* @__PURE__ */ jsx43("p", { id: hintId, className: styles15.hint, children: hint }),
1260
- error && /* @__PURE__ */ jsx43("p", { id: errorId, className: styles15.error, role: "alert", children: error })
1274
+ children(field),
1275
+ hint && !error && /* @__PURE__ */ jsx44("p", { id: hintId, className: styles16.hint, children: hint }),
1276
+ error && /* @__PURE__ */ jsx44("p", { id: errorId, className: styles16.error, role: "alert", children: error })
1261
1277
  ] });
1262
1278
  }
1263
1279
 
1264
1280
  // src/components/PageHeader/PageHeader.tsx
1265
- import styles16 from "./PageHeader.module.css";
1266
- import { jsx as jsx44, jsxs as jsxs29 } from "react/jsx-runtime";
1281
+ import styles17 from "./PageHeader.module.css";
1282
+ import { jsx as jsx45, jsxs as jsxs29 } from "react/jsx-runtime";
1267
1283
  function PageHeader({ title, description, action, className }) {
1268
- return /* @__PURE__ */ jsxs29("header", { className: [styles16.header, className].filter(Boolean).join(" "), children: [
1269
- /* @__PURE__ */ jsxs29("div", { className: styles16.text, children: [
1270
- /* @__PURE__ */ jsx44("h1", { className: styles16.title, children: title }),
1271
- description && /* @__PURE__ */ jsx44("p", { className: styles16.description, children: description })
1284
+ return /* @__PURE__ */ jsxs29("header", { className: [styles17.header, className].filter(Boolean).join(" "), children: [
1285
+ /* @__PURE__ */ jsxs29("div", { className: styles17.text, children: [
1286
+ /* @__PURE__ */ jsx45("h1", { className: styles17.title, children: title }),
1287
+ description && /* @__PURE__ */ jsx45("p", { className: styles17.description, children: description })
1272
1288
  ] }),
1273
- action && /* @__PURE__ */ jsx44("div", { className: styles16.action, children: action })
1289
+ action && /* @__PURE__ */ jsx45("div", { className: styles17.action, children: action })
1274
1290
  ] });
1275
1291
  }
1276
1292
 
1277
1293
  // src/components/EmptyState/EmptyState.tsx
1278
- import styles17 from "./EmptyState.module.css";
1279
- import { jsx as jsx45, jsxs as jsxs30 } from "react/jsx-runtime";
1294
+ import styles18 from "./EmptyState.module.css";
1295
+ import { jsx as jsx46, jsxs as jsxs30 } from "react/jsx-runtime";
1280
1296
  function EmptyState({ icon, heading, description, action, className }) {
1281
- return /* @__PURE__ */ jsxs30("div", { className: [styles17.root, className].filter(Boolean).join(" "), children: [
1282
- icon && /* @__PURE__ */ jsx45("span", { className: styles17.icon, children: /* @__PURE__ */ jsx45(Icon, { name: icon, size: "lg", "aria-hidden": true }) }),
1283
- /* @__PURE__ */ jsx45("h2", { className: styles17.heading, children: heading }),
1284
- description && /* @__PURE__ */ jsx45("p", { className: styles17.description, children: description }),
1297
+ return /* @__PURE__ */ jsxs30("div", { className: [styles18.root, className].filter(Boolean).join(" "), children: [
1298
+ icon && /* @__PURE__ */ jsx46("span", { className: styles18.icon, children: /* @__PURE__ */ jsx46(Icon, { name: icon, size: "lg", "aria-hidden": true }) }),
1299
+ /* @__PURE__ */ jsx46("h2", { className: styles18.heading, children: heading }),
1300
+ description && /* @__PURE__ */ jsx46("p", { className: styles18.description, children: description }),
1285
1301
  action
1286
1302
  ] });
1287
1303
  }
1288
1304
 
1289
1305
  // src/components/Spinner/Spinner.tsx
1290
- import styles18 from "./Spinner.module.css";
1291
- import { jsx as jsx46 } from "react/jsx-runtime";
1306
+ import styles19 from "./Spinner.module.css";
1307
+ import { jsx as jsx47 } from "react/jsx-runtime";
1292
1308
  var sizeClass = {
1293
- sm: styles18.sm,
1294
- md: styles18.md,
1295
- lg: styles18.lg
1309
+ sm: styles19.sm,
1310
+ md: styles19.md,
1311
+ lg: styles19.lg
1296
1312
  };
1297
1313
  function Spinner({ size = "md", label = "Loading\u2026", className }) {
1298
- return /* @__PURE__ */ jsx46(
1314
+ return /* @__PURE__ */ jsx47(
1299
1315
  "span",
1300
1316
  {
1301
1317
  role: "status",
1302
1318
  "aria-label": label,
1303
- className: [styles18.spinner, sizeClass[size], className].filter(Boolean).join(" ")
1319
+ className: [styles19.spinner, sizeClass[size], className].filter(Boolean).join(" ")
1304
1320
  }
1305
1321
  );
1306
1322
  }
1307
1323
 
1308
1324
  // src/components/Avatar/Avatar.tsx
1309
1325
  import { useState as useState2 } from "react";
1310
- import styles19 from "./Avatar.module.css";
1311
- import { jsx as jsx47 } from "react/jsx-runtime";
1326
+ import styles20 from "./Avatar.module.css";
1327
+ import { jsx as jsx48 } from "react/jsx-runtime";
1312
1328
  function initials(name) {
1313
1329
  const parts = name.trim().split(/\s+/);
1314
1330
  if (parts.length === 1) return (parts[0] ?? "").slice(0, 2).toUpperCase();
1315
1331
  return ((parts[0]?.[0] ?? "") + (parts[parts.length - 1]?.[0] ?? "")).toUpperCase();
1316
1332
  }
1317
1333
  var sizeClass2 = {
1318
- sm: styles19.sm,
1319
- md: styles19.md,
1320
- lg: styles19.lg
1334
+ sm: styles20.sm,
1335
+ md: styles20.md,
1336
+ lg: styles20.lg
1321
1337
  };
1322
1338
  function Avatar({ name, src, size = "md", className }) {
1323
1339
  const [imgFailed, setImgFailed] = useState2(false);
1324
- const cls = [styles19.avatar, sizeClass2[size], className].filter(Boolean).join(" ");
1340
+ const cls = [styles20.avatar, sizeClass2[size], className].filter(Boolean).join(" ");
1325
1341
  if (src && !imgFailed) {
1326
- return /* @__PURE__ */ jsx47("span", { className: cls, "aria-label": name, children: /* @__PURE__ */ jsx47("img", { src, alt: name, className: styles19.img, onError: () => setImgFailed(true) }) });
1342
+ return /* @__PURE__ */ jsx48("span", { className: cls, "aria-label": name, children: /* @__PURE__ */ jsx48("img", { src, alt: name, className: styles20.img, onError: () => setImgFailed(true) }) });
1327
1343
  }
1328
- return /* @__PURE__ */ jsx47("span", { className: cls, "aria-label": name, children: initials(name) });
1344
+ return /* @__PURE__ */ jsx48("span", { className: cls, "aria-label": name, children: initials(name) });
1329
1345
  }
1330
1346
 
1331
1347
  // src/components/NavTabs/NavTabs.tsx
1332
- import styles20 from "./NavTabs.module.css";
1333
- import { jsx as jsx48 } from "react/jsx-runtime";
1348
+ import styles21 from "./NavTabs.module.css";
1349
+ import { jsx as jsx49 } from "react/jsx-runtime";
1334
1350
  function NavTabs({ items, className, "aria-label": ariaLabel }) {
1335
- return /* @__PURE__ */ jsx48(
1351
+ return /* @__PURE__ */ jsx49(
1336
1352
  "nav",
1337
1353
  {
1338
- className: [styles20.nav, className].filter(Boolean).join(" "),
1354
+ className: [styles21.nav, className].filter(Boolean).join(" "),
1339
1355
  "aria-label": ariaLabel ?? "Page navigation",
1340
- children: items.map((item) => /* @__PURE__ */ jsx48(
1356
+ children: items.map((item) => /* @__PURE__ */ jsx49(
1341
1357
  "a",
1342
1358
  {
1343
1359
  href: item.href,
1344
- className: [styles20.link, item.active && styles20.active].filter(Boolean).join(" "),
1360
+ className: [styles21.link, item.active && styles21.active].filter(Boolean).join(" "),
1345
1361
  "aria-current": item.active ? "page" : void 0,
1346
1362
  children: item.label
1347
1363
  },
@@ -1353,27 +1369,132 @@ function NavTabs({ items, className, "aria-label": ariaLabel }) {
1353
1369
 
1354
1370
  // src/components/Tooltip/Tooltip.tsx
1355
1371
  import { useId as useId3 } from "react";
1356
- import styles21 from "./Tooltip.module.css";
1357
- import { jsx as jsx49, jsxs as jsxs31 } from "react/jsx-runtime";
1372
+ import styles22 from "./Tooltip.module.css";
1373
+ import { jsx as jsx50, jsxs as jsxs31 } from "react/jsx-runtime";
1358
1374
  var sideClass = {
1359
- top: styles21.top,
1360
- bottom: styles21.bottom,
1361
- left: styles21.left,
1362
- right: styles21.right
1375
+ top: styles22.top,
1376
+ bottom: styles22.bottom,
1377
+ left: styles22.left,
1378
+ right: styles22.right
1363
1379
  };
1364
1380
  function Tooltip({ content, children, side = "top" }) {
1365
1381
  const tipId = useId3();
1366
- return /* @__PURE__ */ jsxs31("span", { className: styles21.wrapper, children: [
1367
- /* @__PURE__ */ jsx49("span", { "aria-describedby": tipId, children }),
1368
- /* @__PURE__ */ jsx49("span", { id: tipId, role: "tooltip", className: [styles21.tip, sideClass[side]].join(" "), children: content })
1382
+ return /* @__PURE__ */ jsxs31("span", { className: styles22.wrapper, children: [
1383
+ /* @__PURE__ */ jsx50("span", { "aria-describedby": tipId, children }),
1384
+ /* @__PURE__ */ jsx50("span", { id: tipId, role: "tooltip", className: [styles22.tip, sideClass[side]].join(" "), children: content })
1369
1385
  ] });
1370
1386
  }
1387
+
1388
+ // src/components/Checkbox/Checkbox.tsx
1389
+ import styles23 from "./Checkbox.module.css";
1390
+ import { jsx as jsx51, jsxs as jsxs32 } from "react/jsx-runtime";
1391
+ function Checkbox({
1392
+ checked,
1393
+ onChange,
1394
+ label,
1395
+ strikeThrough = false,
1396
+ disabled,
1397
+ className,
1398
+ id,
1399
+ ...rest
1400
+ }) {
1401
+ const inputId = id ?? `checkbox-${label.toLowerCase().replace(/\s+/g, "-")}`;
1402
+ return /* @__PURE__ */ jsxs32("span", { className: [styles23.root, className].filter(Boolean).join(" "), children: [
1403
+ /* @__PURE__ */ jsxs32("span", { className: [styles23.box, checked ? styles23.checked : ""].filter(Boolean).join(" "), children: [
1404
+ /* @__PURE__ */ jsx51(
1405
+ "input",
1406
+ {
1407
+ ...rest,
1408
+ id: inputId,
1409
+ type: "checkbox",
1410
+ checked,
1411
+ disabled,
1412
+ className: styles23.input,
1413
+ onChange: (e) => onChange(e.target.checked)
1414
+ }
1415
+ ),
1416
+ checked && /* @__PURE__ */ jsx51("svg", { className: styles23.tick, viewBox: "0 0 10 8", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx51(
1417
+ "path",
1418
+ {
1419
+ d: "M1 4l3 3 5-6",
1420
+ stroke: "currentColor",
1421
+ strokeWidth: "1.5",
1422
+ strokeLinecap: "round",
1423
+ strokeLinejoin: "round"
1424
+ }
1425
+ ) })
1426
+ ] }),
1427
+ /* @__PURE__ */ jsx51(
1428
+ "label",
1429
+ {
1430
+ htmlFor: inputId,
1431
+ className: [
1432
+ styles23.label,
1433
+ strikeThrough && checked ? styles23.struck : "",
1434
+ disabled ? styles23.disabled : ""
1435
+ ].filter(Boolean).join(" "),
1436
+ children: label
1437
+ }
1438
+ )
1439
+ ] });
1440
+ }
1441
+
1442
+ // src/components/DragHandleRow/DragHandleRow.tsx
1443
+ import styles24 from "./DragHandleRow.module.css";
1444
+ import { jsx as jsx52, jsxs as jsxs33 } from "react/jsx-runtime";
1445
+ function DragHandleRow({
1446
+ children,
1447
+ handleProps,
1448
+ isDragging,
1449
+ className,
1450
+ ...rest
1451
+ }) {
1452
+ return /* @__PURE__ */ jsxs33(
1453
+ "div",
1454
+ {
1455
+ ...rest,
1456
+ className: [styles24.row, isDragging ? styles24.dragging : "", className].filter(Boolean).join(" "),
1457
+ children: [
1458
+ /* @__PURE__ */ jsx52(
1459
+ "button",
1460
+ {
1461
+ type: "button",
1462
+ "aria-label": "Drag to reorder",
1463
+ className: styles24.handle,
1464
+ tabIndex: -1,
1465
+ ...handleProps,
1466
+ children: /* @__PURE__ */ jsx52(DragIcon, {})
1467
+ }
1468
+ ),
1469
+ /* @__PURE__ */ jsx52("div", { className: styles24.content, children })
1470
+ ]
1471
+ }
1472
+ );
1473
+ }
1474
+ function DragIcon() {
1475
+ return /* @__PURE__ */ jsx52(
1476
+ "svg",
1477
+ {
1478
+ width: "14",
1479
+ height: "14",
1480
+ viewBox: "0 0 14 14",
1481
+ fill: "none",
1482
+ "aria-hidden": "true",
1483
+ className: styles24.icon,
1484
+ children: [3, 7, 11].map(
1485
+ (cy) => [4, 10].map((cx) => /* @__PURE__ */ jsx52("circle", { cx, cy, r: 1.2, fill: "currentColor" }, `${cx}-${cy}`))
1486
+ )
1487
+ }
1488
+ );
1489
+ }
1371
1490
  export {
1372
1491
  Avatar,
1373
1492
  Badge,
1374
1493
  Button,
1375
1494
  Card,
1495
+ Checkbox,
1376
1496
  Dialog,
1497
+ DragHandleRow,
1377
1498
  Drawer,
1378
1499
  EmptyState,
1379
1500
  FormField,
@@ -1387,6 +1508,7 @@ export {
1387
1508
  Spinner,
1388
1509
  SystemBanner,
1389
1510
  Tabs,
1511
+ Textarea,
1390
1512
  ToastProvider,
1391
1513
  Toggle,
1392
1514
  Tooltip,
@@ -25,6 +25,10 @@
25
25
 
26
26
  --sv-color-accent: var(--sv-grey-900);
27
27
  --sv-color-accent-hover: var(--sv-grey-700);
28
+ /* Derived from --sv-color-accent via color-mix, so it re-themes automatically —
29
+ no separate dark-mode override needed. Tinted background for badges/chips
30
+ paired with --sv-color-accent text. */
31
+ --sv-color-accent-subtle: color-mix(in srgb, var(--sv-color-accent) 12%, transparent);
28
32
 
29
33
  --sv-color-focus-ring: var(--sv-grey-900);
30
34
 
@@ -56,6 +60,7 @@
56
60
  --sv-shadow-popover:
57
61
  0 4px 16px rgba(0, 0, 0, 0.14), 0 2px 6px rgba(0, 0, 0, 0.08); /* e2 — floating panels */
58
62
  --sv-shadow-overlay: 0 10px 38px rgba(0, 0, 0, 0.18), 0 6px 12px rgba(0, 0, 0, 0.12);
63
+ --sv-shadow-control: 0 1px 3px rgba(0, 0, 0, 0.2); /* small interactive-control shadows, e.g. Toggle thumb */
59
64
 
60
65
  /*
61
66
  * Instance identity tokens (RFC 0027 / RFC 0032 rename). Distinct namespace from --sv-color-*:
@@ -108,4 +113,5 @@
108
113
  --sv-shadow-hover: 0 2px 6px rgba(0, 0, 0, 0.3);
109
114
  --sv-shadow-popover: 0 4px 16px rgba(0, 0, 0, 0.5), 0 2px 6px rgba(0, 0, 0, 0.3);
110
115
  --sv-shadow-overlay: 0 10px 38px rgba(0, 0, 0, 0.6), 0 6px 12px rgba(0, 0, 0, 0.5);
116
+ --sv-shadow-control: 0 1px 3px rgba(0, 0, 0, 0.5);
111
117
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sovereignfs/ui",
3
- "version": "0.21.3",
3
+ "version": "0.23.0",
4
4
  "type": "module",
5
5
  "description": "Sovereign Design System — design tokens and React components.",
6
6
  "keywords": [
@@ -0,0 +1,97 @@
1
+ .root {
2
+ display: inline-flex;
3
+ align-items: center;
4
+ gap: var(--sv-space-2);
5
+ cursor: pointer;
6
+ }
7
+
8
+ /* The visual box */
9
+ .box {
10
+ position: relative;
11
+ flex-shrink: 0;
12
+ width: 18px;
13
+ height: 18px;
14
+ border-radius: var(--sv-radius-sm);
15
+ border: 1.5px solid var(--sv-color-border-strong);
16
+ background: var(--sv-color-surface);
17
+ transition:
18
+ background-color 0.15s ease,
19
+ border-color 0.15s ease;
20
+ display: flex;
21
+ align-items: center;
22
+ justify-content: center;
23
+ }
24
+
25
+ .checked {
26
+ background-color: var(--sv-color-accent);
27
+ border-color: var(--sv-color-accent);
28
+ color: var(--sv-white);
29
+ }
30
+
31
+ /* Hide the native input visually but keep it accessible */
32
+ .input {
33
+ position: absolute;
34
+ inset: 0;
35
+ opacity: 0;
36
+ margin: 0;
37
+ width: 100%;
38
+ height: 100%;
39
+ cursor: pointer;
40
+ }
41
+
42
+ .input:disabled {
43
+ cursor: not-allowed;
44
+ }
45
+
46
+ .input:focus-visible + .tick,
47
+ .input:focus-visible {
48
+ outline: none;
49
+ }
50
+
51
+ .box:has(.input:focus-visible) {
52
+ outline: 2px solid var(--sv-color-focus-ring);
53
+ outline-offset: 2px;
54
+ }
55
+
56
+ .tick {
57
+ width: 10px;
58
+ height: 8px;
59
+ pointer-events: none;
60
+ }
61
+
62
+ /* Label */
63
+ .label {
64
+ font-size: var(--sv-font-size-sm);
65
+ color: var(--sv-color-text-primary);
66
+ cursor: pointer;
67
+ user-select: none;
68
+ transition: color 0.15s ease;
69
+ /* Strike-through animates via clip-path on a pseudo-element */
70
+ position: relative;
71
+ }
72
+
73
+ .label::after {
74
+ content: '';
75
+ position: absolute;
76
+ left: 0;
77
+ top: 50%;
78
+ width: 100%;
79
+ height: 1.5px;
80
+ background: var(--sv-color-text-muted);
81
+ transform: scaleX(0);
82
+ transform-origin: left center;
83
+ transition: transform 0.2s ease;
84
+ }
85
+
86
+ .struck {
87
+ color: var(--sv-color-text-muted);
88
+ }
89
+
90
+ .struck::after {
91
+ transform: scaleX(1);
92
+ }
93
+
94
+ .disabled {
95
+ opacity: 0.5;
96
+ cursor: not-allowed;
97
+ }