@vincihairclinic/design-system 0.1.2 → 0.2.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/README.md CHANGED
@@ -23,6 +23,7 @@ use these guides:
23
23
  - [Conversational form](docs/components/conversational-form.md)
24
24
  - [Navigation](docs/components/navigation.md)
25
25
  - [Cards and content](docs/components/cards.md)
26
+ - [Dashboard components](docs/components/dashboard.md)
26
27
  - [React](docs/platforms/react.md)
27
28
  - [WordPress and Elementor](docs/platforms/wordpress-elementor.md)
28
29
  - [Contributing](docs/contributing.md)
@@ -31,12 +32,12 @@ use these guides:
31
32
  ## React
32
33
 
33
34
  ```sh
34
- npm install @vinci-hair-clinic/design-system
35
+ npm install @vincihairclinic/design-system
35
36
  ```
36
37
 
37
38
  ```tsx
38
- import "@vinci-hair-clinic/design-system/styles.css";
39
- import { Button } from "@vinci-hair-clinic/design-system";
39
+ import "@vincihairclinic/design-system/styles.css";
40
+ import { Button } from "@vincihairclinic/design-system";
40
41
 
41
42
  export function BookingCallToAction() {
42
43
  return <Button>Book a consultation</Button>;
@@ -51,7 +52,7 @@ See the [Button API](docs/components/button.md) for variants and native props.
51
52
  make plugin
52
53
  ```
53
54
 
54
- Install `artifacts/vinci-design-system-wordpress-0.1.0.zip` from the WordPress
55
+ Install the versioned ZIP from `artifacts/` through the WordPress
55
56
  plugin screen. Apply documented `vinci-*` classes through Elementor's
56
57
  **Advanced → CSS Classes** field.
57
58
 
package/dist/index.cjs CHANGED
@@ -25,6 +25,7 @@ __export(index_exports, {
25
25
  Checkbox: () => Checkbox,
26
26
  ChoiceChip: () => ChoiceChip,
27
27
  ConversationPanel: () => ConversationPanel,
28
+ DashboardShell: () => DashboardShell,
28
29
  FieldWithAction: () => FieldWithAction,
29
30
  Footer: () => Footer,
30
31
  Header: () => Header,
@@ -32,6 +33,8 @@ __export(index_exports, {
32
33
  LocationCard: () => LocationCard,
33
34
  LocationCombobox: () => LocationCombobox,
34
35
  MessageBubble: () => MessageBubble,
36
+ Modal: () => Modal,
37
+ MultiSelect: () => MultiSelect,
35
38
  NorwoodOptionCard: () => NorwoodOptionCard,
36
39
  NumberInput: () => NumberInput,
37
40
  PasswordInput: () => PasswordInput,
@@ -41,6 +44,7 @@ __export(index_exports, {
41
44
  ResultsCard: () => ResultsCard,
42
45
  StickyCTABar: () => StickyCTABar,
43
46
  Switch: () => Switch,
47
+ Table: () => Table,
44
48
  TestimonialCard: () => TestimonialCard,
45
49
  TextInput: () => TextInput,
46
50
  TypingIndicator: () => TypingIndicator
@@ -908,6 +912,526 @@ function PinInput({
908
912
  error && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "vinci-form-helper is-error", id: errorId, children: error })
909
913
  ] });
910
914
  }
915
+
916
+ // src/components/MultiSelect.tsx
917
+ var import_react10 = require("react");
918
+ var import_jsx_runtime11 = require("react/jsx-runtime");
919
+ function ChevronIcon() {
920
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("svg", { viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("path", { d: "m8 10 4 4 4-4" }) });
921
+ }
922
+ function RemoveIcon() {
923
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("path", { d: "m4.5 4.5 7 7m0-7-7 7" }) });
924
+ }
925
+ function MultiSelect({
926
+ label,
927
+ options,
928
+ value,
929
+ onChange,
930
+ placeholder,
931
+ removeLabel,
932
+ instructions
933
+ }) {
934
+ const generatedId = (0, import_react10.useId)();
935
+ const rootRef = (0, import_react10.useRef)(null);
936
+ const triggerRef = (0, import_react10.useRef)(null);
937
+ const optionRefs = (0, import_react10.useRef)([]);
938
+ const [isOpen, setIsOpen] = (0, import_react10.useState)(false);
939
+ const [activeIndex, setActiveIndex] = (0, import_react10.useState)(0);
940
+ const labelId = `${generatedId}-label`;
941
+ const summaryId = `${generatedId}-summary`;
942
+ const listboxId = `${generatedId}-listbox`;
943
+ const instructionsId = `${generatedId}-instructions`;
944
+ const selectedOptions = options.filter((option) => value.includes(option.value));
945
+ (0, import_react10.useEffect)(() => {
946
+ if (!isOpen) return;
947
+ function handlePointerDown(event) {
948
+ if (!rootRef.current?.contains(event.target)) setIsOpen(false);
949
+ }
950
+ document.addEventListener("pointerdown", handlePointerDown);
951
+ return () => document.removeEventListener("pointerdown", handlePointerDown);
952
+ }, [isOpen]);
953
+ function focusOption(index) {
954
+ setActiveIndex(index);
955
+ requestAnimationFrame(() => optionRefs.current[index]?.focus());
956
+ }
957
+ function openList(preferredIndex) {
958
+ if (options.length === 0) return;
959
+ const firstSelectedIndex = options.findIndex(
960
+ (option) => value.includes(option.value)
961
+ );
962
+ const nextIndex = preferredIndex ?? (firstSelectedIndex >= 0 ? firstSelectedIndex : 0);
963
+ setIsOpen(true);
964
+ focusOption(nextIndex);
965
+ }
966
+ function closeList(returnFocus) {
967
+ setIsOpen(false);
968
+ if (returnFocus) requestAnimationFrame(() => triggerRef.current?.focus());
969
+ }
970
+ function toggleOption(optionValue) {
971
+ const selected = value.includes(optionValue);
972
+ onChange(
973
+ options.filter(
974
+ (option) => selected ? value.includes(option.value) && option.value !== optionValue : value.includes(option.value) || option.value === optionValue
975
+ ).map((option) => option.value)
976
+ );
977
+ }
978
+ function handleTriggerKeyDown(event) {
979
+ if (options.length > 0 && (event.key === "ArrowDown" || event.key === "ArrowUp")) {
980
+ event.preventDefault();
981
+ openList(event.key === "ArrowUp" ? options.length - 1 : void 0);
982
+ }
983
+ }
984
+ function handleControlClick(event) {
985
+ const target = event.target;
986
+ if (target.closest("[data-multiselect-chip]") || target.closest("button")) {
987
+ return;
988
+ }
989
+ if (!isOpen) openList();
990
+ }
991
+ function handleOptionKeyDown(event, index, optionValue) {
992
+ if (event.key === "ArrowDown") {
993
+ event.preventDefault();
994
+ focusOption((index + 1) % options.length);
995
+ return;
996
+ }
997
+ if (event.key === "ArrowUp") {
998
+ event.preventDefault();
999
+ focusOption((index - 1 + options.length) % options.length);
1000
+ return;
1001
+ }
1002
+ if (event.key === "Home" || event.key === "End") {
1003
+ event.preventDefault();
1004
+ focusOption(event.key === "Home" ? 0 : options.length - 1);
1005
+ return;
1006
+ }
1007
+ if (event.key === "Enter" || event.key === " ") {
1008
+ event.preventDefault();
1009
+ toggleOption(optionValue);
1010
+ return;
1011
+ }
1012
+ if (event.key === "Escape") {
1013
+ event.preventDefault();
1014
+ closeList(true);
1015
+ return;
1016
+ }
1017
+ if (event.key === "Tab") setIsOpen(false);
1018
+ }
1019
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { ref: rootRef, className: "vinci-multiselect__field", children: [
1020
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { id: labelId, className: "vinci-multiselect__label", children: label }),
1021
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { id: instructionsId, className: "vinci-multiselect__visually-hidden", children: instructions }),
1022
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
1023
+ "div",
1024
+ {
1025
+ className: `vinci-multiselect__control ${isOpen ? "vinci-multiselect__control-open" : ""}`,
1026
+ onClick: handleControlClick,
1027
+ children: [
1028
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { id: summaryId, className: "vinci-multiselect__selection", children: selectedOptions.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "vinci-multiselect__placeholder", children: placeholder }) : selectedOptions.map((option) => /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
1029
+ "span",
1030
+ {
1031
+ className: "vinci-multiselect__chip",
1032
+ "data-multiselect-chip": true,
1033
+ children: [
1034
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { children: option.label }),
1035
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1036
+ "button",
1037
+ {
1038
+ type: "button",
1039
+ className: "vinci-multiselect__remove-button",
1040
+ "aria-label": removeLabel.replace("{label}", option.label),
1041
+ onClick: (event) => {
1042
+ event.stopPropagation();
1043
+ toggleOption(option.value);
1044
+ requestAnimationFrame(() => triggerRef.current?.focus());
1045
+ },
1046
+ children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(RemoveIcon, {})
1047
+ }
1048
+ )
1049
+ ]
1050
+ },
1051
+ option.value
1052
+ )) }),
1053
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1054
+ "button",
1055
+ {
1056
+ ref: triggerRef,
1057
+ type: "button",
1058
+ className: "vinci-multiselect__trigger",
1059
+ "aria-labelledby": `${labelId} ${summaryId}`,
1060
+ "aria-describedby": instructionsId,
1061
+ "aria-haspopup": "listbox",
1062
+ "aria-expanded": isOpen,
1063
+ "aria-controls": listboxId,
1064
+ onClick: () => isOpen ? closeList(false) : openList(),
1065
+ onKeyDown: handleTriggerKeyDown,
1066
+ children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(ChevronIcon, {})
1067
+ }
1068
+ )
1069
+ ]
1070
+ }
1071
+ ),
1072
+ isOpen && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1073
+ "ul",
1074
+ {
1075
+ id: listboxId,
1076
+ className: "vinci-multiselect__listbox",
1077
+ role: "listbox",
1078
+ "aria-labelledby": labelId,
1079
+ "aria-multiselectable": "true",
1080
+ children: options.map((option, index) => {
1081
+ const selected = value.includes(option.value);
1082
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
1083
+ "li",
1084
+ {
1085
+ ref: (element) => {
1086
+ optionRefs.current[index] = element;
1087
+ },
1088
+ className: "vinci-multiselect__option",
1089
+ role: "option",
1090
+ "aria-selected": selected,
1091
+ tabIndex: activeIndex === index ? 0 : -1,
1092
+ "data-active": activeIndex === index,
1093
+ onClick: () => toggleOption(option.value),
1094
+ onFocus: () => setActiveIndex(index),
1095
+ onKeyDown: (event) => handleOptionKeyDown(event, index, option.value),
1096
+ children: [
1097
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "vinci-multiselect__option-indicator", "aria-hidden": "true", children: selected && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("svg", { viewBox: "0 0 16 16", fill: "none", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("path", { d: "m3.5 8 3 3 6-6" }) }) }),
1098
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { children: option.label })
1099
+ ]
1100
+ },
1101
+ option.value
1102
+ );
1103
+ })
1104
+ }
1105
+ )
1106
+ ] });
1107
+ }
1108
+
1109
+ // src/components/Modal.tsx
1110
+ var import_react11 = require("react");
1111
+ var import_jsx_runtime12 = require("react/jsx-runtime");
1112
+ var Modal = (0, import_react11.forwardRef)(function Modal2({
1113
+ open,
1114
+ onClose,
1115
+ title,
1116
+ description,
1117
+ closeLabel,
1118
+ children,
1119
+ size = "default",
1120
+ className
1121
+ }, ref) {
1122
+ const dialogRef = (0, import_react11.useRef)(null);
1123
+ const timerRef = (0, import_react11.useRef)(null);
1124
+ const returnFocusRef = (0, import_react11.useRef)(null);
1125
+ const id = (0, import_react11.useId)();
1126
+ const titleId = `${id}-title`;
1127
+ const descriptionId = `${id}-description`;
1128
+ function close() {
1129
+ const dialog = dialogRef.current;
1130
+ if (!dialog?.open || dialog.dataset.closing === "true") return;
1131
+ dialog.dataset.closing = "true";
1132
+ const duration = window.matchMedia?.("(prefers-reduced-motion: reduce)").matches ? 100 : 160;
1133
+ timerRef.current = window.setTimeout(() => {
1134
+ dialog.close();
1135
+ delete dialog.dataset.closing;
1136
+ returnFocusRef.current?.focus();
1137
+ onClose();
1138
+ }, duration);
1139
+ }
1140
+ (0, import_react11.useImperativeHandle)(ref, () => ({ close }));
1141
+ (0, import_react11.useEffect)(() => {
1142
+ const dialog = dialogRef.current;
1143
+ if (!dialog) return;
1144
+ if (open && !dialog.open) {
1145
+ returnFocusRef.current = document.activeElement instanceof HTMLElement ? document.activeElement : null;
1146
+ dialog.showModal();
1147
+ } else if (!open && dialog.open) {
1148
+ if (timerRef.current !== null) window.clearTimeout(timerRef.current);
1149
+ dialog.close();
1150
+ delete dialog.dataset.closing;
1151
+ returnFocusRef.current?.focus();
1152
+ }
1153
+ }, [open]);
1154
+ (0, import_react11.useEffect)(() => () => {
1155
+ if (timerRef.current !== null) window.clearTimeout(timerRef.current);
1156
+ }, []);
1157
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1158
+ "dialog",
1159
+ {
1160
+ ref: dialogRef,
1161
+ className: ["vinci-modal", size === "compact" && "vinci-modal--compact", className].filter(Boolean).join(" "),
1162
+ "aria-labelledby": titleId,
1163
+ "aria-describedby": description ? descriptionId : void 0,
1164
+ onClick: (event) => {
1165
+ if (event.target === event.currentTarget) close();
1166
+ },
1167
+ onCancel: (event) => {
1168
+ event.preventDefault();
1169
+ close();
1170
+ },
1171
+ children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "vinci-modal__panel", children: [
1172
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "vinci-modal__header", children: [
1173
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { children: [
1174
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("h2", { id: titleId, children: title }),
1175
+ description && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { id: descriptionId, children: description })
1176
+ ] }),
1177
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("button", { type: "button", className: "vinci-modal__close", "aria-label": closeLabel, onClick: close, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("svg", { viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("path", { d: "m6 6 12 12M18 6 6 18" }) }) })
1178
+ ] }),
1179
+ children
1180
+ ] })
1181
+ }
1182
+ );
1183
+ });
1184
+
1185
+ // src/components/Table.tsx
1186
+ var import_jsx_runtime13 = require("react/jsx-runtime");
1187
+ function Table({ caption, children, className }) {
1188
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "vinci-table-scroll", role: "region", "aria-label": caption, tabIndex: 0, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("table", { className: ["vinci-table", className].filter(Boolean).join(" "), children: [
1189
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("caption", { className: "vinci-visually-hidden", children: caption }),
1190
+ children
1191
+ ] }) });
1192
+ }
1193
+
1194
+ // src/components/DashboardShell.tsx
1195
+ var import_react12 = require("react");
1196
+ var import_jsx_runtime14 = require("react/jsx-runtime");
1197
+ function Icon({ name }) {
1198
+ const paths = {
1199
+ chevron: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("path", { d: "m8 10 4 4 4-4" }),
1200
+ menu: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("path", { d: "M4 7h16M4 12h16M4 17h16" }),
1201
+ close: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("path", { d: "m6 6 12 12M18 6 6 18" }),
1202
+ search: /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
1203
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("circle", { cx: "11", cy: "11", r: "6.5" }),
1204
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("path", { d: "m16 16 4 4" })
1205
+ ] }),
1206
+ notification: /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
1207
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("path", { d: "M18 9a6 6 0 0 0-12 0c0 7-3 7-3 8.5h18C21 16 18 16 18 9Z" }),
1208
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("path", { d: "M10 20h4" })
1209
+ ] }),
1210
+ settings: /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
1211
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("circle", { cx: "12", cy: "12", r: "3" }),
1212
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("path", { d: "M19.4 15a1.7 1.7 0 0 0 .34 1.88l.06.06-2.86 2.86-.06-.06A1.7 1.7 0 0 0 15 19.4a1.7 1.7 0 0 0-1 .6 1.7 1.7 0 0 0-.4 1.1V21H9.5v-.1A1.7 1.7 0 0 0 8.4 19.4a1.7 1.7 0 0 0-1.88.34l-.06.06-2.86-2.86.06-.06A1.7 1.7 0 0 0 4 15a1.7 1.7 0 0 0-.6-1 1.7 1.7 0 0 0-1.1-.4H2V9.5h.3A1.7 1.7 0 0 0 4 8.4a1.7 1.7 0 0 0-.34-1.88l-.06-.06L6.46 3.6l.06.06A1.7 1.7 0 0 0 8.4 4a1.7 1.7 0 0 0 1-.6A1.7 1.7 0 0 0 9.8 2.3V2h4.1v.3A1.7 1.7 0 0 0 15 4a1.7 1.7 0 0 0 1.88-.34l.06-.06 2.86 2.86-.06.06A1.7 1.7 0 0 0 19.4 8.4a1.7 1.7 0 0 0 .6 1 1.7 1.7 0 0 0 1.1.4h.1v4.1h-.1A1.7 1.7 0 0 0 19.4 15Z" })
1213
+ ] }),
1214
+ logout: /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
1215
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("path", { d: "M10 5H6a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h4" }),
1216
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("path", { d: "m15 8 4 4-4 4m4-4H9" })
1217
+ ] })
1218
+ };
1219
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("svg", { viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", children: paths[name] });
1220
+ }
1221
+ function Navigation({ groups, activePath, idPrefix, label, renderLink, onNavigate }) {
1222
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("nav", { className: "vinci-dashboard__navigation", "aria-label": label, children: [
1223
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "vinci-dashboard__nav-track", "aria-hidden": "true" }),
1224
+ groups.map((group) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1225
+ NavigationGroup,
1226
+ {
1227
+ group,
1228
+ activePath,
1229
+ panelId: `${idPrefix}-${group.id}-items`,
1230
+ renderLink,
1231
+ onNavigate
1232
+ },
1233
+ `${group.id}-${activePath}`
1234
+ ))
1235
+ ] });
1236
+ }
1237
+ function NavigationGroup({ group, activePath, panelId, renderLink, onNavigate }) {
1238
+ const activeIndex = group.items.findIndex((item) => item.href === activePath);
1239
+ const [isOpen, setIsOpen] = (0, import_react12.useState)(activeIndex >= 0);
1240
+ const activeIndicatorStyle = { "--active-offset": `${Math.max(activeIndex, 0) * 56}px` };
1241
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "vinci-dashboard__nav-group", children: [
1242
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
1243
+ "button",
1244
+ {
1245
+ type: "button",
1246
+ className: "vinci-dashboard__nav-group-trigger",
1247
+ "aria-expanded": isOpen,
1248
+ "aria-controls": panelId,
1249
+ onClick: () => setIsOpen((value) => !value),
1250
+ children: [
1251
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { children: group.label }),
1252
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "vinci-dashboard__nav-chevron", children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Icon, { name: "chevron" }) })
1253
+ ]
1254
+ }
1255
+ ),
1256
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { id: panelId, className: `vinci-dashboard__nav-group-panel ${isOpen ? "vinci-dashboard__nav-group-panel-open" : ""}`, "aria-hidden": !isOpen, children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "vinci-dashboard__nav-group-panel-inner", children: /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "vinci-dashboard__nav-items", style: activeIndicatorStyle, children: [
1257
+ activeIndex >= 0 && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "vinci-dashboard__nav-active-indicator", "aria-hidden": "true" }),
1258
+ group.items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react12.Fragment, { children: renderLink({
1259
+ href: item.href,
1260
+ className: `vinci-dashboard__nav-link ${activePath === item.href ? "vinci-dashboard__nav-link-active" : ""}`,
1261
+ "aria-current": activePath === item.href ? "page" : void 0,
1262
+ tabIndex: isOpen ? void 0 : -1,
1263
+ onClick: onNavigate,
1264
+ children: /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
1265
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "vinci-dashboard__nav-icon", children: item.icon }),
1266
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { children: item.label })
1267
+ ] })
1268
+ }) }, item.href))
1269
+ ] }) }) })
1270
+ ] });
1271
+ }
1272
+ function DashboardShell({
1273
+ children,
1274
+ brand,
1275
+ navigationGroups,
1276
+ activePath,
1277
+ title,
1278
+ user,
1279
+ onSignOut,
1280
+ labels,
1281
+ renderLink = (props) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("a", { ...props }),
1282
+ footer
1283
+ }) {
1284
+ const id = (0, import_react12.useId)();
1285
+ const dialogRef = (0, import_react12.useRef)(null);
1286
+ const menuButtonRef = (0, import_react12.useRef)(null);
1287
+ const profileRef = (0, import_react12.useRef)(null);
1288
+ const profileButtonRef = (0, import_react12.useRef)(null);
1289
+ const [isProfileOpen, setIsProfileOpen] = (0, import_react12.useState)(false);
1290
+ (0, import_react12.useEffect)(() => {
1291
+ if (!isProfileOpen) return;
1292
+ function dismiss(event) {
1293
+ if (!profileRef.current?.contains(event.target)) {
1294
+ setIsProfileOpen(false);
1295
+ profileButtonRef.current?.focus();
1296
+ }
1297
+ }
1298
+ function escape(event) {
1299
+ if (event.key === "Escape") {
1300
+ setIsProfileOpen(false);
1301
+ profileButtonRef.current?.focus();
1302
+ }
1303
+ }
1304
+ document.addEventListener("pointerdown", dismiss);
1305
+ document.addEventListener("keydown", escape);
1306
+ return () => {
1307
+ document.removeEventListener("pointerdown", dismiss);
1308
+ document.removeEventListener("keydown", escape);
1309
+ };
1310
+ }, [isProfileOpen]);
1311
+ function closeMenu() {
1312
+ dialogRef.current?.close();
1313
+ }
1314
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "vinci-dashboard__shell", children: [
1315
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("aside", { className: "vinci-dashboard__sidebar", children: [
1316
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "vinci-dashboard__brand", children: brand }),
1317
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1318
+ Navigation,
1319
+ {
1320
+ groups: navigationGroups,
1321
+ activePath,
1322
+ idPrefix: `${id}-sidebar`,
1323
+ label: labels.navigation,
1324
+ renderLink
1325
+ }
1326
+ ),
1327
+ footer && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { className: "vinci-dashboard__sidebar-footer", children: footer })
1328
+ ] }),
1329
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "vinci-dashboard__workspace", children: [
1330
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("header", { className: "vinci-dashboard__header", children: [
1331
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1332
+ "button",
1333
+ {
1334
+ ref: menuButtonRef,
1335
+ type: "button",
1336
+ className: "vinci-dashboard__icon-button vinci-dashboard__menu-button",
1337
+ "aria-label": labels.openMenu,
1338
+ onClick: () => dialogRef.current?.showModal(),
1339
+ children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Icon, { name: "menu" })
1340
+ }
1341
+ ),
1342
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("label", { className: "vinci-dashboard__search-field", title: labels.comingSoon, children: [
1343
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "vinci-visually-hidden", children: labels.search }),
1344
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Icon, { name: "search" }),
1345
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("input", { type: "search", placeholder: labels.searchPlaceholder, disabled: true })
1346
+ ] }),
1347
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "vinci-dashboard__header-actions", children: [
1348
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1349
+ "button",
1350
+ {
1351
+ type: "button",
1352
+ className: "vinci-dashboard__header-action",
1353
+ "aria-label": `${labels.notifications} \u2014 ${labels.comingSoon}`,
1354
+ title: `${labels.notifications} \u2014 ${labels.comingSoon}`,
1355
+ disabled: true,
1356
+ children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Icon, { name: "notification" })
1357
+ }
1358
+ ),
1359
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1360
+ "button",
1361
+ {
1362
+ type: "button",
1363
+ className: "vinci-dashboard__header-action",
1364
+ "aria-label": `${labels.settings} \u2014 ${labels.comingSoon}`,
1365
+ title: `${labels.settings} \u2014 ${labels.comingSoon}`,
1366
+ disabled: true,
1367
+ children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Icon, { name: "settings" })
1368
+ }
1369
+ ),
1370
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { ref: profileRef, className: "vinci-dashboard__profile", children: [
1371
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1372
+ "button",
1373
+ {
1374
+ ref: profileButtonRef,
1375
+ type: "button",
1376
+ className: "vinci-dashboard__profile-button",
1377
+ "aria-label": labels.profile.replace("{name}", user.name),
1378
+ "aria-expanded": isProfileOpen,
1379
+ "aria-controls": `${id}-profile-menu`,
1380
+ onClick: () => setIsProfileOpen((value) => !value),
1381
+ children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "vinci-dashboard__avatar", "aria-hidden": "true", children: user.initials })
1382
+ }
1383
+ ),
1384
+ isProfileOpen && /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { id: `${id}-profile-menu`, className: "vinci-dashboard__profile-menu", role: "region", "aria-label": labels.profileMenu, children: [
1385
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { className: "vinci-dashboard__profile-eyebrow", children: labels.signedInAs }),
1386
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { className: "vinci-dashboard__profile-name", children: user.name }),
1387
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { className: "vinci-dashboard__profile-email", children: user.email }),
1388
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("button", { type: "button", className: "vinci-dashboard__sign-out", onClick: () => {
1389
+ setIsProfileOpen(false);
1390
+ onSignOut();
1391
+ }, children: [
1392
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Icon, { name: "logout" }),
1393
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { children: labels.signOut })
1394
+ ] })
1395
+ ] })
1396
+ ] })
1397
+ ] })
1398
+ ] }),
1399
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("main", { className: "vinci-dashboard__main", children: [
1400
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("h1", { className: "vinci-visually-hidden", children: title }),
1401
+ children
1402
+ ] })
1403
+ ] }),
1404
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1405
+ "dialog",
1406
+ {
1407
+ ref: dialogRef,
1408
+ className: "vinci-dashboard__drawer",
1409
+ "aria-label": labels.navigation,
1410
+ onClick: (event) => {
1411
+ if (event.target === event.currentTarget) closeMenu();
1412
+ },
1413
+ onClose: () => menuButtonRef.current?.focus(),
1414
+ children: /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "vinci-dashboard__drawer-panel", children: [
1415
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "vinci-dashboard__drawer-header", children: [
1416
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "vinci-dashboard__brand", children: brand }),
1417
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("button", { type: "button", className: "vinci-dashboard__icon-button", "aria-label": labels.closeMenu, onClick: closeMenu, children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Icon, { name: "close" }) })
1418
+ ] }),
1419
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1420
+ Navigation,
1421
+ {
1422
+ groups: navigationGroups,
1423
+ activePath,
1424
+ idPrefix: `${id}-drawer`,
1425
+ label: labels.navigation,
1426
+ renderLink,
1427
+ onNavigate: closeMenu
1428
+ }
1429
+ )
1430
+ ] })
1431
+ }
1432
+ )
1433
+ ] });
1434
+ }
911
1435
  // Annotate the CommonJS export names for ESM import in node:
912
1436
  0 && (module.exports = {
913
1437
  AnnouncementBar,
@@ -915,6 +1439,7 @@ function PinInput({
915
1439
  Checkbox,
916
1440
  ChoiceChip,
917
1441
  ConversationPanel,
1442
+ DashboardShell,
918
1443
  FieldWithAction,
919
1444
  Footer,
920
1445
  Header,
@@ -922,6 +1447,8 @@ function PinInput({
922
1447
  LocationCard,
923
1448
  LocationCombobox,
924
1449
  MessageBubble,
1450
+ Modal,
1451
+ MultiSelect,
925
1452
  NorwoodOptionCard,
926
1453
  NumberInput,
927
1454
  PasswordInput,
@@ -931,6 +1458,7 @@ function PinInput({
931
1458
  ResultsCard,
932
1459
  StickyCTABar,
933
1460
  Switch,
1461
+ Table,
934
1462
  TestimonialCard,
935
1463
  TextInput,
936
1464
  TypingIndicator