@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.
- package/dist/fidel.manifest.json +2 -2
- package/dist/index.cjs +352 -2
- package/dist/index.css +643 -0
- package/dist/index.d.cts +175 -2
- package/dist/index.d.ts +175 -2
- package/dist/index.js +342 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -571,11 +571,352 @@ var Toggle = forwardRef2(function Toggle2({ size = "medium", label, description,
|
|
|
571
571
|
] })
|
|
572
572
|
] });
|
|
573
573
|
});
|
|
574
|
+
|
|
575
|
+
// src/components/AppNav/AppNav.tsx
|
|
576
|
+
import { useEffect as useEffect2, useId as useId3, useRef as useRef2, useState as useState4 } from "react";
|
|
577
|
+
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
578
|
+
function AppNav({
|
|
579
|
+
logo,
|
|
580
|
+
actions,
|
|
581
|
+
children,
|
|
582
|
+
menuLabel = "Menu",
|
|
583
|
+
className,
|
|
584
|
+
...rest
|
|
585
|
+
}) {
|
|
586
|
+
const [open, setOpen] = useState4(false);
|
|
587
|
+
const linksId = useId3();
|
|
588
|
+
const navRef = useRef2(null);
|
|
589
|
+
const toggleRef = useRef2(null);
|
|
590
|
+
useEffect2(() => {
|
|
591
|
+
if (!open) return;
|
|
592
|
+
const onKey = (e) => {
|
|
593
|
+
if (e.key === "Escape") {
|
|
594
|
+
setOpen(false);
|
|
595
|
+
toggleRef.current?.focus();
|
|
596
|
+
}
|
|
597
|
+
};
|
|
598
|
+
const onPointer = (e) => {
|
|
599
|
+
if (!navRef.current?.contains(e.target)) setOpen(false);
|
|
600
|
+
};
|
|
601
|
+
window.addEventListener("keydown", onKey);
|
|
602
|
+
document.addEventListener("mousedown", onPointer);
|
|
603
|
+
return () => {
|
|
604
|
+
window.removeEventListener("keydown", onKey);
|
|
605
|
+
document.removeEventListener("mousedown", onPointer);
|
|
606
|
+
};
|
|
607
|
+
}, [open]);
|
|
608
|
+
const classes = ["fds-app-nav", className].filter(Boolean).join(" ");
|
|
609
|
+
return /* @__PURE__ */ jsxs6("nav", { ref: navRef, className: classes, ...rest, children: [
|
|
610
|
+
/* @__PURE__ */ jsx6("div", { className: "fds-app-nav__logo", children: logo }),
|
|
611
|
+
/* @__PURE__ */ jsx6(
|
|
612
|
+
"div",
|
|
613
|
+
{
|
|
614
|
+
id: linksId,
|
|
615
|
+
className: "fds-app-nav__links",
|
|
616
|
+
"data-open": open ? "" : void 0,
|
|
617
|
+
onClick: (e) => {
|
|
618
|
+
if (e.target.closest("a, button")) setOpen(false);
|
|
619
|
+
},
|
|
620
|
+
children
|
|
621
|
+
}
|
|
622
|
+
),
|
|
623
|
+
actions != null && /* @__PURE__ */ jsx6("div", { className: "fds-app-nav__actions", children: actions }),
|
|
624
|
+
/* @__PURE__ */ jsx6(
|
|
625
|
+
"button",
|
|
626
|
+
{
|
|
627
|
+
ref: toggleRef,
|
|
628
|
+
type: "button",
|
|
629
|
+
className: "fds-app-nav__toggle",
|
|
630
|
+
"aria-label": menuLabel,
|
|
631
|
+
"aria-expanded": open,
|
|
632
|
+
"aria-controls": linksId,
|
|
633
|
+
onClick: () => setOpen((v) => !v),
|
|
634
|
+
children: /* @__PURE__ */ jsx6("svg", { width: "20", height: "20", viewBox: "0 0 20 20", fill: "none", "aria-hidden": "true", children: open ? /* @__PURE__ */ jsx6("path", { d: "M5 5l10 10M15 5L5 15", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round" }) : /* @__PURE__ */ jsx6("path", { d: "M3 6h14M3 10h14M3 14h14", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round" }) })
|
|
635
|
+
}
|
|
636
|
+
)
|
|
637
|
+
] });
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
// src/components/TabNav/TabNav.tsx
|
|
641
|
+
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
642
|
+
function TabNav({ children, className, ...rest }) {
|
|
643
|
+
const classes = ["fds-tab-nav", className].filter(Boolean).join(" ");
|
|
644
|
+
return /* @__PURE__ */ jsx7("nav", { className: classes, ...rest, children });
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
// src/components/SettingsSection/SettingsSection.tsx
|
|
648
|
+
import { useId as useId4 } from "react";
|
|
649
|
+
import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
650
|
+
function SettingsSection({
|
|
651
|
+
title,
|
|
652
|
+
description,
|
|
653
|
+
children,
|
|
654
|
+
className,
|
|
655
|
+
...rest
|
|
656
|
+
}) {
|
|
657
|
+
const titleId = useId4();
|
|
658
|
+
const classes = ["fds-settings-section", className].filter(Boolean).join(" ");
|
|
659
|
+
return /* @__PURE__ */ jsxs7("section", { className: classes, "aria-labelledby": titleId, ...rest, children: [
|
|
660
|
+
/* @__PURE__ */ jsxs7("div", { className: "fds-settings-section__head", children: [
|
|
661
|
+
/* @__PURE__ */ jsx8("h2", { id: titleId, className: "fds-settings-section__title", children: title }),
|
|
662
|
+
description != null && /* @__PURE__ */ jsx8("p", { className: "fds-settings-section__description", children: description })
|
|
663
|
+
] }),
|
|
664
|
+
/* @__PURE__ */ jsx8("div", { className: "fds-settings-section__body", children })
|
|
665
|
+
] });
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
// src/components/Modal/Modal.tsx
|
|
669
|
+
import { useEffect as useEffect3, useId as useId5, useRef as useRef3 } from "react";
|
|
670
|
+
import { createPortal } from "react-dom";
|
|
671
|
+
import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
672
|
+
var FOCUSABLE = 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
|
|
673
|
+
function Modal({
|
|
674
|
+
open,
|
|
675
|
+
onClose,
|
|
676
|
+
title,
|
|
677
|
+
description,
|
|
678
|
+
children,
|
|
679
|
+
actions,
|
|
680
|
+
size = "small",
|
|
681
|
+
dismissible = true,
|
|
682
|
+
tone = "default",
|
|
683
|
+
className
|
|
684
|
+
}) {
|
|
685
|
+
const titleId = useId5();
|
|
686
|
+
const descriptionId = useId5();
|
|
687
|
+
const dialogRef = useRef3(null);
|
|
688
|
+
const onCloseRef = useRef3(onClose);
|
|
689
|
+
onCloseRef.current = onClose;
|
|
690
|
+
const dismissibleRef = useRef3(dismissible);
|
|
691
|
+
dismissibleRef.current = dismissible;
|
|
692
|
+
useEffect3(() => {
|
|
693
|
+
if (!open) return;
|
|
694
|
+
const dialog = dialogRef.current;
|
|
695
|
+
const previouslyFocused = document.activeElement;
|
|
696
|
+
const initial = dialog?.querySelector("[data-autofocus]") ?? dialog?.querySelector(`.fds-modal__content ${FOCUSABLE}, .fds-modal__actions ${FOCUSABLE}`) ?? dialog;
|
|
697
|
+
initial?.focus();
|
|
698
|
+
const previousOverflow = document.body.style.overflow;
|
|
699
|
+
document.body.style.overflow = "hidden";
|
|
700
|
+
const onKey = (e) => {
|
|
701
|
+
if (e.key === "Escape" && dismissibleRef.current) {
|
|
702
|
+
e.preventDefault();
|
|
703
|
+
onCloseRef.current();
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
706
|
+
if (e.key === "Tab" && dialog) {
|
|
707
|
+
const focusable = Array.from(dialog.querySelectorAll(FOCUSABLE));
|
|
708
|
+
if (focusable.length === 0) {
|
|
709
|
+
e.preventDefault();
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
const first = focusable[0];
|
|
713
|
+
const last = focusable[focusable.length - 1];
|
|
714
|
+
if (e.shiftKey && (document.activeElement === first || document.activeElement === dialog)) {
|
|
715
|
+
e.preventDefault();
|
|
716
|
+
last.focus();
|
|
717
|
+
} else if (!e.shiftKey && document.activeElement === last) {
|
|
718
|
+
e.preventDefault();
|
|
719
|
+
first.focus();
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
};
|
|
723
|
+
window.addEventListener("keydown", onKey);
|
|
724
|
+
return () => {
|
|
725
|
+
window.removeEventListener("keydown", onKey);
|
|
726
|
+
document.body.style.overflow = previousOverflow;
|
|
727
|
+
previouslyFocused?.focus?.();
|
|
728
|
+
};
|
|
729
|
+
}, [open]);
|
|
730
|
+
if (!open || typeof document === "undefined") return null;
|
|
731
|
+
const classes = ["fds-modal", `fds-modal--${size}`, tone === "danger" && "fds-modal--danger", className].filter(Boolean).join(" ");
|
|
732
|
+
return createPortal(
|
|
733
|
+
/* @__PURE__ */ jsx9(
|
|
734
|
+
"div",
|
|
735
|
+
{
|
|
736
|
+
className: "fds-modal__scrim",
|
|
737
|
+
onMouseDown: (e) => {
|
|
738
|
+
if (e.target === e.currentTarget && dismissible) onClose();
|
|
739
|
+
},
|
|
740
|
+
children: /* @__PURE__ */ jsxs8(
|
|
741
|
+
"div",
|
|
742
|
+
{
|
|
743
|
+
ref: dialogRef,
|
|
744
|
+
className: classes,
|
|
745
|
+
role: "dialog",
|
|
746
|
+
"aria-modal": "true",
|
|
747
|
+
"aria-labelledby": titleId,
|
|
748
|
+
"aria-describedby": description != null ? descriptionId : void 0,
|
|
749
|
+
tabIndex: -1,
|
|
750
|
+
children: [
|
|
751
|
+
/* @__PURE__ */ jsx9(
|
|
752
|
+
"button",
|
|
753
|
+
{
|
|
754
|
+
type: "button",
|
|
755
|
+
className: "fds-modal__close",
|
|
756
|
+
onClick: onClose,
|
|
757
|
+
disabled: !dismissible,
|
|
758
|
+
"aria-label": "Close",
|
|
759
|
+
children: /* @__PURE__ */ jsx9("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx9("path", { d: "M4 4l8 8M12 4l-8 8", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" }) })
|
|
760
|
+
}
|
|
761
|
+
),
|
|
762
|
+
tone === "danger" && /* @__PURE__ */ jsx9("div", { className: "fds-modal__icon", "aria-hidden": "true", children: /* @__PURE__ */ jsxs8("svg", { width: "20", height: "20", viewBox: "0 0 20 20", fill: "none", children: [
|
|
763
|
+
/* @__PURE__ */ jsx9("path", { d: "M10 3.5L17.5 16.5H2.5L10 3.5z", stroke: "currentColor", strokeWidth: "1.5", strokeLinejoin: "round" }),
|
|
764
|
+
/* @__PURE__ */ jsx9("path", { d: "M10 8.5v3.5", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" }),
|
|
765
|
+
/* @__PURE__ */ jsx9("circle", { cx: "10", cy: "14.25", r: "0.9", fill: "currentColor" })
|
|
766
|
+
] }) }),
|
|
767
|
+
/* @__PURE__ */ jsx9("h2", { id: titleId, className: "fds-modal__title", children: title }),
|
|
768
|
+
description != null && /* @__PURE__ */ jsx9("p", { id: descriptionId, className: "fds-modal__description", children: description }),
|
|
769
|
+
children != null && /* @__PURE__ */ jsx9("div", { className: "fds-modal__content", children }),
|
|
770
|
+
actions != null && /* @__PURE__ */ jsx9("div", { className: "fds-modal__actions", children: actions })
|
|
771
|
+
]
|
|
772
|
+
}
|
|
773
|
+
)
|
|
774
|
+
}
|
|
775
|
+
),
|
|
776
|
+
document.body
|
|
777
|
+
);
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
// src/components/SegmentedControl/SegmentedControl.tsx
|
|
781
|
+
import { useRef as useRef4 } from "react";
|
|
782
|
+
import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
783
|
+
function SegmentedControl({
|
|
784
|
+
options,
|
|
785
|
+
value,
|
|
786
|
+
onChange,
|
|
787
|
+
"aria-label": ariaLabel,
|
|
788
|
+
className
|
|
789
|
+
}) {
|
|
790
|
+
const refs = useRef4([]);
|
|
791
|
+
const enabled = options.map((o, i) => o.disabled ? -1 : i).filter((i) => i >= 0);
|
|
792
|
+
const onKeyDown = (e, index) => {
|
|
793
|
+
const at = enabled.indexOf(index);
|
|
794
|
+
let next;
|
|
795
|
+
if (e.key === "ArrowRight") next = enabled[(at + 1) % enabled.length];
|
|
796
|
+
else if (e.key === "ArrowLeft") next = enabled[(at - 1 + enabled.length) % enabled.length];
|
|
797
|
+
else if (e.key === "Home") next = enabled[0];
|
|
798
|
+
else if (e.key === "End") next = enabled[enabled.length - 1];
|
|
799
|
+
if (next === void 0) return;
|
|
800
|
+
e.preventDefault();
|
|
801
|
+
onChange(options[next].value);
|
|
802
|
+
refs.current[next]?.focus();
|
|
803
|
+
};
|
|
804
|
+
const classes = ["fds-segmented", className].filter(Boolean).join(" ");
|
|
805
|
+
return /* @__PURE__ */ jsx10("div", { role: "tablist", "aria-label": ariaLabel, className: classes, children: options.map((option, i) => {
|
|
806
|
+
const selected = option.value === value;
|
|
807
|
+
return /* @__PURE__ */ jsxs9(
|
|
808
|
+
"button",
|
|
809
|
+
{
|
|
810
|
+
ref: (el) => {
|
|
811
|
+
refs.current[i] = el;
|
|
812
|
+
},
|
|
813
|
+
type: "button",
|
|
814
|
+
role: "tab",
|
|
815
|
+
"aria-selected": selected,
|
|
816
|
+
"aria-controls": option.controls,
|
|
817
|
+
tabIndex: selected ? 0 : -1,
|
|
818
|
+
disabled: option.disabled,
|
|
819
|
+
className: "fds-segmented__option",
|
|
820
|
+
onClick: () => onChange(option.value),
|
|
821
|
+
onKeyDown: (e) => onKeyDown(e, i),
|
|
822
|
+
children: [
|
|
823
|
+
option.label,
|
|
824
|
+
option.count != null && /* @__PURE__ */ jsx10("span", { className: "fds-segmented__count", children: option.count })
|
|
825
|
+
]
|
|
826
|
+
},
|
|
827
|
+
option.value
|
|
828
|
+
);
|
|
829
|
+
}) });
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
// src/breakpoints.ts
|
|
833
|
+
var breakpoints = {
|
|
834
|
+
/** Phones. */
|
|
835
|
+
sm: 480,
|
|
836
|
+
/** Large phones, small tablets. */
|
|
837
|
+
md: 640,
|
|
838
|
+
/** Tablets, narrow windows. */
|
|
839
|
+
lg: 900
|
|
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
|
+
}
|
|
574
906
|
export {
|
|
907
|
+
AppNav,
|
|
908
|
+
Avatar,
|
|
575
909
|
Button,
|
|
576
910
|
DiffPropertyList,
|
|
577
911
|
DiffSection,
|
|
578
912
|
MismatchCard,
|
|
913
|
+
Modal,
|
|
914
|
+
SectionHeader,
|
|
915
|
+
SegmentedControl,
|
|
579
916
|
Select,
|
|
580
|
-
|
|
917
|
+
SettingsSection,
|
|
918
|
+
Split,
|
|
919
|
+
TabNav,
|
|
920
|
+
Toggle,
|
|
921
|
+
breakpoints
|
|
581
922
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@usefidel/design-system",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.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",
|