eidos-ui 3.5.0 → 3.6.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.
@@ -1,10 +1,10 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true});
2
2
 
3
3
 
4
- var _chunkSDEKANNLcjs = require('../chunk-SDEKANNL.cjs');
4
+ var _chunkEAJGSXEWcjs = require('../chunk-EAJGSXEW.cjs');
5
5
  require('../chunk-DZUJEN5N.cjs');
6
6
 
7
7
 
8
8
 
9
- exports.Accordion = _chunkSDEKANNLcjs.Accordion; exports.AccordionItem = _chunkSDEKANNLcjs.AccordionItem;
9
+ exports.Accordion = _chunkEAJGSXEWcjs.Accordion; exports.AccordionItem = _chunkEAJGSXEWcjs.AccordionItem;
10
10
  //# sourceMappingURL=index.cjs.map
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  Accordion,
3
3
  AccordionItem
4
- } from "../chunk-CMOBXLNA.js";
4
+ } from "../chunk-LNDFVC2Q.js";
5
5
  import "../chunk-5WRI5ZAA.js";
6
6
  export {
7
7
  Accordion,
@@ -0,0 +1,96 @@
1
+ import {
2
+ Toolbar
3
+ } from "./chunk-SFFKQQ5Z.js";
4
+ import {
5
+ Header
6
+ } from "./chunk-OOCCSMWM.js";
7
+ import {
8
+ Navigation
9
+ } from "./chunk-HEX4IMLO.js";
10
+ import {
11
+ Footer
12
+ } from "./chunk-W2F6WDUX.js";
13
+
14
+ // src/components/PageLayout/useScrollRestoration.ts
15
+ import { useCallback, useEffect, useLayoutEffect, useRef } from "react";
16
+ var useScrollRestoration = (key, forwardTo) => {
17
+ const node = useRef(null);
18
+ const offsets = useRef(/* @__PURE__ */ new Map());
19
+ const appliedKey = useRef(key);
20
+ const forwarded = useRef(forwardTo);
21
+ useEffect(() => {
22
+ forwarded.current = forwardTo;
23
+ }, [forwardTo]);
24
+ const setNode = useCallback((element) => {
25
+ node.current = element;
26
+ const ref = forwarded.current;
27
+ if (typeof ref === "function") ref(element);
28
+ else if (ref) ref.current = element;
29
+ }, []);
30
+ useEffect(() => {
31
+ const element = node.current;
32
+ if (!element || key === void 0) return;
33
+ const record = () => offsets.current.set(key, element.scrollTop);
34
+ element.addEventListener("scroll", record, { passive: true });
35
+ return () => element.removeEventListener("scroll", record);
36
+ }, [key]);
37
+ useLayoutEffect(() => {
38
+ const element = node.current;
39
+ if (!element || key === void 0 || appliedKey.current === key) return;
40
+ appliedKey.current = key;
41
+ element.scrollTop = offsets.current.get(key) ?? 0;
42
+ }, [key]);
43
+ return setNode;
44
+ };
45
+
46
+ // src/components/PageLayout/PageLayout.component.tsx
47
+ import { jsx, jsxs } from "react/jsx-runtime";
48
+ var defaultFooter = { copyright: `\xA9 ${(/* @__PURE__ */ new Date()).getFullYear()} Eidos UI` };
49
+ var PageLayout = ({
50
+ toolbar,
51
+ navigation,
52
+ header,
53
+ footer,
54
+ children,
55
+ className = "",
56
+ // Destructured so they cannot fall into `...rest` and land on the root
57
+ // element, which is not the scroll container.
58
+ contentRef,
59
+ scrollRestorationKey,
60
+ ...rest
61
+ }) => {
62
+ const classes = ["eidos-pagelayout", className].filter(Boolean).join(" ");
63
+ const setContentRef = useScrollRestoration(scrollRestorationKey, contentRef);
64
+ return /* @__PURE__ */ jsxs("div", { className: classes, ...rest, children: [
65
+ /* @__PURE__ */ jsx(
66
+ Toolbar,
67
+ {
68
+ ...toolbar,
69
+ className: ["eidos-pagelayout__toolbar", toolbar?.className].filter(Boolean).join(" ")
70
+ }
71
+ ),
72
+ /* @__PURE__ */ jsx("div", { className: "eidos-pagelayout__navigation", children: navigation && /* @__PURE__ */ jsx(Navigation, { ...navigation }) }),
73
+ /* @__PURE__ */ jsxs("main", { ref: setContentRef, tabIndex: 0, className: "eidos-pagelayout__content", children: [
74
+ header && /* @__PURE__ */ jsx(
75
+ Header,
76
+ {
77
+ ...header,
78
+ className: ["eidos-pagelayout__header", header.className].filter(Boolean).join(" ")
79
+ }
80
+ ),
81
+ /* @__PURE__ */ jsx("div", { className: "eidos-pagelayout__body", children }),
82
+ /* @__PURE__ */ jsx(
83
+ Footer,
84
+ {
85
+ ...footer ?? defaultFooter,
86
+ className: ["eidos-pagelayout__footer", footer?.className].filter(Boolean).join(" ")
87
+ }
88
+ )
89
+ ] })
90
+ ] });
91
+ };
92
+
93
+ export {
94
+ PageLayout
95
+ };
96
+ //# sourceMappingURL=chunk-3LRT7ZIB.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/PageLayout/useScrollRestoration.ts","../src/components/PageLayout/PageLayout.component.tsx"],"sourcesContent":["import { useCallback, useEffect, useLayoutEffect, useRef } from 'react';\n\n/**\n * Remembers the content region's scroll offset per key, and applies the\n * stored offset whenever the key changes.\n *\n * The key is supplied by the consumer rather than derived here, because only\n * they know what counts as a navigation - this library has no router opinion.\n * That also decides the semantics, exactly as React Router's `getKey` does:\n * a per-path key gives \"this path always reopens where you left it\", while a\n * per-history-entry key gives the browser's own behaviour, where Back\n * restores but a fresh link to the same URL starts at the top.\n *\n * Returns a ref callback for the scroll container, which also forwards the\n * node to the caller's own `contentRef`.\n */\nexport const useScrollRestoration = (\n key: string | undefined,\n forwardTo: React.Ref<HTMLElement> | undefined,\n): ((node: HTMLElement | null) => void) => {\n const node = useRef<HTMLElement | null>(null);\n const offsets = useRef(new Map<string, number>());\n const appliedKey = useRef(key);\n\n // Held in a ref so the ref callback below can keep a stable identity. A\n // callback ref that changes identity is invoked with `null` and then the\n // node again on every render that changes it, which would detach and\n // reattach the caller's ref for no reason.\n const forwarded = useRef(forwardTo);\n useEffect(() => {\n forwarded.current = forwardTo;\n }, [forwardTo]);\n\n const setNode = useCallback((element: HTMLElement | null) => {\n node.current = element;\n const ref = forwarded.current;\n if (typeof ref === 'function') ref(element);\n else if (ref) (ref as React.RefObject<HTMLElement | null>).current = element;\n }, []);\n\n // Recorded as it happens rather than when the key changes: by the time a\n // navigation commits, the new page's content is already in the DOM and the\n // browser has clamped `scrollTop` to the new, possibly shorter, height. The\n // outgoing offset is no longer readable at that point.\n useEffect(() => {\n const element = node.current;\n if (!element || key === undefined) return;\n\n const record = () => offsets.current.set(key, element.scrollTop);\n element.addEventListener('scroll', record, { passive: true });\n return () => element.removeEventListener('scroll', record);\n }, [key]);\n\n // Layout effect so the offset is applied before paint - in a passive effect\n // the new page is visible at the previous page's offset for a frame first.\n // It does not run on the server, which is correct here: there is no scroll\n // position to restore in static markup, and the client applies it on mount.\n useLayoutEffect(() => {\n const element = node.current;\n if (!element || key === undefined || appliedKey.current === key) return;\n\n appliedKey.current = key;\n element.scrollTop = offsets.current.get(key) ?? 0;\n }, [key]);\n\n return setNode;\n};\n","import React from 'react';\nimport type { PageLayoutProps } from './PageLayout.types';\nimport './PageLayout.scss';\nimport { Toolbar } from '../Toolbar';\nimport { Header } from '../Header';\nimport { Footer } from '../Footer';\nimport type { FooterProps } from '../Footer';\nimport { Navigation } from '../Navigation';\nimport { useScrollRestoration } from './useScrollRestoration';\n\nconst defaultFooter: FooterProps = { copyright: `© ${new Date().getFullYear()} Eidos UI` };\n\nexport const PageLayout: React.FC<PageLayoutProps> = ({\n toolbar,\n navigation,\n header,\n footer,\n children,\n className = '',\n // Destructured so they cannot fall into `...rest` and land on the root\n // element, which is not the scroll container.\n contentRef,\n scrollRestorationKey,\n ...rest\n}) => {\n const classes = ['eidos-pagelayout', className].filter(Boolean).join(' ');\n const setContentRef = useScrollRestoration(scrollRestorationKey, contentRef);\n\n return (\n <div className={classes} {...rest}>\n {/* Spread rather than forwarded field by field, so a prop added to\n `Toolbar` reaches `PageLayout` consumers without a second edit\n here - the previous per-field list silently dropped anything it\n didn't know about. `className` is merged last so the layout's own\n grid-area class can't be overwritten by the caller's. */}\n <Toolbar\n {...toolbar}\n className={['eidos-pagelayout__toolbar', toolbar?.className].filter(Boolean).join(' ')}\n />\n <div className=\"eidos-pagelayout__navigation\">\n {navigation && <Navigation {...navigation} />}\n </div>\n {/* A scrollable region containing nothing focusable cannot be scrolled\n by keyboard at all, and a page body is often pure content - a\n report, an article. `tabIndex={0}` makes the region itself focusable\n so arrow keys and Page Up/Down reach it (SC 2.1.1), matching\n `VirtualList` and `Chat`. */}\n <main ref={setContentRef} tabIndex={0} className=\"eidos-pagelayout__content\">\n {header && (\n <Header\n {...header}\n className={['eidos-pagelayout__header', header.className].filter(Boolean).join(' ')}\n />\n )}\n <div className=\"eidos-pagelayout__body\">{children}</div>\n <Footer\n {...(footer ?? defaultFooter)}\n className={['eidos-pagelayout__footer', footer?.className].filter(Boolean).join(' ')}\n />\n </main>\n </div>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;AAAA,SAAS,aAAa,WAAW,iBAAiB,cAAc;AAgBzD,IAAM,uBAAuB,CAClC,KACA,cACyC;AACzC,QAAM,OAAO,OAA2B,IAAI;AAC5C,QAAM,UAAU,OAAO,oBAAI,IAAoB,CAAC;AAChD,QAAM,aAAa,OAAO,GAAG;AAM7B,QAAM,YAAY,OAAO,SAAS;AAClC,YAAU,MAAM;AACd,cAAU,UAAU;AAAA,EACtB,GAAG,CAAC,SAAS,CAAC;AAEd,QAAM,UAAU,YAAY,CAAC,YAAgC;AAC3D,SAAK,UAAU;AACf,UAAM,MAAM,UAAU;AACtB,QAAI,OAAO,QAAQ,WAAY,KAAI,OAAO;AAAA,aACjC,IAAK,CAAC,IAA4C,UAAU;AAAA,EACvE,GAAG,CAAC,CAAC;AAML,YAAU,MAAM;AACd,UAAM,UAAU,KAAK;AACrB,QAAI,CAAC,WAAW,QAAQ,OAAW;AAEnC,UAAM,SAAS,MAAM,QAAQ,QAAQ,IAAI,KAAK,QAAQ,SAAS;AAC/D,YAAQ,iBAAiB,UAAU,QAAQ,EAAE,SAAS,KAAK,CAAC;AAC5D,WAAO,MAAM,QAAQ,oBAAoB,UAAU,MAAM;AAAA,EAC3D,GAAG,CAAC,GAAG,CAAC;AAMR,kBAAgB,MAAM;AACpB,UAAM,UAAU,KAAK;AACrB,QAAI,CAAC,WAAW,QAAQ,UAAa,WAAW,YAAY,IAAK;AAEjE,eAAW,UAAU;AACrB,YAAQ,YAAY,QAAQ,QAAQ,IAAI,GAAG,KAAK;AAAA,EAClD,GAAG,CAAC,GAAG,CAAC;AAER,SAAO;AACT;;;AC/BM,cAYA,YAZA;AAzBN,IAAM,gBAA6B,EAAE,WAAW,SAAK,oBAAI,KAAK,GAAE,YAAY,CAAC,YAAY;AAElF,IAAM,aAAwC,CAAC;AAAA,EACpD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,YAAY;AAAA;AAAA;AAAA,EAGZ;AAAA,EACA;AAAA,EACA,GAAG;AACL,MAAM;AACJ,QAAM,UAAU,CAAC,oBAAoB,SAAS,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG;AACxE,QAAM,gBAAgB,qBAAqB,sBAAsB,UAAU;AAE3E,SACE,qBAAC,SAAI,WAAW,SAAU,GAAG,MAM3B;AAAA;AAAA,MAAC;AAAA;AAAA,QACE,GAAG;AAAA,QACJ,WAAW,CAAC,6BAA6B,SAAS,SAAS,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG;AAAA;AAAA,IACvF;AAAA,IACA,oBAAC,SAAI,WAAU,gCACZ,wBAAc,oBAAC,cAAY,GAAG,YAAY,GAC7C;AAAA,IAMA,qBAAC,UAAK,KAAK,eAAe,UAAU,GAAG,WAAU,6BAC9C;AAAA,gBACC;AAAA,QAAC;AAAA;AAAA,UACE,GAAG;AAAA,UACJ,WAAW,CAAC,4BAA4B,OAAO,SAAS,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG;AAAA;AAAA,MACpF;AAAA,MAEF,oBAAC,SAAI,WAAU,0BAA0B,UAAS;AAAA,MAClD;AAAA,QAAC;AAAA;AAAA,UACE,GAAI,UAAU;AAAA,UACf,WAAW,CAAC,4BAA4B,QAAQ,SAAS,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG;AAAA;AAAA,MACrF;AAAA,OACF;AAAA,KACF;AAEJ;","names":[]}
@@ -51,13 +51,22 @@ var AccordionItem = ({
51
51
  }) => {
52
52
  const { openValues, toggle, size, color } = useAccordion();
53
53
  const isOpen = openValues.includes(value);
54
- const contentRef = _react.useRef.call(void 0, null);
54
+ const innerRef = _react.useRef.call(void 0, null);
55
55
  const triggerId = _react.useId.call(void 0, );
56
56
  const panelId = _react.useId.call(void 0, );
57
57
  const [maxHeight, setMaxHeight] = _react.useState.call(void 0, 0);
58
58
  _react.useEffect.call(void 0, () => {
59
- if (!contentRef.current) return;
60
- setMaxHeight(isOpen ? contentRef.current.scrollHeight : 0);
59
+ if (!isOpen) {
60
+ setMaxHeight(0);
61
+ return;
62
+ }
63
+ const node = innerRef.current;
64
+ if (!node) return;
65
+ const update = () => setMaxHeight(node.scrollHeight);
66
+ update();
67
+ const observer = new ResizeObserver(update);
68
+ observer.observe(node);
69
+ return () => observer.disconnect();
61
70
  }, [isOpen]);
62
71
  const itemClasses = [
63
72
  "eidos-accordion-item",
@@ -95,10 +104,9 @@ var AccordionItem = ({
95
104
  id: panelId,
96
105
  role: "region",
97
106
  "aria-labelledby": triggerId,
98
- ref: contentRef,
99
107
  className: "eidos-accordion-content-wrapper",
100
108
  style: { maxHeight: `${maxHeight}px` },
101
- children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "eidos-accordion-content", children })
109
+ children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { ref: innerRef, className: "eidos-accordion-content", children })
102
110
  }
103
111
  )
104
112
  ] });
@@ -109,4 +117,4 @@ AccordionItem.displayName = "AccordionItem";
109
117
 
110
118
 
111
119
  exports.Accordion = Accordion; exports.AccordionItem = AccordionItem;
112
- //# sourceMappingURL=chunk-SDEKANNL.cjs.map
120
+ //# sourceMappingURL=chunk-EAJGSXEW.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/home/runner/work/eidos-ui/eidos-ui/dist/chunk-EAJGSXEW.cjs","../src/components/Accordion/Accordion.component.tsx"],"names":[],"mappings":"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","file":"/home/runner/work/eidos-ui/eidos-ui/dist/chunk-EAJGSXEW.cjs","sourcesContent":[null,"import React, { useState, useRef, useContext, useId, useEffect, useCallback } from 'react';\nimport { ChevronDown } from 'lucide-react';\nimport type { AccordionProps, AccordionItemProps, AccordionContextValue } from './Accordion.types';\n\n// ============================================================================\n// HELPERS\n// ============================================================================\n\n/** Normalise a controlled/default value (string | string[] | undefined) to string[]. */\nconst toArray = (v: string | string[] | undefined): string[] =>\n v === undefined ? [] : Array.isArray(v) ? v : [v];\n\n// ============================================================================\n// CONTEXT\n// ============================================================================\n\nconst AccordionContext = React.createContext<AccordionContextValue | null>(null);\n\nconst useAccordion = (): AccordionContextValue => {\n const ctx = useContext(AccordionContext);\n if (!ctx) throw new Error('AccordionItem must be used inside <Accordion>');\n return ctx;\n};\n\n// ============================================================================\n// ACCORDION - root container; owns open state; provides context\n// ============================================================================\n\nexport const Accordion: React.FC<AccordionProps> = ({\n multiple = false,\n value,\n defaultValue,\n onChange,\n variant = 'default',\n size = 'md',\n color = 'primary',\n className = '',\n children,\n}) => {\n const isControlled = value !== undefined;\n\n const [localOpen, setLocalOpen] = useState<string[]>(() => toArray(defaultValue));\n const openValues = isControlled ? toArray(value) : localOpen;\n\n const toggle = useCallback(\n (itemValue: string) => {\n const isOpen = openValues.includes(itemValue);\n let next: string[];\n\n if (multiple) {\n next = isOpen ? openValues.filter((v) => v !== itemValue) : [...openValues, itemValue];\n } else {\n next = isOpen ? [] : [itemValue];\n }\n\n if (!isControlled) setLocalOpen(next);\n onChange?.(multiple ? next : (next[0] ?? ''));\n },\n [openValues, multiple, isControlled, onChange],\n );\n\n const rootClasses = ['eidos-accordion', `eidos-accordion--${variant}`, className]\n .filter(Boolean)\n .join(' ');\n\n return (\n <AccordionContext.Provider value={{ openValues, toggle, variant, size, color }}>\n <div className={rootClasses}>{children}</div>\n </AccordionContext.Provider>\n );\n};\n\nAccordion.displayName = 'Accordion';\n\n// ============================================================================\n// ACCORDION ITEM - individual item; reads context; renders trigger + animated panel\n// ============================================================================\n\nexport const AccordionItem: React.FC<AccordionItemProps> = ({\n value,\n label,\n icon,\n disabled = false,\n className = '',\n children,\n}) => {\n const { openValues, toggle, size, color } = useAccordion();\n const isOpen = openValues.includes(value);\n // The unclipped content itself, not the wrapper around it. The wrapper's own\n // height is what `maxHeight` below sets, so observing it would feed the\n // observer its own output; the inner node is always laid out at its natural\n // height regardless of the wrapper's clipping, so its size genuinely changes\n // when its content does.\n const innerRef = useRef<HTMLDivElement>(null);\n const triggerId = useId();\n const panelId = useId();\n\n // Drives the max-height CSS transition.\n // On open: set to the content's height so the wrapper animates to it.\n // On close: set to 0 so the wrapper animates shut.\n // The CSS transition on .eidos-accordion-content-wrapper handles the animation.\n const [maxHeight, setMaxHeight] = useState<number>(0);\n\n // `max-height` is a fixed px value, so it goes stale the moment the panel's\n // content reflows after opening - a narrower viewport rewrapping prose, a\n // font finishing loading, an image arriving, a nested collapsible opening.\n // The wrapper is `overflow: hidden` with no scrollbar, so a stale value\n // silently clips the tail of the panel with nothing on screen to suggest\n // content is missing. Tracking the content for as long as the panel stays\n // open is therefore part of the technique, not a refinement of it - which is\n // what `DataGrid`'s row expansion (`useExpandAnimation`) already does.\n useEffect(() => {\n if (!isOpen) {\n setMaxHeight(0);\n return;\n }\n\n const node = innerRef.current;\n if (!node) return;\n\n const update = () => setMaxHeight(node.scrollHeight);\n update();\n\n const observer = new ResizeObserver(update);\n observer.observe(node);\n return () => observer.disconnect();\n }, [isOpen]);\n\n const itemClasses = [\n 'eidos-accordion-item',\n isOpen && 'eidos-accordion-item--open',\n disabled && 'eidos-accordion-item--disabled',\n className,\n ]\n .filter(Boolean)\n .join(' ');\n\n const triggerClasses = [\n 'eidos-accordion-trigger',\n `eidos-accordion-trigger--${size}`,\n isOpen && 'eidos-accordion-trigger--open',\n isOpen && `eidos-accordion-trigger--${color}`,\n ]\n .filter(Boolean)\n .join(' ');\n\n return (\n <div className={itemClasses}>\n <button\n id={triggerId}\n type=\"button\"\n aria-expanded={isOpen}\n aria-controls={panelId}\n disabled={disabled}\n onClick={() => !disabled && toggle(value)}\n className={triggerClasses}\n >\n {icon && <span className=\"eidos-accordion-trigger-icon\">{icon}</span>}\n <span className=\"eidos-accordion-trigger-label\">{label}</span>\n <ChevronDown className=\"eidos-accordion-chevron\" aria-hidden=\"true\" />\n </button>\n\n <div\n id={panelId}\n role=\"region\"\n aria-labelledby={triggerId}\n className=\"eidos-accordion-content-wrapper\"\n style={{ maxHeight: `${maxHeight}px` }}\n >\n <div ref={innerRef} className=\"eidos-accordion-content\">\n {children}\n </div>\n </div>\n </div>\n );\n};\n\nAccordionItem.displayName = 'AccordionItem';\n"]}
@@ -51,13 +51,22 @@ var AccordionItem = ({
51
51
  }) => {
52
52
  const { openValues, toggle, size, color } = useAccordion();
53
53
  const isOpen = openValues.includes(value);
54
- const contentRef = useRef(null);
54
+ const innerRef = useRef(null);
55
55
  const triggerId = useId();
56
56
  const panelId = useId();
57
57
  const [maxHeight, setMaxHeight] = useState(0);
58
58
  useEffect(() => {
59
- if (!contentRef.current) return;
60
- setMaxHeight(isOpen ? contentRef.current.scrollHeight : 0);
59
+ if (!isOpen) {
60
+ setMaxHeight(0);
61
+ return;
62
+ }
63
+ const node = innerRef.current;
64
+ if (!node) return;
65
+ const update = () => setMaxHeight(node.scrollHeight);
66
+ update();
67
+ const observer = new ResizeObserver(update);
68
+ observer.observe(node);
69
+ return () => observer.disconnect();
61
70
  }, [isOpen]);
62
71
  const itemClasses = [
63
72
  "eidos-accordion-item",
@@ -95,10 +104,9 @@ var AccordionItem = ({
95
104
  id: panelId,
96
105
  role: "region",
97
106
  "aria-labelledby": triggerId,
98
- ref: contentRef,
99
107
  className: "eidos-accordion-content-wrapper",
100
108
  style: { maxHeight: `${maxHeight}px` },
101
- children: /* @__PURE__ */ jsx("div", { className: "eidos-accordion-content", children })
109
+ children: /* @__PURE__ */ jsx("div", { ref: innerRef, className: "eidos-accordion-content", children })
102
110
  }
103
111
  )
104
112
  ] });
@@ -109,4 +117,4 @@ export {
109
117
  Accordion,
110
118
  AccordionItem
111
119
  };
112
- //# sourceMappingURL=chunk-CMOBXLNA.js.map
120
+ //# sourceMappingURL=chunk-LNDFVC2Q.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/Accordion/Accordion.component.tsx"],"sourcesContent":["import React, { useState, useRef, useContext, useId, useEffect, useCallback } from 'react';\nimport { ChevronDown } from 'lucide-react';\nimport type { AccordionProps, AccordionItemProps, AccordionContextValue } from './Accordion.types';\n\n// ============================================================================\n// HELPERS\n// ============================================================================\n\n/** Normalise a controlled/default value (string | string[] | undefined) to string[]. */\nconst toArray = (v: string | string[] | undefined): string[] =>\n v === undefined ? [] : Array.isArray(v) ? v : [v];\n\n// ============================================================================\n// CONTEXT\n// ============================================================================\n\nconst AccordionContext = React.createContext<AccordionContextValue | null>(null);\n\nconst useAccordion = (): AccordionContextValue => {\n const ctx = useContext(AccordionContext);\n if (!ctx) throw new Error('AccordionItem must be used inside <Accordion>');\n return ctx;\n};\n\n// ============================================================================\n// ACCORDION - root container; owns open state; provides context\n// ============================================================================\n\nexport const Accordion: React.FC<AccordionProps> = ({\n multiple = false,\n value,\n defaultValue,\n onChange,\n variant = 'default',\n size = 'md',\n color = 'primary',\n className = '',\n children,\n}) => {\n const isControlled = value !== undefined;\n\n const [localOpen, setLocalOpen] = useState<string[]>(() => toArray(defaultValue));\n const openValues = isControlled ? toArray(value) : localOpen;\n\n const toggle = useCallback(\n (itemValue: string) => {\n const isOpen = openValues.includes(itemValue);\n let next: string[];\n\n if (multiple) {\n next = isOpen ? openValues.filter((v) => v !== itemValue) : [...openValues, itemValue];\n } else {\n next = isOpen ? [] : [itemValue];\n }\n\n if (!isControlled) setLocalOpen(next);\n onChange?.(multiple ? next : (next[0] ?? ''));\n },\n [openValues, multiple, isControlled, onChange],\n );\n\n const rootClasses = ['eidos-accordion', `eidos-accordion--${variant}`, className]\n .filter(Boolean)\n .join(' ');\n\n return (\n <AccordionContext.Provider value={{ openValues, toggle, variant, size, color }}>\n <div className={rootClasses}>{children}</div>\n </AccordionContext.Provider>\n );\n};\n\nAccordion.displayName = 'Accordion';\n\n// ============================================================================\n// ACCORDION ITEM - individual item; reads context; renders trigger + animated panel\n// ============================================================================\n\nexport const AccordionItem: React.FC<AccordionItemProps> = ({\n value,\n label,\n icon,\n disabled = false,\n className = '',\n children,\n}) => {\n const { openValues, toggle, size, color } = useAccordion();\n const isOpen = openValues.includes(value);\n // The unclipped content itself, not the wrapper around it. The wrapper's own\n // height is what `maxHeight` below sets, so observing it would feed the\n // observer its own output; the inner node is always laid out at its natural\n // height regardless of the wrapper's clipping, so its size genuinely changes\n // when its content does.\n const innerRef = useRef<HTMLDivElement>(null);\n const triggerId = useId();\n const panelId = useId();\n\n // Drives the max-height CSS transition.\n // On open: set to the content's height so the wrapper animates to it.\n // On close: set to 0 so the wrapper animates shut.\n // The CSS transition on .eidos-accordion-content-wrapper handles the animation.\n const [maxHeight, setMaxHeight] = useState<number>(0);\n\n // `max-height` is a fixed px value, so it goes stale the moment the panel's\n // content reflows after opening - a narrower viewport rewrapping prose, a\n // font finishing loading, an image arriving, a nested collapsible opening.\n // The wrapper is `overflow: hidden` with no scrollbar, so a stale value\n // silently clips the tail of the panel with nothing on screen to suggest\n // content is missing. Tracking the content for as long as the panel stays\n // open is therefore part of the technique, not a refinement of it - which is\n // what `DataGrid`'s row expansion (`useExpandAnimation`) already does.\n useEffect(() => {\n if (!isOpen) {\n setMaxHeight(0);\n return;\n }\n\n const node = innerRef.current;\n if (!node) return;\n\n const update = () => setMaxHeight(node.scrollHeight);\n update();\n\n const observer = new ResizeObserver(update);\n observer.observe(node);\n return () => observer.disconnect();\n }, [isOpen]);\n\n const itemClasses = [\n 'eidos-accordion-item',\n isOpen && 'eidos-accordion-item--open',\n disabled && 'eidos-accordion-item--disabled',\n className,\n ]\n .filter(Boolean)\n .join(' ');\n\n const triggerClasses = [\n 'eidos-accordion-trigger',\n `eidos-accordion-trigger--${size}`,\n isOpen && 'eidos-accordion-trigger--open',\n isOpen && `eidos-accordion-trigger--${color}`,\n ]\n .filter(Boolean)\n .join(' ');\n\n return (\n <div className={itemClasses}>\n <button\n id={triggerId}\n type=\"button\"\n aria-expanded={isOpen}\n aria-controls={panelId}\n disabled={disabled}\n onClick={() => !disabled && toggle(value)}\n className={triggerClasses}\n >\n {icon && <span className=\"eidos-accordion-trigger-icon\">{icon}</span>}\n <span className=\"eidos-accordion-trigger-label\">{label}</span>\n <ChevronDown className=\"eidos-accordion-chevron\" aria-hidden=\"true\" />\n </button>\n\n <div\n id={panelId}\n role=\"region\"\n aria-labelledby={triggerId}\n className=\"eidos-accordion-content-wrapper\"\n style={{ maxHeight: `${maxHeight}px` }}\n >\n <div ref={innerRef} className=\"eidos-accordion-content\">\n {children}\n </div>\n </div>\n </div>\n );\n};\n\nAccordionItem.displayName = 'AccordionItem';\n"],"mappings":"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","names":[]}
@@ -11,6 +11,38 @@ var _chunkV4BZWBZTcjs = require('./chunk-V4BZWBZT.cjs');
11
11
 
12
12
  var _chunkPTBBY4ZXcjs = require('./chunk-PTBBY4ZX.cjs');
13
13
 
14
+ // src/components/PageLayout/useScrollRestoration.ts
15
+ var _react = require('react');
16
+ var useScrollRestoration = (key, forwardTo) => {
17
+ const node = _react.useRef.call(void 0, null);
18
+ const offsets = _react.useRef.call(void 0, /* @__PURE__ */ new Map());
19
+ const appliedKey = _react.useRef.call(void 0, key);
20
+ const forwarded = _react.useRef.call(void 0, forwardTo);
21
+ _react.useEffect.call(void 0, () => {
22
+ forwarded.current = forwardTo;
23
+ }, [forwardTo]);
24
+ const setNode = _react.useCallback.call(void 0, (element) => {
25
+ node.current = element;
26
+ const ref = forwarded.current;
27
+ if (typeof ref === "function") ref(element);
28
+ else if (ref) ref.current = element;
29
+ }, []);
30
+ _react.useEffect.call(void 0, () => {
31
+ const element = node.current;
32
+ if (!element || key === void 0) return;
33
+ const record = () => offsets.current.set(key, element.scrollTop);
34
+ element.addEventListener("scroll", record, { passive: true });
35
+ return () => element.removeEventListener("scroll", record);
36
+ }, [key]);
37
+ _react.useLayoutEffect.call(void 0, () => {
38
+ const element = node.current;
39
+ if (!element || key === void 0 || appliedKey.current === key) return;
40
+ appliedKey.current = key;
41
+ element.scrollTop = _nullishCoalesce(offsets.current.get(key), () => ( 0));
42
+ }, [key]);
43
+ return setNode;
44
+ };
45
+
14
46
  // src/components/PageLayout/PageLayout.component.tsx
15
47
  var _jsxruntime = require('react/jsx-runtime');
16
48
  var defaultFooter = { copyright: `\xA9 ${(/* @__PURE__ */ new Date()).getFullYear()} Eidos UI` };
@@ -21,9 +53,14 @@ var PageLayout = ({
21
53
  footer,
22
54
  children,
23
55
  className = "",
56
+ // Destructured so they cannot fall into `...rest` and land on the root
57
+ // element, which is not the scroll container.
58
+ contentRef,
59
+ scrollRestorationKey,
24
60
  ...rest
25
61
  }) => {
26
62
  const classes = ["eidos-pagelayout", className].filter(Boolean).join(" ");
63
+ const setContentRef = useScrollRestoration(scrollRestorationKey, contentRef);
27
64
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: classes, ...rest, children: [
28
65
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
29
66
  _chunkJCKGAAPZcjs.Toolbar,
@@ -33,7 +70,7 @@ var PageLayout = ({
33
70
  }
34
71
  ),
35
72
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "eidos-pagelayout__navigation", children: navigation && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkV4BZWBZTcjs.Navigation, { ...navigation }) }),
36
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "main", { className: "eidos-pagelayout__content", children: [
73
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "main", { ref: setContentRef, tabIndex: 0, className: "eidos-pagelayout__content", children: [
37
74
  header && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
38
75
  _chunk3KBBJZHKcjs.Header,
39
76
  {
@@ -56,4 +93,4 @@ var PageLayout = ({
56
93
 
57
94
 
58
95
  exports.PageLayout = PageLayout;
59
- //# sourceMappingURL=chunk-HMEREPDB.cjs.map
96
+ //# sourceMappingURL=chunk-TNBML2MJ.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/home/runner/work/eidos-ui/eidos-ui/dist/chunk-TNBML2MJ.cjs","../src/components/PageLayout/useScrollRestoration.ts","../src/components/PageLayout/PageLayout.component.tsx"],"names":[],"mappings":"AAAA;AACE;AACF,wDAA6B;AAC7B;AACE;AACF,wDAA6B;AAC7B;AACE;AACF,wDAA6B;AAC7B;AACE;AACF,wDAA6B;AAC7B;AACA;ACbA,8BAAgE;AAgBzD,IAAM,qBAAA,EAAuB,CAClC,GAAA,EACA,SAAA,EAAA,GACyC;AACzC,EAAA,MAAM,KAAA,EAAO,2BAAA,IAA+B,CAAA;AAC5C,EAAA,MAAM,QAAA,EAAU,2BAAA,gBAAO,IAAI,GAAA,CAAoB,CAAC,CAAA;AAChD,EAAA,MAAM,WAAA,EAAa,2BAAA,GAAU,CAAA;AAM7B,EAAA,MAAM,UAAA,EAAY,2BAAA,SAAgB,CAAA;AAClC,EAAA,8BAAA,CAAU,EAAA,GAAM;AACd,IAAA,SAAA,CAAU,QAAA,EAAU,SAAA;AAAA,EACtB,CAAA,EAAG,CAAC,SAAS,CAAC,CAAA;AAEd,EAAA,MAAM,QAAA,EAAU,gCAAA,CAAa,OAAA,EAAA,GAAgC;AAC3D,IAAA,IAAA,CAAK,QAAA,EAAU,OAAA;AACf,IAAA,MAAM,IAAA,EAAM,SAAA,CAAU,OAAA;AACtB,IAAA,GAAA,CAAI,OAAO,IAAA,IAAQ,UAAA,EAAY,GAAA,CAAI,OAAO,CAAA;AAAA,IAAA,KAAA,GAAA,CACjC,GAAA,EAAM,GAAA,CAA4C,QAAA,EAAU,OAAA;AAAA,EACvE,CAAA,EAAG,CAAC,CAAC,CAAA;AAML,EAAA,8BAAA,CAAU,EAAA,GAAM;AACd,IAAA,MAAM,QAAA,EAAU,IAAA,CAAK,OAAA;AACrB,IAAA,GAAA,CAAI,CAAC,QAAA,GAAW,IAAA,IAAQ,KAAA,CAAA,EAAW,MAAA;AAEnC,IAAA,MAAM,OAAA,EAAS,CAAA,EAAA,GAAM,OAAA,CAAQ,OAAA,CAAQ,GAAA,CAAI,GAAA,EAAK,OAAA,CAAQ,SAAS,CAAA;AAC/D,IAAA,OAAA,CAAQ,gBAAA,CAAiB,QAAA,EAAU,MAAA,EAAQ,EAAE,OAAA,EAAS,KAAK,CAAC,CAAA;AAC5D,IAAA,OAAO,CAAA,EAAA,GAAM,OAAA,CAAQ,mBAAA,CAAoB,QAAA,EAAU,MAAM,CAAA;AAAA,EAC3D,CAAA,EAAG,CAAC,GAAG,CAAC,CAAA;AAMR,EAAA,oCAAA,CAAgB,EAAA,GAAM;AACpB,IAAA,MAAM,QAAA,EAAU,IAAA,CAAK,OAAA;AACrB,IAAA,GAAA,CAAI,CAAC,QAAA,GAAW,IAAA,IAAQ,KAAA,EAAA,GAAa,UAAA,CAAW,QAAA,IAAY,GAAA,EAAK,MAAA;AAEjE,IAAA,UAAA,CAAW,QAAA,EAAU,GAAA;AACrB,IAAA,OAAA,CAAQ,UAAA,mBAAY,OAAA,CAAQ,OAAA,CAAQ,GAAA,CAAI,GAAG,CAAA,UAAK,GAAA;AAAA,EAClD,CAAA,EAAG,CAAC,GAAG,CAAC,CAAA;AAER,EAAA,OAAO,OAAA;AACT,CAAA;ADtBA;AACA;AEVM,+CAAA;AAzBN,IAAM,cAAA,EAA6B,EAAE,SAAA,EAAW,CAAA,KAAA,EAAA,iBAAK,IAAI,IAAA,CAAK,CAAA,CAAA,CAAE,WAAA,CAAY,CAAC,CAAA,SAAA,EAAY,CAAA;AAElF,IAAM,WAAA,EAAwC,CAAC;AAAA,EACpD,OAAA;AAAA,EACA,UAAA;AAAA,EACA,MAAA;AAAA,EACA,MAAA;AAAA,EACA,QAAA;AAAA,EACA,UAAA,EAAY,EAAA;AAAA;AAAA;AAAA,EAGZ,UAAA;AAAA,EACA,oBAAA;AAAA,EACA,GAAG;AACL,CAAA,EAAA,GAAM;AACJ,EAAA,MAAM,QAAA,EAAU,CAAC,kBAAA,EAAoB,SAAS,CAAA,CAAE,MAAA,CAAO,OAAO,CAAA,CAAE,IAAA,CAAK,GAAG,CAAA;AACxE,EAAA,MAAM,cAAA,EAAgB,oBAAA,CAAqB,oBAAA,EAAsB,UAAU,CAAA;AAE3E,EAAA,uBACE,8BAAA,KAAC,EAAA,EAAI,SAAA,EAAW,OAAA,EAAU,GAAG,IAAA,EAM3B,QAAA,EAAA;AAAA,oBAAA,6BAAA;AAAA,MAAC,yBAAA;AAAA,MAAA;AAAA,QACE,GAAG,OAAA;AAAA,QACJ,SAAA,EAAW,CAAC,2BAAA,kBAA6B,OAAA,2BAAS,WAAS,CAAA,CAAE,MAAA,CAAO,OAAO,CAAA,CAAE,IAAA,CAAK,GAAG;AAAA,MAAA;AAAA,IACvF,CAAA;AAAA,oBACA,6BAAA,KAAC,EAAA,EAAI,SAAA,EAAU,8BAAA,EACZ,QAAA,EAAA,WAAA,mBAAc,6BAAA,4BAAC,EAAA,EAAY,GAAG,WAAA,CAAY,EAAA,CAC7C,CAAA;AAAA,oBAMA,8BAAA,MAAC,EAAA,EAAK,GAAA,EAAK,aAAA,EAAe,QAAA,EAAU,CAAA,EAAG,SAAA,EAAU,2BAAA,EAC9C,QAAA,EAAA;AAAA,MAAA,OAAA,mBACC,6BAAA;AAAA,QAAC,wBAAA;AAAA,QAAA;AAAA,UACE,GAAG,MAAA;AAAA,UACJ,SAAA,EAAW,CAAC,0BAAA,EAA4B,MAAA,CAAO,SAAS,CAAA,CAAE,MAAA,CAAO,OAAO,CAAA,CAAE,IAAA,CAAK,GAAG;AAAA,QAAA;AAAA,MACpF,CAAA;AAAA,sBAEF,6BAAA,KAAC,EAAA,EAAI,SAAA,EAAU,wBAAA,EAA0B,SAAA,CAAS,CAAA;AAAA,sBAClD,6BAAA;AAAA,QAAC,wBAAA;AAAA,QAAA;AAAA,UACE,oBAAI,MAAA,UAAU,eAAA;AAAA,UACf,SAAA,EAAW,CAAC,0BAAA,kBAA4B,MAAA,6BAAQ,WAAS,CAAA,CAAE,MAAA,CAAO,OAAO,CAAA,CAAE,IAAA,CAAK,GAAG;AAAA,QAAA;AAAA,MACrF;AAAA,IAAA,EAAA,CACF;AAAA,EAAA,EAAA,CACF,CAAA;AAEJ,CAAA;AF6BA;AACA;AACE;AACF,gCAAC","file":"/home/runner/work/eidos-ui/eidos-ui/dist/chunk-TNBML2MJ.cjs","sourcesContent":[null,"import { useCallback, useEffect, useLayoutEffect, useRef } from 'react';\n\n/**\n * Remembers the content region's scroll offset per key, and applies the\n * stored offset whenever the key changes.\n *\n * The key is supplied by the consumer rather than derived here, because only\n * they know what counts as a navigation - this library has no router opinion.\n * That also decides the semantics, exactly as React Router's `getKey` does:\n * a per-path key gives \"this path always reopens where you left it\", while a\n * per-history-entry key gives the browser's own behaviour, where Back\n * restores but a fresh link to the same URL starts at the top.\n *\n * Returns a ref callback for the scroll container, which also forwards the\n * node to the caller's own `contentRef`.\n */\nexport const useScrollRestoration = (\n key: string | undefined,\n forwardTo: React.Ref<HTMLElement> | undefined,\n): ((node: HTMLElement | null) => void) => {\n const node = useRef<HTMLElement | null>(null);\n const offsets = useRef(new Map<string, number>());\n const appliedKey = useRef(key);\n\n // Held in a ref so the ref callback below can keep a stable identity. A\n // callback ref that changes identity is invoked with `null` and then the\n // node again on every render that changes it, which would detach and\n // reattach the caller's ref for no reason.\n const forwarded = useRef(forwardTo);\n useEffect(() => {\n forwarded.current = forwardTo;\n }, [forwardTo]);\n\n const setNode = useCallback((element: HTMLElement | null) => {\n node.current = element;\n const ref = forwarded.current;\n if (typeof ref === 'function') ref(element);\n else if (ref) (ref as React.RefObject<HTMLElement | null>).current = element;\n }, []);\n\n // Recorded as it happens rather than when the key changes: by the time a\n // navigation commits, the new page's content is already in the DOM and the\n // browser has clamped `scrollTop` to the new, possibly shorter, height. The\n // outgoing offset is no longer readable at that point.\n useEffect(() => {\n const element = node.current;\n if (!element || key === undefined) return;\n\n const record = () => offsets.current.set(key, element.scrollTop);\n element.addEventListener('scroll', record, { passive: true });\n return () => element.removeEventListener('scroll', record);\n }, [key]);\n\n // Layout effect so the offset is applied before paint - in a passive effect\n // the new page is visible at the previous page's offset for a frame first.\n // It does not run on the server, which is correct here: there is no scroll\n // position to restore in static markup, and the client applies it on mount.\n useLayoutEffect(() => {\n const element = node.current;\n if (!element || key === undefined || appliedKey.current === key) return;\n\n appliedKey.current = key;\n element.scrollTop = offsets.current.get(key) ?? 0;\n }, [key]);\n\n return setNode;\n};\n","import React from 'react';\nimport type { PageLayoutProps } from './PageLayout.types';\nimport './PageLayout.scss';\nimport { Toolbar } from '../Toolbar';\nimport { Header } from '../Header';\nimport { Footer } from '../Footer';\nimport type { FooterProps } from '../Footer';\nimport { Navigation } from '../Navigation';\nimport { useScrollRestoration } from './useScrollRestoration';\n\nconst defaultFooter: FooterProps = { copyright: `© ${new Date().getFullYear()} Eidos UI` };\n\nexport const PageLayout: React.FC<PageLayoutProps> = ({\n toolbar,\n navigation,\n header,\n footer,\n children,\n className = '',\n // Destructured so they cannot fall into `...rest` and land on the root\n // element, which is not the scroll container.\n contentRef,\n scrollRestorationKey,\n ...rest\n}) => {\n const classes = ['eidos-pagelayout', className].filter(Boolean).join(' ');\n const setContentRef = useScrollRestoration(scrollRestorationKey, contentRef);\n\n return (\n <div className={classes} {...rest}>\n {/* Spread rather than forwarded field by field, so a prop added to\n `Toolbar` reaches `PageLayout` consumers without a second edit\n here - the previous per-field list silently dropped anything it\n didn't know about. `className` is merged last so the layout's own\n grid-area class can't be overwritten by the caller's. */}\n <Toolbar\n {...toolbar}\n className={['eidos-pagelayout__toolbar', toolbar?.className].filter(Boolean).join(' ')}\n />\n <div className=\"eidos-pagelayout__navigation\">\n {navigation && <Navigation {...navigation} />}\n </div>\n {/* A scrollable region containing nothing focusable cannot be scrolled\n by keyboard at all, and a page body is often pure content - a\n report, an article. `tabIndex={0}` makes the region itself focusable\n so arrow keys and Page Up/Down reach it (SC 2.1.1), matching\n `VirtualList` and `Chat`. */}\n <main ref={setContentRef} tabIndex={0} className=\"eidos-pagelayout__content\">\n {header && (\n <Header\n {...header}\n className={['eidos-pagelayout__header', header.className].filter(Boolean).join(' ')}\n />\n )}\n <div className=\"eidos-pagelayout__body\">{children}</div>\n <Footer\n {...(footer ?? defaultFooter)}\n className={['eidos-pagelayout__footer', footer?.className].filter(Boolean).join(' ')}\n />\n </main>\n </div>\n );\n};\n"]}
package/dist/index.cjs CHANGED
@@ -56,7 +56,7 @@ var _chunkIXZMKIV5cjs = require('./chunk-IXZMKIV5.cjs');
56
56
  var _chunkVHO6JPRRcjs = require('./chunk-VHO6JPRR.cjs');
57
57
 
58
58
 
59
- var _chunkHMEREPDBcjs = require('./chunk-HMEREPDB.cjs');
59
+ var _chunkTNBML2MJcjs = require('./chunk-TNBML2MJ.cjs');
60
60
 
61
61
 
62
62
  var _chunkJCKGAAPZcjs = require('./chunk-JCKGAAPZ.cjs');
@@ -184,7 +184,7 @@ var _chunkKVIL4EP4cjs = require('./chunk-KVIL4EP4.cjs');
184
184
 
185
185
 
186
186
 
187
- var _chunkSDEKANNLcjs = require('./chunk-SDEKANNL.cjs');
187
+ var _chunkEAJGSXEWcjs = require('./chunk-EAJGSXEW.cjs');
188
188
 
189
189
 
190
190
  var _chunk3RWZC6OWcjs = require('./chunk-3RWZC6OW.cjs');
@@ -301,5 +301,5 @@ require('./chunk-DZUJEN5N.cjs');
301
301
 
302
302
 
303
303
 
304
- exports.Accordion = _chunkSDEKANNLcjs.Accordion; exports.AccordionItem = _chunkSDEKANNLcjs.AccordionItem; exports.Alert = _chunk3RWZC6OWcjs.Alert; exports.Avatar = _chunkQ3GNKF74cjs.Avatar; exports.AvatarGroup = _chunkQ3GNKF74cjs.AvatarGroup; exports.Badge = _chunk3ETWFS2Ncjs.Badge; exports.Breadcrumb = _chunk5O73AALPcjs.Breadcrumb; exports.Button = _chunkDA375AI6cjs.Button; exports.ButtonGroup = _chunk2ROKOO22cjs.ButtonGroup; exports.Calendar = _chunk6JHVXWDVcjs.Calendar; exports.Card = _chunkV5RS3JZNcjs.Card; exports.Chat = _chunkX3BVQBXDcjs.Chat; exports.Checkbox = _chunkYJNQLH2Lcjs.Checkbox; exports.Chip = _chunkTTLFY76Jcjs.Chip; exports.ColorPicker = _chunkKBQD4JLKcjs.ColorPicker; exports.Combobox = _chunk4AO5RKV5cjs.Combobox; exports.CommandPalette = _chunk2T7ZMMH5cjs.CommandPalette; exports.CommentThread = _chunkX3BVQBXDcjs.CommentThread; exports.ContextMenu = _chunkHNGFRRVZcjs.ContextMenu; exports.DataGrid = _chunk66UQTF57cjs.DataGrid; exports.DatePicker = _chunk6JHVXWDVcjs.DatePicker; exports.Divider = _chunk366R4GKRcjs.Divider; exports.Drawer = _chunkDU5JKTHKcjs.Drawer; exports.Dropdown = _chunkKIZVY57Hcjs.Dropdown; exports.DropdownProvider = _chunkKIZVY57Hcjs.DropdownProvider; exports.EmptyState = _chunkKV3V2CPHcjs.EmptyState; exports.FONT_ACCEPT = _chunkRJVHR45Icjs.FONT_ACCEPT; exports.FileUpload = _chunkXRVRGRC3cjs.FileUpload; exports.Footer = _chunkPTBBY4ZXcjs.Footer; exports.Header = _chunk3KBBJZHKcjs.Header; exports.IconButton = _chunkDA375AI6cjs.IconButton; exports.IdentityHeader = _chunk3KBBJZHKcjs.IdentityHeader; exports.InlineEdit = _chunkI6M6CVI2cjs.InlineEdit; exports.Input = _chunk7VJCGKVPcjs.Input; exports.Kbd = _chunkKVIL4EP4cjs.Kbd; exports.Menu = _chunkJ76JHN7Jcjs.Menu; exports.MenuPanel = _chunkJ76JHN7Jcjs.MenuPanel; exports.MessageComposer = _chunkX3BVQBXDcjs.MessageComposer; exports.Modal = _chunk4JSTBA4Fcjs.Modal; exports.Navigation = _chunkV4BZWBZTcjs.Navigation; exports.NumberInput = _chunkTC5IC6OPcjs.NumberInput; exports.OTPInput = _chunkVHO6JPRRcjs.OTPInput; exports.PageLayout = _chunkHMEREPDBcjs.PageLayout; exports.Pagination = _chunkKREHT4AQcjs.Pagination; exports.Pill = _chunkEQ6BNPTLcjs.Pill; exports.Popover = _chunkVQGLVBPLcjs.Popover; exports.Progress = _chunkV2S6XWIQcjs.Progress; exports.Radio = _chunkKPMIIGHHcjs.Radio; exports.RadioGroup = _chunkKPMIIGHHcjs.RadioGroup; exports.SegmentedControl = _chunkJPYJQ4TFcjs.SegmentedControl; exports.Select = _chunkXZLSHEHEcjs.Select; exports.Skeleton = _chunkIGFR5TEScjs.Skeleton; exports.Slider = _chunkOZZHOEN5cjs.Slider; exports.SnackbarContainer = _chunkXERBOFYZcjs.SnackbarContainer; exports.SnackbarProvider = _chunkXERBOFYZcjs.SnackbarProvider; exports.Spinner = _chunkTROYIUDHcjs.Spinner; exports.SplitButton = _chunkLQXFXXMJcjs.SplitButton; exports.Stepper = _chunkHFZ5FKRGcjs.Stepper; exports.Switch = _chunkIXZMKIV5cjs.Switch; exports.Tab = _chunkNOQF7UQ5cjs.Tab; exports.TabPanel = _chunkNOQF7UQ5cjs.TabPanel; exports.Table = _chunk3ZRFOKDMcjs.Table; exports.TableFiltersDropdown = _chunkPMSENLOAcjs.TableFiltersDropdown; exports.Tabs = _chunkNOQF7UQ5cjs.Tabs; exports.TagInput = _chunkZMRINZIOcjs.TagInput; exports.Textarea = _chunk7BFODYNLcjs.Textarea; exports.ThemeEditor = _chunk36JFXFM2cjs.ThemeEditor; exports.ThemeProvider = _chunkRJVHR45Icjs.ThemeProvider; exports.TimeInput = _chunk6JHVXWDVcjs.TimeInput; exports.Timeline = _chunkZMDZGL5Acjs.Timeline; exports.Toolbar = _chunkJCKGAAPZcjs.Toolbar; exports.Tooltip = _chunkBEOECPVJcjs.Tooltip; exports.TreeView = _chunkXKQMEVODcjs.TreeView; exports.VirtualList = _chunkRUDXHT76cjs.VirtualList; exports.defaultTheme = _chunkRJVHR45Icjs.defaultTheme; exports.familyNameFromFile = _chunkRJVHR45Icjs.familyNameFromFile; exports.isFontAvailable = _chunkRJVHR45Icjs.isFontAvailable; exports.isFontStackAvailable = _chunkRJVHR45Icjs.isFontStackAvailable; exports.registerFontFace = _chunkRJVHR45Icjs.registerFontFace; exports.registerFontFile = _chunkRJVHR45Icjs.registerFontFile; exports.renderIcon = _chunk35LGEPLPcjs.renderIcon; exports.toFontStack = _chunkRJVHR45Icjs.toFontStack; exports.useDropdownContext = _chunkKIZVY57Hcjs.useDropdownContext; exports.useSnackbar = _chunkXERBOFYZcjs.useSnackbar; exports.useSnackbarContext = _chunkXERBOFYZcjs.useSnackbarContext; exports.useTheme = _chunkRJVHR45Icjs.useTheme;
304
+ exports.Accordion = _chunkEAJGSXEWcjs.Accordion; exports.AccordionItem = _chunkEAJGSXEWcjs.AccordionItem; exports.Alert = _chunk3RWZC6OWcjs.Alert; exports.Avatar = _chunkQ3GNKF74cjs.Avatar; exports.AvatarGroup = _chunkQ3GNKF74cjs.AvatarGroup; exports.Badge = _chunk3ETWFS2Ncjs.Badge; exports.Breadcrumb = _chunk5O73AALPcjs.Breadcrumb; exports.Button = _chunkDA375AI6cjs.Button; exports.ButtonGroup = _chunk2ROKOO22cjs.ButtonGroup; exports.Calendar = _chunk6JHVXWDVcjs.Calendar; exports.Card = _chunkV5RS3JZNcjs.Card; exports.Chat = _chunkX3BVQBXDcjs.Chat; exports.Checkbox = _chunkYJNQLH2Lcjs.Checkbox; exports.Chip = _chunkTTLFY76Jcjs.Chip; exports.ColorPicker = _chunkKBQD4JLKcjs.ColorPicker; exports.Combobox = _chunk4AO5RKV5cjs.Combobox; exports.CommandPalette = _chunk2T7ZMMH5cjs.CommandPalette; exports.CommentThread = _chunkX3BVQBXDcjs.CommentThread; exports.ContextMenu = _chunkHNGFRRVZcjs.ContextMenu; exports.DataGrid = _chunk66UQTF57cjs.DataGrid; exports.DatePicker = _chunk6JHVXWDVcjs.DatePicker; exports.Divider = _chunk366R4GKRcjs.Divider; exports.Drawer = _chunkDU5JKTHKcjs.Drawer; exports.Dropdown = _chunkKIZVY57Hcjs.Dropdown; exports.DropdownProvider = _chunkKIZVY57Hcjs.DropdownProvider; exports.EmptyState = _chunkKV3V2CPHcjs.EmptyState; exports.FONT_ACCEPT = _chunkRJVHR45Icjs.FONT_ACCEPT; exports.FileUpload = _chunkXRVRGRC3cjs.FileUpload; exports.Footer = _chunkPTBBY4ZXcjs.Footer; exports.Header = _chunk3KBBJZHKcjs.Header; exports.IconButton = _chunkDA375AI6cjs.IconButton; exports.IdentityHeader = _chunk3KBBJZHKcjs.IdentityHeader; exports.InlineEdit = _chunkI6M6CVI2cjs.InlineEdit; exports.Input = _chunk7VJCGKVPcjs.Input; exports.Kbd = _chunkKVIL4EP4cjs.Kbd; exports.Menu = _chunkJ76JHN7Jcjs.Menu; exports.MenuPanel = _chunkJ76JHN7Jcjs.MenuPanel; exports.MessageComposer = _chunkX3BVQBXDcjs.MessageComposer; exports.Modal = _chunk4JSTBA4Fcjs.Modal; exports.Navigation = _chunkV4BZWBZTcjs.Navigation; exports.NumberInput = _chunkTC5IC6OPcjs.NumberInput; exports.OTPInput = _chunkVHO6JPRRcjs.OTPInput; exports.PageLayout = _chunkTNBML2MJcjs.PageLayout; exports.Pagination = _chunkKREHT4AQcjs.Pagination; exports.Pill = _chunkEQ6BNPTLcjs.Pill; exports.Popover = _chunkVQGLVBPLcjs.Popover; exports.Progress = _chunkV2S6XWIQcjs.Progress; exports.Radio = _chunkKPMIIGHHcjs.Radio; exports.RadioGroup = _chunkKPMIIGHHcjs.RadioGroup; exports.SegmentedControl = _chunkJPYJQ4TFcjs.SegmentedControl; exports.Select = _chunkXZLSHEHEcjs.Select; exports.Skeleton = _chunkIGFR5TEScjs.Skeleton; exports.Slider = _chunkOZZHOEN5cjs.Slider; exports.SnackbarContainer = _chunkXERBOFYZcjs.SnackbarContainer; exports.SnackbarProvider = _chunkXERBOFYZcjs.SnackbarProvider; exports.Spinner = _chunkTROYIUDHcjs.Spinner; exports.SplitButton = _chunkLQXFXXMJcjs.SplitButton; exports.Stepper = _chunkHFZ5FKRGcjs.Stepper; exports.Switch = _chunkIXZMKIV5cjs.Switch; exports.Tab = _chunkNOQF7UQ5cjs.Tab; exports.TabPanel = _chunkNOQF7UQ5cjs.TabPanel; exports.Table = _chunk3ZRFOKDMcjs.Table; exports.TableFiltersDropdown = _chunkPMSENLOAcjs.TableFiltersDropdown; exports.Tabs = _chunkNOQF7UQ5cjs.Tabs; exports.TagInput = _chunkZMRINZIOcjs.TagInput; exports.Textarea = _chunk7BFODYNLcjs.Textarea; exports.ThemeEditor = _chunk36JFXFM2cjs.ThemeEditor; exports.ThemeProvider = _chunkRJVHR45Icjs.ThemeProvider; exports.TimeInput = _chunk6JHVXWDVcjs.TimeInput; exports.Timeline = _chunkZMDZGL5Acjs.Timeline; exports.Toolbar = _chunkJCKGAAPZcjs.Toolbar; exports.Tooltip = _chunkBEOECPVJcjs.Tooltip; exports.TreeView = _chunkXKQMEVODcjs.TreeView; exports.VirtualList = _chunkRUDXHT76cjs.VirtualList; exports.defaultTheme = _chunkRJVHR45Icjs.defaultTheme; exports.familyNameFromFile = _chunkRJVHR45Icjs.familyNameFromFile; exports.isFontAvailable = _chunkRJVHR45Icjs.isFontAvailable; exports.isFontStackAvailable = _chunkRJVHR45Icjs.isFontStackAvailable; exports.registerFontFace = _chunkRJVHR45Icjs.registerFontFace; exports.registerFontFile = _chunkRJVHR45Icjs.registerFontFile; exports.renderIcon = _chunk35LGEPLPcjs.renderIcon; exports.toFontStack = _chunkRJVHR45Icjs.toFontStack; exports.useDropdownContext = _chunkKIZVY57Hcjs.useDropdownContext; exports.useSnackbar = _chunkXERBOFYZcjs.useSnackbar; exports.useSnackbarContext = _chunkXERBOFYZcjs.useSnackbarContext; exports.useTheme = _chunkRJVHR45Icjs.useTheme;
305
305
  //# sourceMappingURL=index.cjs.map
package/dist/index.css CHANGED
@@ -9965,6 +9965,10 @@ kbd.eidos-cmd-kbd {
9965
9965
  min-width: 0;
9966
9966
  overflow: auto;
9967
9967
  }
9968
+ .eidos-pagelayout__content:focus-visible {
9969
+ outline: none;
9970
+ box-shadow: inset 0 0 0 2px var(--primary-color);
9971
+ }
9968
9972
  .eidos-pagelayout__header, .eidos-pagelayout__footer {
9969
9973
  flex: 0 0 auto;
9970
9974
  }
package/dist/index.js CHANGED
@@ -56,7 +56,7 @@ import {
56
56
  } from "./chunk-U233KQZX.js";
57
57
  import {
58
58
  PageLayout
59
- } from "./chunk-26SO4LZ5.js";
59
+ } from "./chunk-3LRT7ZIB.js";
60
60
  import {
61
61
  Toolbar
62
62
  } from "./chunk-SFFKQQ5Z.js";
@@ -184,7 +184,7 @@ import {
184
184
  import {
185
185
  Accordion,
186
186
  AccordionItem
187
- } from "./chunk-CMOBXLNA.js";
187
+ } from "./chunk-LNDFVC2Q.js";
188
188
  import {
189
189
  Alert
190
190
  } from "./chunk-AR2ZOO6L.js";
@@ -1,6 +1,6 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true});
2
2
 
3
- var _chunkHMEREPDBcjs = require('../chunk-HMEREPDB.cjs');
3
+ var _chunkTNBML2MJcjs = require('../chunk-TNBML2MJ.cjs');
4
4
  require('../chunk-JCKGAAPZ.cjs');
5
5
  require('../chunk-3KBBJZHK.cjs');
6
6
  require('../chunk-VQGLVBPL.cjs');
@@ -21,5 +21,5 @@ require('../chunk-35LGEPLP.cjs');
21
21
  require('../chunk-DZUJEN5N.cjs');
22
22
 
23
23
 
24
- exports.PageLayout = _chunkHMEREPDBcjs.PageLayout;
24
+ exports.PageLayout = _chunkTNBML2MJcjs.PageLayout;
25
25
  //# sourceMappingURL=index.cjs.map
@@ -22,6 +22,29 @@ interface PageLayoutProps extends React.HTMLAttributes<HTMLDivElement> {
22
22
  header?: HeaderProps;
23
23
  /** Configures the `Footer` rendered below the page content. Omit to render the default Eidos UI copyright notice. */
24
24
  footer?: FooterProps;
25
+ /**
26
+ * Ref to the `main` content region - the layout's only scroll container.
27
+ *
28
+ * Use it to drive that region imperatively, most commonly to scroll back to
29
+ * the top on navigation (`contentRef.current?.scrollTo({ top: 0 })`). Any
30
+ * ref passed through `HTMLAttributes` reaches the outer element instead,
31
+ * which never scrolls.
32
+ */
33
+ contentRef?: React.Ref<HTMLElement>;
34
+ /**
35
+ * Identifies the current page, so the content region's scroll offset is
36
+ * remembered per page and applied when this value changes. A page seen
37
+ * before reopens where it was left; a new one starts at the top.
38
+ *
39
+ * Pass the value that defines a navigation in your app - `location.pathname`
40
+ * for one offset per path, or a per-history-entry id to match the browser,
41
+ * where Back restores but a fresh link to the same URL starts at the top.
42
+ * Keep it unchanged across a transition that should not move the scroll
43
+ * position, such as a filter written to the query string.
44
+ *
45
+ * Omit it to leave the scroll position alone entirely.
46
+ */
47
+ scrollRestorationKey?: string;
25
48
  }
26
49
 
27
50
  declare const PageLayout: React__default.FC<PageLayoutProps>;
@@ -22,6 +22,29 @@ interface PageLayoutProps extends React.HTMLAttributes<HTMLDivElement> {
22
22
  header?: HeaderProps;
23
23
  /** Configures the `Footer` rendered below the page content. Omit to render the default Eidos UI copyright notice. */
24
24
  footer?: FooterProps;
25
+ /**
26
+ * Ref to the `main` content region - the layout's only scroll container.
27
+ *
28
+ * Use it to drive that region imperatively, most commonly to scroll back to
29
+ * the top on navigation (`contentRef.current?.scrollTo({ top: 0 })`). Any
30
+ * ref passed through `HTMLAttributes` reaches the outer element instead,
31
+ * which never scrolls.
32
+ */
33
+ contentRef?: React.Ref<HTMLElement>;
34
+ /**
35
+ * Identifies the current page, so the content region's scroll offset is
36
+ * remembered per page and applied when this value changes. A page seen
37
+ * before reopens where it was left; a new one starts at the top.
38
+ *
39
+ * Pass the value that defines a navigation in your app - `location.pathname`
40
+ * for one offset per path, or a per-history-entry id to match the browser,
41
+ * where Back restores but a fresh link to the same URL starts at the top.
42
+ * Keep it unchanged across a transition that should not move the scroll
43
+ * position, such as a filter written to the query string.
44
+ *
45
+ * Omit it to leave the scroll position alone entirely.
46
+ */
47
+ scrollRestorationKey?: string;
25
48
  }
26
49
 
27
50
  declare const PageLayout: React__default.FC<PageLayoutProps>;
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  PageLayout
3
- } from "../chunk-26SO4LZ5.js";
3
+ } from "../chunk-3LRT7ZIB.js";
4
4
  import "../chunk-SFFKQQ5Z.js";
5
5
  import "../chunk-OOCCSMWM.js";
6
6
  import "../chunk-4J25XZDI.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eidos-ui",
3
- "version": "3.5.0",
3
+ "version": "3.6.0",
4
4
  "description": "A modern React component library with TypeScript and SCSS",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -1,59 +0,0 @@
1
- import {
2
- Toolbar
3
- } from "./chunk-SFFKQQ5Z.js";
4
- import {
5
- Header
6
- } from "./chunk-OOCCSMWM.js";
7
- import {
8
- Navigation
9
- } from "./chunk-HEX4IMLO.js";
10
- import {
11
- Footer
12
- } from "./chunk-W2F6WDUX.js";
13
-
14
- // src/components/PageLayout/PageLayout.component.tsx
15
- import { jsx, jsxs } from "react/jsx-runtime";
16
- var defaultFooter = { copyright: `\xA9 ${(/* @__PURE__ */ new Date()).getFullYear()} Eidos UI` };
17
- var PageLayout = ({
18
- toolbar,
19
- navigation,
20
- header,
21
- footer,
22
- children,
23
- className = "",
24
- ...rest
25
- }) => {
26
- const classes = ["eidos-pagelayout", className].filter(Boolean).join(" ");
27
- return /* @__PURE__ */ jsxs("div", { className: classes, ...rest, children: [
28
- /* @__PURE__ */ jsx(
29
- Toolbar,
30
- {
31
- ...toolbar,
32
- className: ["eidos-pagelayout__toolbar", toolbar?.className].filter(Boolean).join(" ")
33
- }
34
- ),
35
- /* @__PURE__ */ jsx("div", { className: "eidos-pagelayout__navigation", children: navigation && /* @__PURE__ */ jsx(Navigation, { ...navigation }) }),
36
- /* @__PURE__ */ jsxs("main", { className: "eidos-pagelayout__content", children: [
37
- header && /* @__PURE__ */ jsx(
38
- Header,
39
- {
40
- ...header,
41
- className: ["eidos-pagelayout__header", header.className].filter(Boolean).join(" ")
42
- }
43
- ),
44
- /* @__PURE__ */ jsx("div", { className: "eidos-pagelayout__body", children }),
45
- /* @__PURE__ */ jsx(
46
- Footer,
47
- {
48
- ...footer ?? defaultFooter,
49
- className: ["eidos-pagelayout__footer", footer?.className].filter(Boolean).join(" ")
50
- }
51
- )
52
- ] })
53
- ] });
54
- };
55
-
56
- export {
57
- PageLayout
58
- };
59
- //# sourceMappingURL=chunk-26SO4LZ5.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/components/PageLayout/PageLayout.component.tsx"],"sourcesContent":["import React from 'react';\nimport type { PageLayoutProps } from './PageLayout.types';\nimport './PageLayout.scss';\nimport { Toolbar } from '../Toolbar';\nimport { Header } from '../Header';\nimport { Footer } from '../Footer';\nimport type { FooterProps } from '../Footer';\nimport { Navigation } from '../Navigation';\n\nconst defaultFooter: FooterProps = { copyright: `© ${new Date().getFullYear()} Eidos UI` };\n\nexport const PageLayout: React.FC<PageLayoutProps> = ({\n toolbar,\n navigation,\n header,\n footer,\n children,\n className = '',\n ...rest\n}) => {\n const classes = ['eidos-pagelayout', className].filter(Boolean).join(' ');\n\n return (\n <div className={classes} {...rest}>\n {/* Spread rather than forwarded field by field, so a prop added to\n `Toolbar` reaches `PageLayout` consumers without a second edit\n here - the previous per-field list silently dropped anything it\n didn't know about. `className` is merged last so the layout's own\n grid-area class can't be overwritten by the caller's. */}\n <Toolbar\n {...toolbar}\n className={['eidos-pagelayout__toolbar', toolbar?.className].filter(Boolean).join(' ')}\n />\n <div className=\"eidos-pagelayout__navigation\">\n {navigation && <Navigation {...navigation} />}\n </div>\n <main className=\"eidos-pagelayout__content\">\n {header && (\n <Header\n {...header}\n className={['eidos-pagelayout__header', header.className].filter(Boolean).join(' ')}\n />\n )}\n <div className=\"eidos-pagelayout__body\">{children}</div>\n <Footer\n {...(footer ?? defaultFooter)}\n className={['eidos-pagelayout__footer', footer?.className].filter(Boolean).join(' ')}\n />\n </main>\n </div>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;AA6BM,cAOA,YAPA;AApBN,IAAM,gBAA6B,EAAE,WAAW,SAAK,oBAAI,KAAK,GAAE,YAAY,CAAC,YAAY;AAElF,IAAM,aAAwC,CAAC;AAAA,EACpD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,YAAY;AAAA,EACZ,GAAG;AACL,MAAM;AACJ,QAAM,UAAU,CAAC,oBAAoB,SAAS,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG;AAExE,SACE,qBAAC,SAAI,WAAW,SAAU,GAAG,MAM3B;AAAA;AAAA,MAAC;AAAA;AAAA,QACE,GAAG;AAAA,QACJ,WAAW,CAAC,6BAA6B,SAAS,SAAS,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG;AAAA;AAAA,IACvF;AAAA,IACA,oBAAC,SAAI,WAAU,gCACZ,wBAAc,oBAAC,cAAY,GAAG,YAAY,GAC7C;AAAA,IACA,qBAAC,UAAK,WAAU,6BACb;AAAA,gBACC;AAAA,QAAC;AAAA;AAAA,UACE,GAAG;AAAA,UACJ,WAAW,CAAC,4BAA4B,OAAO,SAAS,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG;AAAA;AAAA,MACpF;AAAA,MAEF,oBAAC,SAAI,WAAU,0BAA0B,UAAS;AAAA,MAClD;AAAA,QAAC;AAAA;AAAA,UACE,GAAI,UAAU;AAAA,UACf,WAAW,CAAC,4BAA4B,QAAQ,SAAS,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG;AAAA;AAAA,MACrF;AAAA,OACF;AAAA,KACF;AAEJ;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/components/Accordion/Accordion.component.tsx"],"sourcesContent":["import React, { useState, useRef, useContext, useId, useEffect, useCallback } from 'react';\nimport { ChevronDown } from 'lucide-react';\nimport type { AccordionProps, AccordionItemProps, AccordionContextValue } from './Accordion.types';\n\n// ============================================================================\n// HELPERS\n// ============================================================================\n\n/** Normalise a controlled/default value (string | string[] | undefined) to string[]. */\nconst toArray = (v: string | string[] | undefined): string[] =>\n v === undefined ? [] : Array.isArray(v) ? v : [v];\n\n// ============================================================================\n// CONTEXT\n// ============================================================================\n\nconst AccordionContext = React.createContext<AccordionContextValue | null>(null);\n\nconst useAccordion = (): AccordionContextValue => {\n const ctx = useContext(AccordionContext);\n if (!ctx) throw new Error('AccordionItem must be used inside <Accordion>');\n return ctx;\n};\n\n// ============================================================================\n// ACCORDION - root container; owns open state; provides context\n// ============================================================================\n\nexport const Accordion: React.FC<AccordionProps> = ({\n multiple = false,\n value,\n defaultValue,\n onChange,\n variant = 'default',\n size = 'md',\n color = 'primary',\n className = '',\n children,\n}) => {\n const isControlled = value !== undefined;\n\n const [localOpen, setLocalOpen] = useState<string[]>(() => toArray(defaultValue));\n const openValues = isControlled ? toArray(value) : localOpen;\n\n const toggle = useCallback(\n (itemValue: string) => {\n const isOpen = openValues.includes(itemValue);\n let next: string[];\n\n if (multiple) {\n next = isOpen ? openValues.filter((v) => v !== itemValue) : [...openValues, itemValue];\n } else {\n next = isOpen ? [] : [itemValue];\n }\n\n if (!isControlled) setLocalOpen(next);\n onChange?.(multiple ? next : (next[0] ?? ''));\n },\n [openValues, multiple, isControlled, onChange],\n );\n\n const rootClasses = ['eidos-accordion', `eidos-accordion--${variant}`, className]\n .filter(Boolean)\n .join(' ');\n\n return (\n <AccordionContext.Provider value={{ openValues, toggle, variant, size, color }}>\n <div className={rootClasses}>{children}</div>\n </AccordionContext.Provider>\n );\n};\n\nAccordion.displayName = 'Accordion';\n\n// ============================================================================\n// ACCORDION ITEM - individual item; reads context; renders trigger + animated panel\n// ============================================================================\n\nexport const AccordionItem: React.FC<AccordionItemProps> = ({\n value,\n label,\n icon,\n disabled = false,\n className = '',\n children,\n}) => {\n const { openValues, toggle, size, color } = useAccordion();\n const isOpen = openValues.includes(value);\n const contentRef = useRef<HTMLDivElement>(null);\n const triggerId = useId();\n const panelId = useId();\n\n // Drives the max-height CSS transition.\n // On open: set to scrollHeight so the wrapper animates to its natural height.\n // On close: set to 0 so the wrapper animates shut.\n // The CSS transition on .eidos-accordion-content-wrapper handles the animation.\n const [maxHeight, setMaxHeight] = useState<number>(0);\n\n useEffect(() => {\n if (!contentRef.current) return;\n setMaxHeight(isOpen ? contentRef.current.scrollHeight : 0);\n }, [isOpen]);\n\n const itemClasses = [\n 'eidos-accordion-item',\n isOpen && 'eidos-accordion-item--open',\n disabled && 'eidos-accordion-item--disabled',\n className,\n ]\n .filter(Boolean)\n .join(' ');\n\n const triggerClasses = [\n 'eidos-accordion-trigger',\n `eidos-accordion-trigger--${size}`,\n isOpen && 'eidos-accordion-trigger--open',\n isOpen && `eidos-accordion-trigger--${color}`,\n ]\n .filter(Boolean)\n .join(' ');\n\n return (\n <div className={itemClasses}>\n <button\n id={triggerId}\n type=\"button\"\n aria-expanded={isOpen}\n aria-controls={panelId}\n disabled={disabled}\n onClick={() => !disabled && toggle(value)}\n className={triggerClasses}\n >\n {icon && <span className=\"eidos-accordion-trigger-icon\">{icon}</span>}\n <span className=\"eidos-accordion-trigger-label\">{label}</span>\n <ChevronDown className=\"eidos-accordion-chevron\" aria-hidden=\"true\" />\n </button>\n\n <div\n id={panelId}\n role=\"region\"\n aria-labelledby={triggerId}\n ref={contentRef}\n className=\"eidos-accordion-content-wrapper\"\n style={{ maxHeight: `${maxHeight}px` }}\n >\n <div className=\"eidos-accordion-content\">{children}</div>\n </div>\n </div>\n );\n};\n\nAccordionItem.displayName = 'AccordionItem';\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["/home/runner/work/eidos-ui/eidos-ui/dist/chunk-HMEREPDB.cjs","../src/components/PageLayout/PageLayout.component.tsx"],"names":[],"mappings":"AAAA;AACE;AACF,wDAA6B;AAC7B;AACE;AACF,wDAA6B;AAC7B;AACE;AACF,wDAA6B;AAC7B;AACE;AACF,wDAA6B;AAC7B;AACA;ACgBM,+CAAA;AApBN,IAAM,cAAA,EAA6B,EAAE,SAAA,EAAW,CAAA,KAAA,EAAA,iBAAK,IAAI,IAAA,CAAK,CAAA,CAAA,CAAE,WAAA,CAAY,CAAC,CAAA,SAAA,EAAY,CAAA;AAElF,IAAM,WAAA,EAAwC,CAAC;AAAA,EACpD,OAAA;AAAA,EACA,UAAA;AAAA,EACA,MAAA;AAAA,EACA,MAAA;AAAA,EACA,QAAA;AAAA,EACA,UAAA,EAAY,EAAA;AAAA,EACZ,GAAG;AACL,CAAA,EAAA,GAAM;AACJ,EAAA,MAAM,QAAA,EAAU,CAAC,kBAAA,EAAoB,SAAS,CAAA,CAAE,MAAA,CAAO,OAAO,CAAA,CAAE,IAAA,CAAK,GAAG,CAAA;AAExE,EAAA,uBACE,8BAAA,KAAC,EAAA,EAAI,SAAA,EAAW,OAAA,EAAU,GAAG,IAAA,EAM3B,QAAA,EAAA;AAAA,oBAAA,6BAAA;AAAA,MAAC,yBAAA;AAAA,MAAA;AAAA,QACE,GAAG,OAAA;AAAA,QACJ,SAAA,EAAW,CAAC,2BAAA,kBAA6B,OAAA,2BAAS,WAAS,CAAA,CAAE,MAAA,CAAO,OAAO,CAAA,CAAE,IAAA,CAAK,GAAG;AAAA,MAAA;AAAA,IACvF,CAAA;AAAA,oBACA,6BAAA,KAAC,EAAA,EAAI,SAAA,EAAU,8BAAA,EACZ,QAAA,EAAA,WAAA,mBAAc,6BAAA,4BAAC,EAAA,EAAY,GAAG,WAAA,CAAY,EAAA,CAC7C,CAAA;AAAA,oBACA,8BAAA,MAAC,EAAA,EAAK,SAAA,EAAU,2BAAA,EACb,QAAA,EAAA;AAAA,MAAA,OAAA,mBACC,6BAAA;AAAA,QAAC,wBAAA;AAAA,QAAA;AAAA,UACE,GAAG,MAAA;AAAA,UACJ,SAAA,EAAW,CAAC,0BAAA,EAA4B,MAAA,CAAO,SAAS,CAAA,CAAE,MAAA,CAAO,OAAO,CAAA,CAAE,IAAA,CAAK,GAAG;AAAA,QAAA;AAAA,MACpF,CAAA;AAAA,sBAEF,6BAAA,KAAC,EAAA,EAAI,SAAA,EAAU,wBAAA,EAA0B,SAAA,CAAS,CAAA;AAAA,sBAClD,6BAAA;AAAA,QAAC,wBAAA;AAAA,QAAA;AAAA,UACE,oBAAI,MAAA,UAAU,eAAA;AAAA,UACf,SAAA,EAAW,CAAC,0BAAA,kBAA4B,MAAA,6BAAQ,WAAS,CAAA,CAAE,MAAA,CAAO,OAAO,CAAA,CAAE,IAAA,CAAK,GAAG;AAAA,QAAA;AAAA,MACrF;AAAA,IAAA,EAAA,CACF;AAAA,EAAA,EAAA,CACF,CAAA;AAEJ,CAAA;ADGA;AACA;AACE;AACF,gCAAC","file":"/home/runner/work/eidos-ui/eidos-ui/dist/chunk-HMEREPDB.cjs","sourcesContent":[null,"import React from 'react';\nimport type { PageLayoutProps } from './PageLayout.types';\nimport './PageLayout.scss';\nimport { Toolbar } from '../Toolbar';\nimport { Header } from '../Header';\nimport { Footer } from '../Footer';\nimport type { FooterProps } from '../Footer';\nimport { Navigation } from '../Navigation';\n\nconst defaultFooter: FooterProps = { copyright: `© ${new Date().getFullYear()} Eidos UI` };\n\nexport const PageLayout: React.FC<PageLayoutProps> = ({\n toolbar,\n navigation,\n header,\n footer,\n children,\n className = '',\n ...rest\n}) => {\n const classes = ['eidos-pagelayout', className].filter(Boolean).join(' ');\n\n return (\n <div className={classes} {...rest}>\n {/* Spread rather than forwarded field by field, so a prop added to\n `Toolbar` reaches `PageLayout` consumers without a second edit\n here - the previous per-field list silently dropped anything it\n didn't know about. `className` is merged last so the layout's own\n grid-area class can't be overwritten by the caller's. */}\n <Toolbar\n {...toolbar}\n className={['eidos-pagelayout__toolbar', toolbar?.className].filter(Boolean).join(' ')}\n />\n <div className=\"eidos-pagelayout__navigation\">\n {navigation && <Navigation {...navigation} />}\n </div>\n <main className=\"eidos-pagelayout__content\">\n {header && (\n <Header\n {...header}\n className={['eidos-pagelayout__header', header.className].filter(Boolean).join(' ')}\n />\n )}\n <div className=\"eidos-pagelayout__body\">{children}</div>\n <Footer\n {...(footer ?? defaultFooter)}\n className={['eidos-pagelayout__footer', footer?.className].filter(Boolean).join(' ')}\n />\n </main>\n </div>\n );\n};\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["/home/runner/work/eidos-ui/eidos-ui/dist/chunk-SDEKANNL.cjs","../src/components/Accordion/Accordion.component.tsx"],"names":[],"mappings":"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","file":"/home/runner/work/eidos-ui/eidos-ui/dist/chunk-SDEKANNL.cjs","sourcesContent":[null,"import React, { useState, useRef, useContext, useId, useEffect, useCallback } from 'react';\nimport { ChevronDown } from 'lucide-react';\nimport type { AccordionProps, AccordionItemProps, AccordionContextValue } from './Accordion.types';\n\n// ============================================================================\n// HELPERS\n// ============================================================================\n\n/** Normalise a controlled/default value (string | string[] | undefined) to string[]. */\nconst toArray = (v: string | string[] | undefined): string[] =>\n v === undefined ? [] : Array.isArray(v) ? v : [v];\n\n// ============================================================================\n// CONTEXT\n// ============================================================================\n\nconst AccordionContext = React.createContext<AccordionContextValue | null>(null);\n\nconst useAccordion = (): AccordionContextValue => {\n const ctx = useContext(AccordionContext);\n if (!ctx) throw new Error('AccordionItem must be used inside <Accordion>');\n return ctx;\n};\n\n// ============================================================================\n// ACCORDION - root container; owns open state; provides context\n// ============================================================================\n\nexport const Accordion: React.FC<AccordionProps> = ({\n multiple = false,\n value,\n defaultValue,\n onChange,\n variant = 'default',\n size = 'md',\n color = 'primary',\n className = '',\n children,\n}) => {\n const isControlled = value !== undefined;\n\n const [localOpen, setLocalOpen] = useState<string[]>(() => toArray(defaultValue));\n const openValues = isControlled ? toArray(value) : localOpen;\n\n const toggle = useCallback(\n (itemValue: string) => {\n const isOpen = openValues.includes(itemValue);\n let next: string[];\n\n if (multiple) {\n next = isOpen ? openValues.filter((v) => v !== itemValue) : [...openValues, itemValue];\n } else {\n next = isOpen ? [] : [itemValue];\n }\n\n if (!isControlled) setLocalOpen(next);\n onChange?.(multiple ? next : (next[0] ?? ''));\n },\n [openValues, multiple, isControlled, onChange],\n );\n\n const rootClasses = ['eidos-accordion', `eidos-accordion--${variant}`, className]\n .filter(Boolean)\n .join(' ');\n\n return (\n <AccordionContext.Provider value={{ openValues, toggle, variant, size, color }}>\n <div className={rootClasses}>{children}</div>\n </AccordionContext.Provider>\n );\n};\n\nAccordion.displayName = 'Accordion';\n\n// ============================================================================\n// ACCORDION ITEM - individual item; reads context; renders trigger + animated panel\n// ============================================================================\n\nexport const AccordionItem: React.FC<AccordionItemProps> = ({\n value,\n label,\n icon,\n disabled = false,\n className = '',\n children,\n}) => {\n const { openValues, toggle, size, color } = useAccordion();\n const isOpen = openValues.includes(value);\n const contentRef = useRef<HTMLDivElement>(null);\n const triggerId = useId();\n const panelId = useId();\n\n // Drives the max-height CSS transition.\n // On open: set to scrollHeight so the wrapper animates to its natural height.\n // On close: set to 0 so the wrapper animates shut.\n // The CSS transition on .eidos-accordion-content-wrapper handles the animation.\n const [maxHeight, setMaxHeight] = useState<number>(0);\n\n useEffect(() => {\n if (!contentRef.current) return;\n setMaxHeight(isOpen ? contentRef.current.scrollHeight : 0);\n }, [isOpen]);\n\n const itemClasses = [\n 'eidos-accordion-item',\n isOpen && 'eidos-accordion-item--open',\n disabled && 'eidos-accordion-item--disabled',\n className,\n ]\n .filter(Boolean)\n .join(' ');\n\n const triggerClasses = [\n 'eidos-accordion-trigger',\n `eidos-accordion-trigger--${size}`,\n isOpen && 'eidos-accordion-trigger--open',\n isOpen && `eidos-accordion-trigger--${color}`,\n ]\n .filter(Boolean)\n .join(' ');\n\n return (\n <div className={itemClasses}>\n <button\n id={triggerId}\n type=\"button\"\n aria-expanded={isOpen}\n aria-controls={panelId}\n disabled={disabled}\n onClick={() => !disabled && toggle(value)}\n className={triggerClasses}\n >\n {icon && <span className=\"eidos-accordion-trigger-icon\">{icon}</span>}\n <span className=\"eidos-accordion-trigger-label\">{label}</span>\n <ChevronDown className=\"eidos-accordion-chevron\" aria-hidden=\"true\" />\n </button>\n\n <div\n id={panelId}\n role=\"region\"\n aria-labelledby={triggerId}\n ref={contentRef}\n className=\"eidos-accordion-content-wrapper\"\n style={{ maxHeight: `${maxHeight}px` }}\n >\n <div className=\"eidos-accordion-content\">{children}</div>\n </div>\n </div>\n );\n};\n\nAccordionItem.displayName = 'AccordionItem';\n"]}