@usefidel/design-system 0.13.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10,6 +10,6 @@
10
10
  "webapp.css"
11
11
  ],
12
12
  "storybookUrl": null,
13
- "packageVersion": "0.13.0",
14
- "resolvedSha": "a9ed7fb5a355ee414279eb17bf75df5cc18a7dc1"
13
+ "packageVersion": "0.15.0",
14
+ "resolvedSha": "51806455235b517cf1c3b2695f590570786af610"
15
15
  }
package/dist/index.cjs CHANGED
@@ -20,12 +20,21 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
+ AppNav: () => AppNav,
24
+ Avatar: () => Avatar,
23
25
  Button: () => Button,
24
26
  DiffPropertyList: () => DiffPropertyList,
25
27
  DiffSection: () => DiffSection,
26
28
  MismatchCard: () => MismatchCard,
29
+ Modal: () => Modal,
30
+ SectionHeader: () => SectionHeader,
31
+ SegmentedControl: () => SegmentedControl,
27
32
  Select: () => Select,
28
- Toggle: () => Toggle
33
+ SettingsSection: () => SettingsSection,
34
+ Split: () => Split,
35
+ TabNav: () => TabNav,
36
+ Toggle: () => Toggle,
37
+ breakpoints: () => breakpoints
29
38
  });
30
39
  module.exports = __toCommonJS(index_exports);
31
40
 
@@ -594,12 +603,353 @@ var Toggle = (0, import_react4.forwardRef)(function Toggle2({ size = "medium", l
594
603
  ] })
595
604
  ] });
596
605
  });
606
+
607
+ // src/components/AppNav/AppNav.tsx
608
+ var import_react5 = require("react");
609
+ var import_jsx_runtime6 = require("react/jsx-runtime");
610
+ function AppNav({
611
+ logo,
612
+ actions,
613
+ children,
614
+ menuLabel = "Menu",
615
+ className,
616
+ ...rest
617
+ }) {
618
+ const [open, setOpen] = (0, import_react5.useState)(false);
619
+ const linksId = (0, import_react5.useId)();
620
+ const navRef = (0, import_react5.useRef)(null);
621
+ const toggleRef = (0, import_react5.useRef)(null);
622
+ (0, import_react5.useEffect)(() => {
623
+ if (!open) return;
624
+ const onKey = (e) => {
625
+ if (e.key === "Escape") {
626
+ setOpen(false);
627
+ toggleRef.current?.focus();
628
+ }
629
+ };
630
+ const onPointer = (e) => {
631
+ if (!navRef.current?.contains(e.target)) setOpen(false);
632
+ };
633
+ window.addEventListener("keydown", onKey);
634
+ document.addEventListener("mousedown", onPointer);
635
+ return () => {
636
+ window.removeEventListener("keydown", onKey);
637
+ document.removeEventListener("mousedown", onPointer);
638
+ };
639
+ }, [open]);
640
+ const classes = ["fds-app-nav", className].filter(Boolean).join(" ");
641
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("nav", { ref: navRef, className: classes, ...rest, children: [
642
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "fds-app-nav__logo", children: logo }),
643
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
644
+ "div",
645
+ {
646
+ id: linksId,
647
+ className: "fds-app-nav__links",
648
+ "data-open": open ? "" : void 0,
649
+ onClick: (e) => {
650
+ if (e.target.closest("a, button")) setOpen(false);
651
+ },
652
+ children
653
+ }
654
+ ),
655
+ actions != null && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "fds-app-nav__actions", children: actions }),
656
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
657
+ "button",
658
+ {
659
+ ref: toggleRef,
660
+ type: "button",
661
+ className: "fds-app-nav__toggle",
662
+ "aria-label": menuLabel,
663
+ "aria-expanded": open,
664
+ "aria-controls": linksId,
665
+ onClick: () => setOpen((v) => !v),
666
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("svg", { width: "20", height: "20", viewBox: "0 0 20 20", fill: "none", "aria-hidden": "true", children: open ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M5 5l10 10M15 5L5 15", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round" }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M3 6h14M3 10h14M3 14h14", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round" }) })
667
+ }
668
+ )
669
+ ] });
670
+ }
671
+
672
+ // src/components/TabNav/TabNav.tsx
673
+ var import_jsx_runtime7 = require("react/jsx-runtime");
674
+ function TabNav({ children, className, ...rest }) {
675
+ const classes = ["fds-tab-nav", className].filter(Boolean).join(" ");
676
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("nav", { className: classes, ...rest, children });
677
+ }
678
+
679
+ // src/components/SettingsSection/SettingsSection.tsx
680
+ var import_react6 = require("react");
681
+ var import_jsx_runtime8 = require("react/jsx-runtime");
682
+ function SettingsSection({
683
+ title,
684
+ description,
685
+ children,
686
+ className,
687
+ ...rest
688
+ }) {
689
+ const titleId = (0, import_react6.useId)();
690
+ const classes = ["fds-settings-section", className].filter(Boolean).join(" ");
691
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("section", { className: classes, "aria-labelledby": titleId, ...rest, children: [
692
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "fds-settings-section__head", children: [
693
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("h2", { id: titleId, className: "fds-settings-section__title", children: title }),
694
+ description != null && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { className: "fds-settings-section__description", children: description })
695
+ ] }),
696
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "fds-settings-section__body", children })
697
+ ] });
698
+ }
699
+
700
+ // src/components/Modal/Modal.tsx
701
+ var import_react7 = require("react");
702
+ var import_react_dom = require("react-dom");
703
+ var import_jsx_runtime9 = require("react/jsx-runtime");
704
+ var FOCUSABLE = 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
705
+ function Modal({
706
+ open,
707
+ onClose,
708
+ title,
709
+ description,
710
+ children,
711
+ actions,
712
+ size = "small",
713
+ dismissible = true,
714
+ tone = "default",
715
+ className
716
+ }) {
717
+ const titleId = (0, import_react7.useId)();
718
+ const descriptionId = (0, import_react7.useId)();
719
+ const dialogRef = (0, import_react7.useRef)(null);
720
+ const onCloseRef = (0, import_react7.useRef)(onClose);
721
+ onCloseRef.current = onClose;
722
+ const dismissibleRef = (0, import_react7.useRef)(dismissible);
723
+ dismissibleRef.current = dismissible;
724
+ (0, import_react7.useEffect)(() => {
725
+ if (!open) return;
726
+ const dialog = dialogRef.current;
727
+ const previouslyFocused = document.activeElement;
728
+ const initial = dialog?.querySelector("[data-autofocus]") ?? dialog?.querySelector(`.fds-modal__content ${FOCUSABLE}, .fds-modal__actions ${FOCUSABLE}`) ?? dialog;
729
+ initial?.focus();
730
+ const previousOverflow = document.body.style.overflow;
731
+ document.body.style.overflow = "hidden";
732
+ const onKey = (e) => {
733
+ if (e.key === "Escape" && dismissibleRef.current) {
734
+ e.preventDefault();
735
+ onCloseRef.current();
736
+ return;
737
+ }
738
+ if (e.key === "Tab" && dialog) {
739
+ const focusable = Array.from(dialog.querySelectorAll(FOCUSABLE));
740
+ if (focusable.length === 0) {
741
+ e.preventDefault();
742
+ return;
743
+ }
744
+ const first = focusable[0];
745
+ const last = focusable[focusable.length - 1];
746
+ if (e.shiftKey && (document.activeElement === first || document.activeElement === dialog)) {
747
+ e.preventDefault();
748
+ last.focus();
749
+ } else if (!e.shiftKey && document.activeElement === last) {
750
+ e.preventDefault();
751
+ first.focus();
752
+ }
753
+ }
754
+ };
755
+ window.addEventListener("keydown", onKey);
756
+ return () => {
757
+ window.removeEventListener("keydown", onKey);
758
+ document.body.style.overflow = previousOverflow;
759
+ previouslyFocused?.focus?.();
760
+ };
761
+ }, [open]);
762
+ if (!open || typeof document === "undefined") return null;
763
+ const classes = ["fds-modal", `fds-modal--${size}`, tone === "danger" && "fds-modal--danger", className].filter(Boolean).join(" ");
764
+ return (0, import_react_dom.createPortal)(
765
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
766
+ "div",
767
+ {
768
+ className: "fds-modal__scrim",
769
+ onMouseDown: (e) => {
770
+ if (e.target === e.currentTarget && dismissible) onClose();
771
+ },
772
+ children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
773
+ "div",
774
+ {
775
+ ref: dialogRef,
776
+ className: classes,
777
+ role: "dialog",
778
+ "aria-modal": "true",
779
+ "aria-labelledby": titleId,
780
+ "aria-describedby": description != null ? descriptionId : void 0,
781
+ tabIndex: -1,
782
+ children: [
783
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
784
+ "button",
785
+ {
786
+ type: "button",
787
+ className: "fds-modal__close",
788
+ onClick: onClose,
789
+ disabled: !dismissible,
790
+ "aria-label": "Close",
791
+ children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("path", { d: "M4 4l8 8M12 4l-8 8", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" }) })
792
+ }
793
+ ),
794
+ tone === "danger" && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "fds-modal__icon", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("svg", { width: "20", height: "20", viewBox: "0 0 20 20", fill: "none", children: [
795
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("path", { d: "M10 3.5L17.5 16.5H2.5L10 3.5z", stroke: "currentColor", strokeWidth: "1.5", strokeLinejoin: "round" }),
796
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("path", { d: "M10 8.5v3.5", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" }),
797
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("circle", { cx: "10", cy: "14.25", r: "0.9", fill: "currentColor" })
798
+ ] }) }),
799
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("h2", { id: titleId, className: "fds-modal__title", children: title }),
800
+ description != null && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { id: descriptionId, className: "fds-modal__description", children: description }),
801
+ children != null && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "fds-modal__content", children }),
802
+ actions != null && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "fds-modal__actions", children: actions })
803
+ ]
804
+ }
805
+ )
806
+ }
807
+ ),
808
+ document.body
809
+ );
810
+ }
811
+
812
+ // src/components/SegmentedControl/SegmentedControl.tsx
813
+ var import_react8 = require("react");
814
+ var import_jsx_runtime10 = require("react/jsx-runtime");
815
+ function SegmentedControl({
816
+ options,
817
+ value,
818
+ onChange,
819
+ "aria-label": ariaLabel,
820
+ className
821
+ }) {
822
+ const refs = (0, import_react8.useRef)([]);
823
+ const enabled = options.map((o, i) => o.disabled ? -1 : i).filter((i) => i >= 0);
824
+ const onKeyDown = (e, index) => {
825
+ const at = enabled.indexOf(index);
826
+ let next;
827
+ if (e.key === "ArrowRight") next = enabled[(at + 1) % enabled.length];
828
+ else if (e.key === "ArrowLeft") next = enabled[(at - 1 + enabled.length) % enabled.length];
829
+ else if (e.key === "Home") next = enabled[0];
830
+ else if (e.key === "End") next = enabled[enabled.length - 1];
831
+ if (next === void 0) return;
832
+ e.preventDefault();
833
+ onChange(options[next].value);
834
+ refs.current[next]?.focus();
835
+ };
836
+ const classes = ["fds-segmented", className].filter(Boolean).join(" ");
837
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { role: "tablist", "aria-label": ariaLabel, className: classes, children: options.map((option, i) => {
838
+ const selected = option.value === value;
839
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
840
+ "button",
841
+ {
842
+ ref: (el) => {
843
+ refs.current[i] = el;
844
+ },
845
+ type: "button",
846
+ role: "tab",
847
+ "aria-selected": selected,
848
+ "aria-controls": option.controls,
849
+ tabIndex: selected ? 0 : -1,
850
+ disabled: option.disabled,
851
+ className: "fds-segmented__option",
852
+ onClick: () => onChange(option.value),
853
+ onKeyDown: (e) => onKeyDown(e, i),
854
+ children: [
855
+ option.label,
856
+ option.count != null && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "fds-segmented__count", children: option.count })
857
+ ]
858
+ },
859
+ option.value
860
+ );
861
+ }) });
862
+ }
863
+
864
+ // src/breakpoints.ts
865
+ var breakpoints = {
866
+ /** Phones. */
867
+ sm: 480,
868
+ /** Large phones, small tablets. */
869
+ md: 640,
870
+ /** Tablets, narrow windows. */
871
+ lg: 900
872
+ };
873
+
874
+ // src/components/Avatar/Avatar.tsx
875
+ var import_react9 = require("react");
876
+ var import_jsx_runtime11 = require("react/jsx-runtime");
877
+ function initialsFor(name) {
878
+ const words = name.trim().split(/[\s._@-]+/).filter(Boolean);
879
+ if (words.length === 0) return "?";
880
+ if (words.length === 1) return words[0].slice(0, 2).toUpperCase();
881
+ return (words[0][0] + words[1][0]).toUpperCase();
882
+ }
883
+ function Avatar({ name, src, size = "medium", className, ...rest }) {
884
+ const [failedSrc, setFailedSrc] = (0, import_react9.useState)(null);
885
+ const showImage = !!src && src !== failedSrc;
886
+ const classes = ["fds-avatar", `fds-avatar--${size}`, className].filter(Boolean).join(" ");
887
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: classes, role: "img", "aria-label": name, ...rest, children: showImage ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("img", { className: "fds-avatar__img", src, alt: "", onError: () => setFailedSrc(src) }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { "aria-hidden": "true", children: initialsFor(name) }) });
888
+ }
889
+
890
+ // src/components/SectionHeader/SectionHeader.tsx
891
+ var import_jsx_runtime12 = require("react/jsx-runtime");
892
+ function SectionHeader({
893
+ title,
894
+ description,
895
+ as: Heading = "h2",
896
+ titleId,
897
+ className,
898
+ ...rest
899
+ }) {
900
+ const classes = ["fds-section-header", className].filter(Boolean).join(" ");
901
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: classes, ...rest, children: [
902
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Heading, { id: titleId, className: "fds-section-header__title", children: title }),
903
+ description != null && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { className: "fds-section-header__description", children: description })
904
+ ] });
905
+ }
906
+
907
+ // src/components/Split/Split.tsx
908
+ var import_jsx_runtime13 = require("react/jsx-runtime");
909
+ function Split({
910
+ aside,
911
+ children,
912
+ asideWidth = "280px",
913
+ sticky = false,
914
+ collapseBelow = "lg",
915
+ className,
916
+ style,
917
+ ...rest
918
+ }) {
919
+ const classes = [
920
+ "fds-split",
921
+ `fds-split--collapse-${collapseBelow}`,
922
+ sticky && "fds-split--sticky",
923
+ className
924
+ ].filter(Boolean).join(" ");
925
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
926
+ "div",
927
+ {
928
+ className: classes,
929
+ style: { "--fds-split-aside": asideWidth, ...style },
930
+ ...rest,
931
+ children: [
932
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "fds-split__aside", children: aside }),
933
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "fds-split__main", children })
934
+ ]
935
+ }
936
+ );
937
+ }
597
938
  // Annotate the CommonJS export names for ESM import in node:
598
939
  0 && (module.exports = {
940
+ AppNav,
941
+ Avatar,
599
942
  Button,
600
943
  DiffPropertyList,
601
944
  DiffSection,
602
945
  MismatchCard,
946
+ Modal,
947
+ SectionHeader,
948
+ SegmentedControl,
603
949
  Select,
604
- Toggle
950
+ SettingsSection,
951
+ Split,
952
+ TabNav,
953
+ Toggle,
954
+ breakpoints
605
955
  });