@stackone/malachite 2.76.0 → 2.77.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/COMPONENTS.md +4 -4
- package/dist/components/BaseFormComponent.js +67 -57
- package/dist/components/BaseFormComponent.js.map +1 -1
- package/dist/components/Modal.js +50 -48
- package/dist/components/Modal.js.map +1 -1
- package/dist/components/SidePanel.js +204 -145
- package/dist/components/SidePanel.js.map +1 -1
- package/dist/components/Stepper.js +100 -34
- package/dist/components/Stepper.js.map +1 -1
- package/dist/components/ToastItem.js +1 -1
- package/dist/components/ToastItem.js.map +1 -1
- package/dist/index.d.ts +50 -6
- package/dist/tailwind.css.js +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SidePanel.js","sources":["../../src/components/SidePanel.tsx"],"sourcesContent":["import React, { useEffect, useId, useRef, useState } from 'react';\nimport { createPortal } from 'react-dom';\nimport { ShadowRootWrapper } from '../ShadowRootWrapper';\nimport type { AlertType } from './Alert';\nimport CalloutStrip from './CalloutStrip';\nimport ArrowLeftIcon from './CustomIcons/ArrowLeftIcon';\nimport DismissIcon from './CustomIcons/DismissIcon';\nimport type { RenderLink } from './PageLayout/types';\nimport EllipsisText from './Typography/EllipsisText';\nimport { OVERLAY_ICON_BUTTON_CLASSES, useOverlayBehaviour } from './overlayBehaviour';\nimport { OVERLAY_ELEVATED_ATTR, overlayIdentity } from './portalOverlay';\n\nexport interface SidePanelProps {\n open: boolean;\n /** Dismiss handler — close button, ESC, or overlay click. */\n onClose: () => void;\n title: React.ReactNode;\n subtitle?: React.ReactNode;\n /** `below` stacks the subtitle under the title, so neither truncates the other. */\n subtitlePlacement?: 'inline' | 'below';\n /** Content at the end of the title row, after the subtitle (e.g. a status tag). */\n titleExtra?: React.ReactNode;\n /** Block paragraph below the title row, spanning the header width. Announced as the panel's description. */\n description?: React.ReactNode;\n /** Pinned between the header and the scrolling body. */\n toolbar?: React.ReactNode;\n /** Divider under the header. Default: false. */\n headerDivider?: boolean;\n /** Leading header slot, typically a logo or icon. */\n icon?: React.ReactNode;\n /** Content pinned right in the header, before the close button. */\n headerExtra?: React.ReactNode;\n footer?: React.ReactNode;\n /** Renders a back button before the header icon. */\n onBack?: () => void;\n /** Maximum panel width. Default: 1080. */\n maxWidth?: number | string;\n /** Accessible dialog name; required when `title` isn't a plain string. */\n 'aria-label'?: string;\n /** ID of a visible element naming the dialog (alternative to `aria-label`). */\n 'aria-labelledby'?: string;\n /** Whether the body gets default padding. Default: true. */\n bodyPadding?: boolean;\n /** `above-app-overlays` lifts the overlay stack clear of chrome pinned near max-int. */\n stacking?: 'default' | 'above-app-overlays';\n /** Full-width Alert bands at the top of the body, above `children`, in order. Unaffected by\n * `bodyPadding`, and announced ahead of `description`. The message is markdown. */\n callouts?: { type?: AlertType; message: string }[];\n /** Routes root-relative links in `callouts` through the app's router; see `MarkdownBlock`. */\n renderLink?: RenderLink;\n children: React.ReactNode;\n}\n\nconst TRANSITION_MS = 200;\n\nconst toCssSize = (value: number | string): string =>\n typeof value === 'number' ? `${value}px` : value;\n\n/**\n * A right-hand slide-in overlay panel (drawer) for inspecting or editing an entity\n * without leaving the page. Portals to `document.body`, locks background scroll and\n * traps keyboard focus while open (restoring both on close), dismisses on ESC\n * (topmost open overlay only), overlay click or the close button, and exposes header\n * (back/icon/title/subtitle/extra), scrollable body and optional footer slots.\n */\nconst SidePanel: React.FC<SidePanelProps> = ({\n open,\n onClose,\n title,\n subtitle,\n subtitlePlacement = 'inline',\n titleExtra,\n description,\n toolbar,\n headerDivider = false,\n icon,\n headerExtra,\n footer,\n onBack,\n maxWidth = 1080,\n 'aria-label': ariaLabel,\n 'aria-labelledby': ariaLabelledBy,\n bodyPadding = true,\n stacking = 'default',\n callouts,\n renderLink,\n children,\n}) => {\n const calloutId = useId();\n const descriptionId = useId();\n const [isVisible, setIsVisible] = useState(false);\n const [isMounted, setIsMounted] = useState(false);\n const frameRef = useRef<number | null>(null);\n const unmountTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const hostRef = useRef<HTMLDivElement | null>(null);\n const {\n ref: handlePanelRef,\n isTopmost,\n zIndex,\n elevated,\n } = useOverlayBehaviour({\n open,\n onClose,\n elevated: stacking === 'above-app-overlays',\n hostRef,\n });\n\n useEffect(() => {\n if (open) {\n setIsMounted(true);\n if (unmountTimerRef.current) {\n clearTimeout(unmountTimerRef.current);\n unmountTimerRef.current = null;\n }\n frameRef.current = requestAnimationFrame(() => {\n frameRef.current = null;\n setIsVisible(true);\n });\n } else {\n setIsVisible(false);\n unmountTimerRef.current = setTimeout(() => {\n unmountTimerRef.current = null;\n setIsMounted(false);\n }, TRANSITION_MS);\n }\n return () => {\n if (frameRef.current !== null) {\n cancelAnimationFrame(frameRef.current);\n frameRef.current = null;\n }\n if (unmountTimerRef.current) {\n clearTimeout(unmountTimerRef.current);\n unmountTimerRef.current = null;\n }\n };\n }, [open]);\n\n if (!isMounted) {\n return null;\n }\n\n const shownCallouts = callouts?.filter((callout) => callout.message.trim());\n const hasCallouts = !!shownCallouts?.length;\n const paddedBody = bodyPadding ? (\n <div className={hasCallouts ? 'p-[28px]' : 'py-[28px]'}>{children}</div>\n ) : (\n children\n );\n\n const subtitleBelow = subtitlePlacement === 'below' && !!subtitle;\n const titleNode = (\n <span\n className={subtitleBelow ? 'flex min-w-0 shrink' : 'flex min-w-0 max-w-full shrink-0'}\n >\n <EllipsisText\n tooltip={title}\n tooltipWhenTruncated\n tooltipPlacement=\"bottom\"\n className=\"min-w-0 text-[length:var(--malachite-font-size-15,15px)] font-semibold\"\n >\n {title}\n </EllipsisText>\n </span>\n );\n const subtitleNode = subtitle ? (\n <EllipsisText\n tooltip={subtitle}\n tooltipWhenTruncated\n tooltipPlacement=\"bottom\"\n color=\"secondary\"\n className=\"min-w-0 text-[13px]\"\n >\n {subtitle}\n </EllipsisText>\n ) : null;\n const titleExtraNode = titleExtra ? (\n <span className=\"flex shrink-0 items-center\">{titleExtra}</span>\n ) : null;\n const headerExtraNode = headerExtra ? (\n <span className=\"flex shrink-0 items-baseline\">{headerExtra}</span>\n ) : null;\n\n return createPortal(\n // Host carries the stacking context — ShadowRootWrapper's `isolation` would\n // otherwise flatten our z-index and let sticky page elements paint over us.\n <ShadowRootWrapper\n hostRef={hostRef}\n hostStyle={{ position: 'relative', zIndex }}\n hostProps={\n elevated\n ? { ...overlayIdentity('sidepanel'), [OVERLAY_ELEVATED_ATTR]: '' }\n : overlayIdentity('sidepanel')\n }\n >\n <div className=\"fixed inset-0 z-[1000]\">\n <div\n aria-hidden=\"true\"\n onClick={onClose}\n className={`absolute inset-0 bg-[var(--malachite-overlay-scrim)] transition-opacity duration-200 ease-in-out ${\n isVisible ? 'opacity-100' : 'opacity-0'\n }`}\n />\n <div\n ref={handlePanelRef}\n role=\"dialog\"\n // Only the topmost overlay may claim it, or a screen reader can still\n // browse the one underneath.\n aria-modal={isTopmost ? 'true' : undefined}\n aria-describedby={\n [\n ...(shownCallouts ?? []).map((_, index) => `${calloutId}-${index}`),\n description ? descriptionId : null,\n ]\n .filter(Boolean)\n .join(' ') || undefined\n }\n tabIndex={-1}\n // Prefer aria-labelledby; only fall back to aria-label when it's absent.\n aria-label={\n ariaLabelledBy\n ? undefined\n : (ariaLabel ?? (typeof title === 'string' ? title : undefined))\n }\n aria-labelledby={ariaLabelledBy}\n className=\"absolute top-[8px] right-[8px] bottom-[8px] flex flex-col overflow-hidden rounded-[20px] border border-solid border-[var(--malachite-card-border)] bg-[var(--malachite-card-background)] text-[var(--malachite-card-foreground)] outline-none [box-shadow:var(--malachite-shadow-lg)]\"\n style={{\n width: `min(calc(100% - 16px), ${toCssSize(maxWidth)})`,\n // Inline: Tailwind translate utilities silently break inside\n // shadow roots (see CLAUDE.md Shadow DOM caveats).\n transform: isVisible ? 'translateX(0)' : 'translateX(calc(100% + 16px))',\n transition: `transform ${TRANSITION_MS}ms ease-in-out`,\n }}\n >\n <div\n className={`flex flex-col gap-[20px] px-[20px] py-[18px] ${\n headerDivider\n ? 'border-0 border-b border-solid border-[var(--malachite-card-border)]'\n : ''\n }`}\n >\n <div className=\"flex items-center gap-[12px]\">\n {onBack ? (\n <button\n type=\"button\"\n aria-label=\"Back\"\n onClick={onBack}\n className={OVERLAY_ICON_BUTTON_CLASSES}\n >\n <ArrowLeftIcon size={16} />\n </button>\n ) : null}\n {icon ? (\n <span className=\"flex shrink-0 items-center\">{icon}</span>\n ) : null}\n {subtitleBelow ? (\n <div className=\"flex min-w-0 flex-1 items-center gap-[12px]\">\n <div className=\"flex min-w-0 flex-1 flex-col gap-[2px]\">\n <div className=\"flex min-w-0 items-baseline gap-[8px]\">\n {titleNode}\n {titleExtraNode}\n </div>\n {subtitleNode}\n </div>\n {headerExtraNode}\n </div>\n ) : (\n <div className=\"flex min-w-0 flex-1 items-baseline gap-[12px]\">\n <div className=\"flex min-w-0 flex-1 items-baseline gap-[8px]\">\n <div className=\"flex min-w-0 items-baseline gap-[8px]\">\n {titleNode}\n {subtitleNode}\n </div>\n {titleExtraNode}\n </div>\n {headerExtraNode}\n </div>\n )}\n <button\n type=\"button\"\n aria-label=\"Close\"\n onClick={onClose}\n className={OVERLAY_ICON_BUTTON_CLASSES}\n >\n <DismissIcon size={10} />\n </button>\n </div>\n {description ? (\n <div\n id={descriptionId}\n className=\"text-[13px] leading-[1.6] text-[var(--malachite-text-secondary)]\"\n >\n {description}\n </div>\n ) : null}\n </div>\n {toolbar ? (\n <div className=\"shrink-0 px-[16px] pt-[16px] pb-[8px]\">{toolbar}</div>\n ) : null}\n {/* Callouts move the horizontal padding inward so their strips run full-bleed. */}\n <div\n className={`min-h-0 flex-1 overflow-y-auto ${bodyPadding && !hasCallouts ? 'px-[28px]' : ''}`}\n >\n {shownCallouts?.map((callout, index) => (\n <CalloutStrip\n key={index}\n id={`${calloutId}-${index}`}\n type={callout.type}\n message={callout.message}\n renderLink={renderLink}\n />\n ))}\n {paddedBody}\n </div>\n {footer ? (\n <div className=\"border-0 border-t border-solid border-[var(--malachite-card-border)] px-[24px] py-[16px]\">\n {footer}\n </div>\n ) : null}\n </div>\n </div>\n </ShadowRootWrapper>,\n document.body,\n );\n};\n\nexport default SidePanel;\nexport { SidePanel };\n"],"names":["TRANSITION_MS","toCssSize","value","SidePanel","open","onClose","title","subtitle","subtitlePlacement","titleExtra","description","toolbar","headerDivider","icon","headerExtra","footer","onBack","maxWidth","ariaLabel","ariaLabelledBy","bodyPadding","stacking","callouts","renderLink","children","calloutId","useId","descriptionId","isVisible","setIsVisible","useState","isMounted","setIsMounted","frameRef","useRef","unmountTimerRef","hostRef","handlePanelRef","isTopmost","zIndex","elevated","useOverlayBehaviour","useEffect","shownCallouts","callout","hasCallouts","paddedBody","jsx","subtitleBelow","titleNode","EllipsisText","subtitleNode","titleExtraNode","headerExtraNode","createPortal","ShadowRootWrapper","overlayIdentity","OVERLAY_ELEVATED_ATTR","jsxs","_","index","OVERLAY_ICON_BUTTON_CLASSES","ArrowLeftIcon","DismissIcon","CalloutStrip"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"SidePanel.js","sources":["../../src/components/SidePanel.tsx"],"sourcesContent":["import React, { useEffect, useId, useRef, useState } from 'react';\nimport { createPortal } from 'react-dom';\nimport { ShadowRootWrapper } from '../ShadowRootWrapper';\nimport type { AlertType } from './Alert';\nimport CalloutStrip from './CalloutStrip';\nimport ArrowLeftIcon from './CustomIcons/ArrowLeftIcon';\nimport DismissIcon from './CustomIcons/DismissIcon';\nimport type { RenderLink } from './PageLayout/types';\nimport EllipsisText from './Typography/EllipsisText';\nimport { OVERLAY_ICON_BUTTON_CLASSES, useOverlayBehaviour } from './overlayBehaviour';\nimport { OVERLAY_ELEVATED_ATTR, overlayIdentity } from './portalOverlay';\n\nexport interface SidePanelProps {\n open: boolean;\n /** Dismiss handler — close button, ESC, or overlay click. */\n onClose: () => void;\n title: React.ReactNode;\n subtitle?: React.ReactNode;\n /** `below` stacks the subtitle under the title, so neither truncates the other. */\n subtitlePlacement?: 'inline' | 'below';\n /** Content at the end of the title row, after the subtitle (e.g. a status tag). */\n titleExtra?: React.ReactNode;\n /** Block paragraph below the title row, spanning the header width. Announced as the panel's description. */\n description?: React.ReactNode;\n /** Pinned between the header and the scrolling body. */\n toolbar?: React.ReactNode;\n /** Divider under the header. Default: false. */\n headerDivider?: boolean;\n /** Leading header slot, typically a logo or icon. */\n icon?: React.ReactNode;\n /** Content pinned right in the header, before the close button. */\n headerExtra?: React.ReactNode;\n footer?: React.ReactNode;\n /** Renders a back button before the header icon. */\n onBack?: () => void;\n /** Maximum panel width. Default: 1080. */\n maxWidth?: number | string;\n /** Accessible dialog name; required when `title` isn't a plain string. */\n 'aria-label'?: string;\n /** ID of a visible element naming the dialog (alternative to `aria-label`). */\n 'aria-labelledby'?: string;\n /** Whether the body gets default padding. Default: true. */\n bodyPadding?: boolean;\n /** `above-app-overlays` lifts the overlay stack clear of chrome pinned near max-int. */\n stacking?: 'default' | 'above-app-overlays';\n /** Full-width Alert bands at the top of the body, above `children`, in order. Unaffected by\n * `bodyPadding`, and announced ahead of `description`. The message is markdown. */\n callouts?: { type?: AlertType; message: string }[];\n /** Routes root-relative links in `callouts` through the app's router; see `MarkdownBlock`. */\n renderLink?: RenderLink;\n /** A full-height column opened to the left, as wide again as `maxWidth`. The panel's own\n * header, body and footer keep their width and place; the aside's header and footer line\n * up with them. The aside is never narrower than 360px: a window too narrow for both\n * columns scrolls the panel sideways. */\n aside?: SidePanelAside;\n children: React.ReactNode;\n}\n\nexport interface SidePanelAside {\n /** Shown in the aside's header, and names the column for assistive tech. */\n title: string;\n /** Content pinned right in the aside's header (e.g. a stepper). */\n headerExtra?: React.ReactNode;\n /** Pinned at the bottom, level with the panel's footer. */\n footer?: React.ReactNode;\n /** Whether the aside's body gets default padding, as the panel's `bodyPadding`. Default: true. */\n bodyPadding?: boolean;\n children: React.ReactNode;\n}\n\nconst TRANSITION_MS = 200;\nconst ASIDE_MIN_WIDTH = '360px';\n\ninterface ScrollingBodyProps {\n bodyPadding: boolean;\n callouts?: { type?: AlertType; message: string }[];\n /** Prefix for each callout's id, which the panel names in `aria-describedby`. */\n calloutId?: string;\n renderLink?: RenderLink;\n children: React.ReactNode;\n}\n\nconst ScrollingBody: React.FC<ScrollingBodyProps> = ({\n bodyPadding,\n callouts,\n calloutId,\n renderLink,\n children,\n}) => {\n const hasCallouts = !!callouts?.length;\n\n // Callouts move the horizontal padding inward so their strips run full-bleed.\n return (\n <div\n className={`min-h-0 flex-1 overflow-y-auto ${bodyPadding && !hasCallouts ? 'px-[28px]' : ''}`}\n >\n {callouts?.map((callout, index) => (\n <CalloutStrip\n key={index}\n id={calloutId ? `${calloutId}-${index}` : undefined}\n type={callout.type}\n message={callout.message}\n renderLink={renderLink}\n />\n ))}\n {bodyPadding ? (\n <div className={hasCallouts ? 'p-[28px]' : 'py-[28px]'}>{children}</div>\n ) : (\n children\n )}\n </div>\n );\n};\n\nconst toCssSize = (value: number | string): string =>\n typeof value === 'number' ? `${value}px` : value;\n\n/**\n * A right-hand slide-in overlay panel (drawer) for inspecting or editing an entity\n * without leaving the page. Portals to `document.body`, locks background scroll and\n * traps keyboard focus while open (restoring both on close), dismisses on ESC\n * (topmost open overlay only), overlay click or the close button, and exposes header\n * (back/icon/title/subtitle/extra), scrollable body and optional footer slots.\n */\nconst SidePanel: React.FC<SidePanelProps> = ({\n open,\n onClose,\n title,\n subtitle,\n subtitlePlacement = 'inline',\n titleExtra,\n description,\n toolbar,\n headerDivider = false,\n icon,\n headerExtra,\n footer,\n onBack,\n maxWidth = 1080,\n 'aria-label': ariaLabel,\n 'aria-labelledby': ariaLabelledBy,\n bodyPadding = true,\n stacking = 'default',\n callouts,\n renderLink,\n aside,\n children,\n}) => {\n const calloutId = useId();\n const descriptionId = useId();\n const [isVisible, setIsVisible] = useState(false);\n const [isMounted, setIsMounted] = useState(false);\n const frameRef = useRef<number | null>(null);\n const unmountTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const hostRef = useRef<HTMLDivElement | null>(null);\n const {\n ref: handlePanelRef,\n isTopmost,\n zIndex,\n elevated,\n } = useOverlayBehaviour({\n open,\n onClose,\n elevated: stacking === 'above-app-overlays',\n hostRef,\n });\n\n useEffect(() => {\n if (open) {\n setIsMounted(true);\n if (unmountTimerRef.current) {\n clearTimeout(unmountTimerRef.current);\n unmountTimerRef.current = null;\n }\n frameRef.current = requestAnimationFrame(() => {\n frameRef.current = null;\n setIsVisible(true);\n });\n } else {\n setIsVisible(false);\n unmountTimerRef.current = setTimeout(() => {\n unmountTimerRef.current = null;\n setIsMounted(false);\n }, TRANSITION_MS);\n }\n return () => {\n if (frameRef.current !== null) {\n cancelAnimationFrame(frameRef.current);\n frameRef.current = null;\n }\n if (unmountTimerRef.current) {\n clearTimeout(unmountTimerRef.current);\n unmountTimerRef.current = null;\n }\n };\n }, [open]);\n\n if (!isMounted) {\n return null;\n }\n\n const shownCallouts = callouts?.filter((callout) => callout.message.trim());\n const panelColumnWidth = `calc(${toCssSize(maxWidth)} - 2px)`;\n const dividerBelow = headerDivider\n ? 'border-0 border-b border-solid border-[var(--malachite-card-border)]'\n : '';\n // Each column is a subgrid of the panel's three rows, so the aside's header and footer\n // stay level with the panel's whatever their content. The panel's own column is always the\n // second, so opening or closing the aside never remounts it or reflows it mid-transition:\n // without an aside the first column is simply empty and narrows to nothing.\n const asideColumn = aside ? (\n <aside\n aria-label={aside.title}\n className=\"col-start-1 row-span-3 grid min-w-0 grid-cols-[minmax(0,1fr)] grid-rows-subgrid overflow-hidden border-0 border-r border-solid border-[var(--malachite-card-border)]\"\n >\n <div\n className={`flex min-w-0 items-center gap-[8px] px-[20px] py-[18px] ${dividerBelow}`}\n >\n <span className=\"flex min-w-0 shrink-0\">\n <EllipsisText\n tooltip={aside.title}\n tooltipWhenTruncated\n tooltipPlacement=\"bottom\"\n className=\"min-w-0 text-[length:var(--malachite-font-size-15,15px)] font-semibold\"\n >\n {aside.title}\n </EllipsisText>\n </span>\n {aside.headerExtra ? (\n <span className=\"ml-auto flex shrink-0 items-center\">{aside.headerExtra}</span>\n ) : null}\n </div>\n <div className={`flex min-h-0 flex-col ${aside.footer ? '' : 'row-span-2'}`}>\n <ScrollingBody bodyPadding={aside.bodyPadding ?? true}>\n {aside.children}\n </ScrollingBody>\n </div>\n {aside.footer ? (\n <div className=\"border-0 border-t border-solid border-[var(--malachite-card-border)] px-[24px] py-[16px]\">\n {aside.footer}\n </div>\n ) : null}\n </aside>\n ) : null;\n\n const subtitleBelow = subtitlePlacement === 'below' && !!subtitle;\n const titleNode = (\n <span\n className={subtitleBelow ? 'flex min-w-0 shrink' : 'flex min-w-0 max-w-full shrink-0'}\n >\n <EllipsisText\n tooltip={title}\n tooltipWhenTruncated\n tooltipPlacement=\"bottom\"\n className=\"min-w-0 text-[length:var(--malachite-font-size-15,15px)] font-semibold\"\n >\n {title}\n </EllipsisText>\n </span>\n );\n const subtitleNode = subtitle ? (\n <EllipsisText\n tooltip={subtitle}\n tooltipWhenTruncated\n tooltipPlacement=\"bottom\"\n color=\"secondary\"\n className=\"min-w-0 text-[13px]\"\n >\n {subtitle}\n </EllipsisText>\n ) : null;\n const titleExtraNode = titleExtra ? (\n <span className=\"flex shrink-0 items-center\">{titleExtra}</span>\n ) : null;\n const headerExtraNode = headerExtra ? (\n <span className=\"flex shrink-0 items-baseline\">{headerExtra}</span>\n ) : null;\n\n return createPortal(\n // Host carries the stacking context — ShadowRootWrapper's `isolation` would\n // otherwise flatten our z-index and let sticky page elements paint over us.\n <ShadowRootWrapper\n hostRef={hostRef}\n hostStyle={{ position: 'relative', zIndex }}\n hostProps={\n elevated\n ? { ...overlayIdentity('sidepanel'), [OVERLAY_ELEVATED_ATTR]: '' }\n : overlayIdentity('sidepanel')\n }\n >\n <div className=\"fixed inset-0 z-[1000]\">\n <div\n aria-hidden=\"true\"\n onClick={onClose}\n className={`absolute inset-0 bg-[var(--malachite-overlay-scrim)] transition-opacity duration-200 ease-in-out ${\n isVisible ? 'opacity-100' : 'opacity-0'\n }`}\n />\n <div\n ref={handlePanelRef}\n role=\"dialog\"\n // Only the topmost overlay may claim it, or a screen reader can still\n // browse the one underneath.\n aria-modal={isTopmost ? 'true' : undefined}\n aria-describedby={\n [\n ...(shownCallouts ?? []).map((_, index) => `${calloutId}-${index}`),\n description ? descriptionId : null,\n ]\n .filter(Boolean)\n .join(' ') || undefined\n }\n tabIndex={-1}\n // Prefer aria-labelledby; only fall back to aria-label when it's absent.\n aria-label={\n ariaLabelledBy\n ? undefined\n : (ariaLabel ?? (typeof title === 'string' ? title : undefined))\n }\n aria-labelledby={ariaLabelledBy}\n className={`absolute top-[8px] right-[8px] bottom-[8px] grid grid-rows-[auto_minmax(0,1fr)_auto] overflow-x-auto overflow-y-hidden rounded-[20px] border border-solid border-[var(--malachite-card-border)] bg-[var(--malachite-card-background)] text-[var(--malachite-card-foreground)] outline-none [box-shadow:var(--malachite-shadow-lg)]`}\n style={{\n width: `min(calc(100% - 16px), ${aside ? `calc(2 * ${toCssSize(maxWidth)})` : toCssSize(maxWidth)})`,\n // Inline: Tailwind translate utilities silently break inside\n // shadow roots (see CLAUDE.md Shadow DOM caveats).\n transform: isVisible ? 'translateX(0)' : 'translateX(calc(100% + 16px))',\n transition: `transform ${TRANSITION_MS}ms ease-in-out, width ${TRANSITION_MS}ms ease-in-out, grid-template-columns ${TRANSITION_MS}ms ease-in-out`,\n // The panel's own column is the width it has without an aside: `maxWidth`\n // less the 1px border either side, so opening the aside never widens it.\n // With an aside neither column gives way, so a narrow window scrolls the\n // panel instead. Both templates share one shape so the minimums animate\n // with the width rather than overflowing mid-transition.\n gridTemplateColumns: aside\n ? `minmax(${ASIDE_MIN_WIDTH}, 1fr) minmax(${panelColumnWidth}, ${panelColumnWidth})`\n : `minmax(0px, 1fr) minmax(0px, ${panelColumnWidth})`,\n }}\n >\n {asideColumn}\n {/* An explicit column: an implicit one is sized to its widest content and overflows the panel. */}\n <div className=\"col-start-2 row-span-3 grid min-w-0 grid-cols-[minmax(0,1fr)] grid-rows-subgrid\">\n <div\n className={`flex flex-col gap-[20px] px-[20px] py-[18px] ${dividerBelow}`}\n >\n <div className=\"flex items-center gap-[12px]\">\n {onBack ? (\n <button\n type=\"button\"\n aria-label=\"Back\"\n onClick={onBack}\n className={OVERLAY_ICON_BUTTON_CLASSES}\n >\n <ArrowLeftIcon size={16} />\n </button>\n ) : null}\n {icon ? (\n <span className=\"flex shrink-0 items-center\">{icon}</span>\n ) : null}\n {subtitleBelow ? (\n <div className=\"flex min-w-0 flex-1 items-center gap-[12px]\">\n <div className=\"flex min-w-0 flex-1 flex-col gap-[2px]\">\n <div className=\"flex min-w-0 items-baseline gap-[8px]\">\n {titleNode}\n {titleExtraNode}\n </div>\n {subtitleNode}\n </div>\n {headerExtraNode}\n </div>\n ) : (\n <div className=\"flex min-w-0 flex-1 items-baseline gap-[12px]\">\n <div className=\"flex min-w-0 flex-1 items-baseline gap-[8px]\">\n <div className=\"flex min-w-0 items-baseline gap-[8px]\">\n {titleNode}\n {subtitleNode}\n </div>\n {titleExtraNode}\n </div>\n {headerExtraNode}\n </div>\n )}\n <button\n type=\"button\"\n aria-label=\"Close\"\n onClick={onClose}\n className={OVERLAY_ICON_BUTTON_CLASSES}\n >\n <DismissIcon size={10} />\n </button>\n </div>\n {description ? (\n <div\n id={descriptionId}\n className=\"text-[13px] leading-[1.6] text-[var(--malachite-text-secondary)]\"\n >\n {description}\n </div>\n ) : null}\n </div>\n {/* The toolbar shares the body's row, so an aside's header and footer stay level. */}\n <div className={`flex min-h-0 flex-col ${footer ? '' : 'row-span-2'}`}>\n {toolbar ? (\n <div className=\"shrink-0 px-[16px] pt-[16px] pb-[8px]\">\n {toolbar}\n </div>\n ) : null}\n <ScrollingBody\n bodyPadding={bodyPadding}\n callouts={shownCallouts}\n calloutId={calloutId}\n renderLink={renderLink}\n >\n {children}\n </ScrollingBody>\n </div>\n {footer ? (\n <div className=\"border-0 border-t border-solid border-[var(--malachite-card-border)] px-[24px] py-[16px]\">\n {footer}\n </div>\n ) : null}\n </div>\n </div>\n </div>\n </ShadowRootWrapper>,\n document.body,\n );\n};\n\nexport default SidePanel;\nexport { SidePanel };\n"],"names":["TRANSITION_MS","ASIDE_MIN_WIDTH","ScrollingBody","bodyPadding","callouts","calloutId","renderLink","children","hasCallouts","jsxs","callout","index","jsx","CalloutStrip","toCssSize","value","SidePanel","open","onClose","title","subtitle","subtitlePlacement","titleExtra","description","toolbar","headerDivider","icon","headerExtra","footer","onBack","maxWidth","ariaLabel","ariaLabelledBy","stacking","aside","useId","descriptionId","isVisible","setIsVisible","useState","isMounted","setIsMounted","frameRef","useRef","unmountTimerRef","hostRef","handlePanelRef","isTopmost","zIndex","elevated","useOverlayBehaviour","useEffect","shownCallouts","panelColumnWidth","dividerBelow","asideColumn","EllipsisText","subtitleBelow","titleNode","subtitleNode","titleExtraNode","headerExtraNode","createPortal","ShadowRootWrapper","overlayIdentity","OVERLAY_ELEVATED_ATTR","_","OVERLAY_ICON_BUTTON_CLASSES","ArrowLeftIcon","DismissIcon"],"mappings":";;;;;;;;;;AAsEA,MAAMA,IAAgB,KAChBC,KAAkB,SAWlBC,IAA8C,CAAC;AAAA,EACjD,aAAAC;AAAA,EACA,UAAAC;AAAA,EACA,WAAAC;AAAA,EACA,YAAAC;AAAA,EACA,UAAAC;AACJ,MAAM;AACF,QAAMC,IAAc,CAAC,EAACJ,KAAA,QAAAA,EAAU;AAGhC,SACI,gBAAAK;AAAA,IAAC;AAAA,IAAA;AAAA,MACG,WAAW,kCAAkCN,KAAe,CAACK,IAAc,cAAc,EAAE;AAAA,MAE1F,UAAA;AAAA,QAAAJ,KAAA,gBAAAA,EAAU,IAAI,CAACM,GAASC,MACrB,gBAAAC;AAAA,UAACC;AAAA,UAAA;AAAA,YAEG,IAAIR,IAAY,GAAGA,CAAS,IAAIM,CAAK,KAAK;AAAA,YAC1C,MAAMD,EAAQ;AAAA,YACd,SAASA,EAAQ;AAAA,YACjB,YAAAJ;AAAA,UAAA;AAAA,UAJKK;AAAA,QAAA;AAAA,QAOZR,sBACI,OAAA,EAAI,WAAWK,IAAc,aAAa,aAAc,UAAAD,GAAS,IAElEA;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAIhB,GAEMO,IAAY,CAACC,MACf,OAAOA,KAAU,WAAW,GAAGA,CAAK,OAAOA,GASzCC,KAAsC,CAAC;AAAA,EACzC,MAAAC;AAAA,EACA,SAAAC;AAAA,EACA,OAAAC;AAAA,EACA,UAAAC;AAAA,EACA,mBAAAC,IAAoB;AAAA,EACpB,YAAAC;AAAA,EACA,aAAAC;AAAA,EACA,SAAAC;AAAA,EACA,eAAAC,IAAgB;AAAA,EAChB,MAAAC;AAAA,EACA,aAAAC;AAAA,EACA,QAAAC;AAAA,EACA,QAAAC;AAAA,EACA,UAAAC,IAAW;AAAA,EACX,cAAcC;AAAA,EACd,mBAAmBC;AAAA,EACnB,aAAA7B,IAAc;AAAA,EACd,UAAA8B,IAAW;AAAA,EACX,UAAA7B;AAAA,EACA,YAAAE;AAAA,EACA,OAAA4B;AAAA,EACA,UAAA3B;AACJ,MAAM;AACF,QAAMF,IAAY8B,EAAA,GACZC,IAAgBD,EAAA,GAChB,CAACE,GAAWC,CAAY,IAAIC,EAAS,EAAK,GAC1C,CAACC,GAAWC,CAAY,IAAIF,EAAS,EAAK,GAC1CG,IAAWC,EAAsB,IAAI,GACrCC,IAAkBD,EAA6C,IAAI,GACnEE,IAAUF,EAA8B,IAAI,GAC5C;AAAA,IACF,KAAKG;AAAA,IACL,WAAAC;AAAA,IACA,QAAAC;AAAA,IACA,UAAAC;AAAA,EAAA,IACAC,GAAoB;AAAA,IACpB,MAAAjC;AAAA,IACA,SAAAC;AAAA,IACA,UAAUe,MAAa;AAAA,IACvB,SAAAY;AAAA,EAAA,CACH;AAgCD,MA9BAM,GAAU,OACFlC,KACAwB,EAAa,EAAI,GACbG,EAAgB,YAChB,aAAaA,EAAgB,OAAO,GACpCA,EAAgB,UAAU,OAE9BF,EAAS,UAAU,sBAAsB,MAAM;AAC3C,IAAAA,EAAS,UAAU,MACnBJ,EAAa,EAAI;AAAA,EACrB,CAAC,MAEDA,EAAa,EAAK,GAClBM,EAAgB,UAAU,WAAW,MAAM;AACvC,IAAAA,EAAgB,UAAU,MAC1BH,EAAa,EAAK;AAAA,EACtB,GAAGzC,CAAa,IAEb,MAAM;AACT,IAAI0C,EAAS,YAAY,SACrB,qBAAqBA,EAAS,OAAO,GACrCA,EAAS,UAAU,OAEnBE,EAAgB,YAChB,aAAaA,EAAgB,OAAO,GACpCA,EAAgB,UAAU;AAAA,EAElC,IACD,CAAC3B,CAAI,CAAC,GAEL,CAACuB;AACD,WAAO;AAGX,QAAMY,IAAgBhD,KAAA,gBAAAA,EAAU,OAAO,CAACM,MAAYA,EAAQ,QAAQ,SAC9D2C,IAAmB,QAAQvC,EAAUgB,CAAQ,CAAC,WAC9CwB,IAAe7B,IACf,yEACA,IAKA8B,KAAcrB,IAChB,gBAAAzB;AAAA,IAAC;AAAA,IAAA;AAAA,MACG,cAAYyB,EAAM;AAAA,MAClB,WAAU;AAAA,MAEV,UAAA;AAAA,QAAA,gBAAAzB;AAAA,UAAC;AAAA,UAAA;AAAA,YACG,WAAW,2DAA2D6C,CAAY;AAAA,YAElF,UAAA;AAAA,cAAA,gBAAA1C,EAAC,QAAA,EAAK,WAAU,yBACZ,UAAA,gBAAAA;AAAA,gBAAC4C;AAAA,gBAAA;AAAA,kBACG,SAAStB,EAAM;AAAA,kBACf,sBAAoB;AAAA,kBACpB,kBAAiB;AAAA,kBACjB,WAAU;AAAA,kBAET,UAAAA,EAAM;AAAA,gBAAA;AAAA,cAAA,GAEf;AAAA,cACCA,EAAM,cACH,gBAAAtB,EAAC,QAAA,EAAK,WAAU,sCAAsC,UAAAsB,EAAM,aAAY,IACxE;AAAA,YAAA;AAAA,UAAA;AAAA,QAAA;AAAA,0BAEP,OAAA,EAAI,WAAW,yBAAyBA,EAAM,SAAS,KAAK,YAAY,IACrE,UAAA,gBAAAtB,EAACV,KAAc,aAAagC,EAAM,eAAe,IAC5C,UAAAA,EAAM,UACX,GACJ;AAAA,QACCA,EAAM,SACH,gBAAAtB,EAAC,OAAA,EAAI,WAAU,4FACV,UAAAsB,EAAM,QACX,IACA;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA,IAER,MAEEuB,IAAgBpC,MAAsB,WAAW,CAAC,CAACD,GACnDsC,IACF,gBAAA9C;AAAA,IAAC;AAAA,IAAA;AAAA,MACG,WAAW6C,IAAgB,wBAAwB;AAAA,MAEnD,UAAA,gBAAA7C;AAAA,QAAC4C;AAAA,QAAA;AAAA,UACG,SAASrC;AAAA,UACT,sBAAoB;AAAA,UACpB,kBAAiB;AAAA,UACjB,WAAU;AAAA,UAET,UAAAA;AAAA,QAAA;AAAA,MAAA;AAAA,IACL;AAAA,EAAA,GAGFwC,IAAevC,IACjB,gBAAAR;AAAA,IAAC4C;AAAA,IAAA;AAAA,MACG,SAASpC;AAAA,MACT,sBAAoB;AAAA,MACpB,kBAAiB;AAAA,MACjB,OAAM;AAAA,MACN,WAAU;AAAA,MAET,UAAAA;AAAA,IAAA;AAAA,EAAA,IAEL,MACEwC,IAAiBtC,IACnB,gBAAAV,EAAC,UAAK,WAAU,8BAA8B,aAAW,IACzD,MACEiD,IAAkBlC,IACpB,gBAAAf,EAAC,UAAK,WAAU,gCAAgC,aAAY,IAC5D;AAEJ,SAAOkD;AAAA;AAAA;AAAA,IAGH,gBAAAlD;AAAA,MAACmD;AAAA,MAAA;AAAA,QACG,SAAAlB;AAAA,QACA,WAAW,EAAE,UAAU,YAAY,QAAAG,EAAA;AAAA,QACnC,WACIC,KACM,EAAE,GAAGe,EAAgB,WAAW,GAAG,CAACC,EAAqB,GAAG,GAAA,IAC5DD,EAAgB,WAAW;AAAA,QAGrC,UAAA,gBAAAvD,EAAC,OAAA,EAAI,WAAU,0BACX,UAAA;AAAA,UAAA,gBAAAG;AAAA,YAAC;AAAA,YAAA;AAAA,cACG,eAAY;AAAA,cACZ,SAASM;AAAA,cACT,WAAW,oGACPmB,IAAY,gBAAgB,WAChC;AAAA,YAAA;AAAA,UAAA;AAAA,UAEJ,gBAAA5B;AAAA,YAAC;AAAA,YAAA;AAAA,cACG,KAAKqC;AAAA,cACL,MAAK;AAAA,cAGL,cAAYC,IAAY,SAAS;AAAA,cACjC,oBACI;AAAA,gBACI,IAAIK,KAAiB,IAAI,IAAI,CAACc,GAAGvD,OAAU,GAAGN,CAAS,IAAIM,EAAK,EAAE;AAAA,gBAClEY,IAAca,IAAgB;AAAA,cAAA,EAE7B,OAAO,OAAO,EACd,KAAK,GAAG,KAAK;AAAA,cAEtB,UAAU;AAAA,cAEV,cACIJ,IACM,SACCD,MAAc,OAAOZ,KAAU,WAAWA,IAAQ;AAAA,cAE7D,mBAAiBa;AAAA,cACjB,WAAW;AAAA,cACX,OAAO;AAAA,gBACH,OAAO,0BAA0BE,IAAQ,YAAYpB,EAAUgB,CAAQ,CAAC,MAAMhB,EAAUgB,CAAQ,CAAC;AAAA;AAAA;AAAA,gBAGjG,WAAWO,IAAY,kBAAkB;AAAA,gBACzC,YAAY,aAAarC,CAAa,yBAAyBA,CAAa,yCAAyCA,CAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,gBAMlI,qBAAqBkC,IACf,UAAUjC,EAAe,iBAAiBoD,CAAgB,KAAKA,CAAgB,MAC/E,gCAAgCA,CAAgB;AAAA,cAAA;AAAA,cAGzD,UAAA;AAAA,gBAAAE;AAAA,gBAED,gBAAA9C,EAAC,OAAA,EAAI,WAAU,mFACX,UAAA;AAAA,kBAAA,gBAAAA;AAAA,oBAAC;AAAA,oBAAA;AAAA,sBACG,WAAW,gDAAgD6C,CAAY;AAAA,sBAEvE,UAAA;AAAA,wBAAA,gBAAA7C,EAAC,OAAA,EAAI,WAAU,gCACV,UAAA;AAAA,0BAAAoB,IACG,gBAAAjB;AAAA,4BAAC;AAAA,4BAAA;AAAA,8BACG,MAAK;AAAA,8BACL,cAAW;AAAA,8BACX,SAASiB;AAAA,8BACT,WAAWsC;AAAA,8BAEX,UAAA,gBAAAvD,EAACwD,IAAA,EAAc,MAAM,GAAA,CAAI;AAAA,4BAAA;AAAA,0BAAA,IAE7B;AAAA,0BACH1C,IACG,gBAAAd,EAAC,QAAA,EAAK,WAAU,8BAA8B,aAAK,IACnD;AAAA,0BACH6C,IACG,gBAAAhD,EAAC,OAAA,EAAI,WAAU,+CACX,UAAA;AAAA,4BAAA,gBAAAA,EAAC,OAAA,EAAI,WAAU,0CACX,UAAA;AAAA,8BAAA,gBAAAA,EAAC,OAAA,EAAI,WAAU,yCACV,UAAA;AAAA,gCAAAiD;AAAA,gCACAE;AAAA,8BAAA,GACL;AAAA,8BACCD;AAAA,4BAAA,GACL;AAAA,4BACCE;AAAA,0BAAA,EAAA,CACL,IAEA,gBAAApD,EAAC,OAAA,EAAI,WAAU,iDACX,UAAA;AAAA,4BAAA,gBAAAA,EAAC,OAAA,EAAI,WAAU,gDACX,UAAA;AAAA,8BAAA,gBAAAA,EAAC,OAAA,EAAI,WAAU,yCACV,UAAA;AAAA,gCAAAiD;AAAA,gCACAC;AAAA,8BAAA,GACL;AAAA,8BACCC;AAAA,4BAAA,GACL;AAAA,4BACCC;AAAA,0BAAA,GACL;AAAA,0BAEJ,gBAAAjD;AAAA,4BAAC;AAAA,4BAAA;AAAA,8BACG,MAAK;AAAA,8BACL,cAAW;AAAA,8BACX,SAASM;AAAA,8BACT,WAAWiD;AAAA,8BAEX,UAAA,gBAAAvD,EAACyD,IAAA,EAAY,MAAM,GAAA,CAAI;AAAA,4BAAA;AAAA,0BAAA;AAAA,wBAC3B,GACJ;AAAA,wBACC9C,IACG,gBAAAX;AAAA,0BAAC;AAAA,0BAAA;AAAA,4BACG,IAAIwB;AAAA,4BACJ,WAAU;AAAA,4BAET,UAAAb;AAAA,0BAAA;AAAA,wBAAA,IAEL;AAAA,sBAAA;AAAA,oBAAA;AAAA,kBAAA;AAAA,oCAGP,OAAA,EAAI,WAAW,yBAAyBK,IAAS,KAAK,YAAY,IAC9D,UAAA;AAAA,oBAAAJ,IACG,gBAAAZ,EAAC,OAAA,EAAI,WAAU,yCACV,aACL,IACA;AAAA,oBACJ,gBAAAA;AAAA,sBAACV;AAAA,sBAAA;AAAA,wBACG,aAAAC;AAAA,wBACA,UAAUiD;AAAA,wBACV,WAAA/C;AAAA,wBACA,YAAAC;AAAA,wBAEC,UAAAC;AAAA,sBAAA;AAAA,oBAAA;AAAA,kBACL,GACJ;AAAA,kBACCqB,IACG,gBAAAhB,EAAC,OAAA,EAAI,WAAU,4FACV,aACL,IACA;AAAA,gBAAA,EAAA,CACR;AAAA,cAAA;AAAA,YAAA;AAAA,UAAA;AAAA,QACJ,EAAA,CACJ;AAAA,MAAA;AAAA,IAAA;AAAA,IAEJ,SAAS;AAAA,EAAA;AAEjB;"}
|
|
@@ -1,40 +1,106 @@
|
|
|
1
|
-
import { jsx as
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import { jsx as n } from "react/jsx-runtime";
|
|
2
|
+
import { FOCUS_RING as h } from "./focusRing.js";
|
|
3
|
+
const N = "var(--malachite-accent-foreground)", d = "color-mix(in srgb, var(--malachite-accent-foreground), white 50%)", C = "var(--malachite-card-border)", b = (o, e) => o === e ? N : o < e ? d : C, x = ({ label: o, isCurrent: e, onClick: s, className: a, style: c }) => s ? /* @__PURE__ */ n(
|
|
4
|
+
"button",
|
|
5
|
+
{
|
|
6
|
+
type: "button",
|
|
7
|
+
"aria-label": o,
|
|
8
|
+
"aria-current": e ? "step" : void 0,
|
|
9
|
+
onClick: s,
|
|
10
|
+
className: `${a} cursor-pointer p-0 ${h}`,
|
|
11
|
+
style: c
|
|
12
|
+
}
|
|
13
|
+
) : /* @__PURE__ */ n("span", { className: a, style: c }), k = (o, e, s) => {
|
|
14
|
+
const a = o.map(
|
|
15
|
+
(l, t) => o.slice(0, t).reduce((i, p) => i + Math.max(0, p.steps), 0)
|
|
16
|
+
), c = o.findIndex(
|
|
17
|
+
(l, t) => e >= a[t] && e < a[t] + l.steps
|
|
18
|
+
);
|
|
19
|
+
return o.map((l, t) => {
|
|
20
|
+
const i = a[t];
|
|
21
|
+
if (l.steps <= 0)
|
|
22
|
+
return null;
|
|
23
|
+
if (t !== c) {
|
|
24
|
+
const p = t < c;
|
|
25
|
+
return /* @__PURE__ */ n(
|
|
26
|
+
x,
|
|
27
|
+
{
|
|
28
|
+
label: `Go to ${l.label}`,
|
|
29
|
+
isCurrent: !1,
|
|
30
|
+
onClick: s && (() => s(i)),
|
|
31
|
+
className: "h-[8px] w-[16px] shrink-0 rounded-[3px] border-2 border-solid",
|
|
32
|
+
style: {
|
|
33
|
+
borderColor: p ? d : C,
|
|
34
|
+
backgroundColor: p ? d : "transparent"
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
t
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
return /* @__PURE__ */ n(
|
|
41
|
+
"div",
|
|
42
|
+
{
|
|
43
|
+
className: "mx-[6px] flex min-w-0 flex-1 gap-[4px] first:ml-0 last:mr-0",
|
|
44
|
+
children: Array.from({ length: Math.max(0, l.steps) }, (p, u) => {
|
|
45
|
+
const r = i + u;
|
|
46
|
+
return /* @__PURE__ */ n(
|
|
47
|
+
x,
|
|
48
|
+
{
|
|
49
|
+
label: `Go to step ${r + 1}`,
|
|
50
|
+
isCurrent: r === e,
|
|
51
|
+
onClick: s && (() => s(r)),
|
|
52
|
+
className: "h-[8px] min-w-0 flex-1 rounded-[3px] border-0",
|
|
53
|
+
style: { backgroundColor: b(r, e) }
|
|
54
|
+
},
|
|
55
|
+
r
|
|
56
|
+
);
|
|
57
|
+
})
|
|
58
|
+
},
|
|
59
|
+
t
|
|
60
|
+
);
|
|
61
|
+
});
|
|
62
|
+
}, y = ({
|
|
63
|
+
steps: o,
|
|
64
|
+
groups: e,
|
|
65
|
+
active: s,
|
|
66
|
+
onChange: a,
|
|
67
|
+
className: c = "",
|
|
7
68
|
"aria-label": l,
|
|
8
|
-
"aria-labelledby":
|
|
69
|
+
"aria-labelledby": t
|
|
9
70
|
}) => {
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
"
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
71
|
+
const p = {
|
|
72
|
+
role: !!(l ?? t) ? "group" : void 0,
|
|
73
|
+
"aria-label": l,
|
|
74
|
+
"aria-labelledby": t
|
|
75
|
+
};
|
|
76
|
+
if (e)
|
|
77
|
+
return /* @__PURE__ */ n("div", { ...p, className: `flex w-full items-center gap-[4px] ${c}`, children: k(e, s, a) });
|
|
78
|
+
const u = Array.from({ length: Math.max(0, o) }, (r, m) => m);
|
|
79
|
+
return /* @__PURE__ */ n("div", { ...p, className: `flex gap-[4px] ${c}`, children: u.map((r) => {
|
|
80
|
+
const m = r === s, f = `h-[8px] rounded-full transition-all ${m ? "w-[28px]" : "w-[16px]"}`;
|
|
81
|
+
return a ? /* @__PURE__ */ n(
|
|
82
|
+
"button",
|
|
83
|
+
{
|
|
84
|
+
type: "button",
|
|
85
|
+
"aria-label": `Go to step ${r + 1}`,
|
|
86
|
+
"aria-current": m ? "step" : void 0,
|
|
87
|
+
onClick: () => a(r),
|
|
88
|
+
className: `${f} border-0 p-0 cursor-pointer`,
|
|
89
|
+
style: { backgroundColor: b(r, s) }
|
|
90
|
+
},
|
|
91
|
+
r
|
|
92
|
+
) : /* @__PURE__ */ n(
|
|
93
|
+
"span",
|
|
94
|
+
{
|
|
95
|
+
className: f,
|
|
96
|
+
style: { backgroundColor: b(r, s) }
|
|
97
|
+
},
|
|
98
|
+
r
|
|
99
|
+
);
|
|
100
|
+
}) });
|
|
35
101
|
};
|
|
36
102
|
export {
|
|
37
|
-
|
|
38
|
-
|
|
103
|
+
y as Stepper,
|
|
104
|
+
y as default
|
|
39
105
|
};
|
|
40
106
|
//# sourceMappingURL=Stepper.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Stepper.js","sources":["../../src/components/Stepper.tsx"],"sourcesContent":["import React from 'react';\
|
|
1
|
+
{"version":3,"file":"Stepper.js","sources":["../../src/components/Stepper.tsx"],"sourcesContent":["import React from 'react';\nimport { FOCUS_RING } from './focusRing';\n\ninterface StepperBaseProps {\n /** Active step index (0-based), counted across every group when grouped. */\n active: number;\n /** When provided, each step becomes a clickable control. */\n onChange?: (index: number) => void;\n className?: string;\n 'aria-label'?: string;\n 'aria-labelledby'?: string;\n}\n\nexport interface StepperProps extends StepperBaseProps {\n /** Total number of steps. */\n steps: number;\n groups?: never;\n}\n\nexport interface StepperGroup {\n /** Names the group's folded mark, e.g. a section title. */\n label: string;\n steps: number;\n}\n\n/**\n * Steps in consecutive groups. The active step's group opens into one bar per step across\n * the full width, and every other group folds to a short bar. Folded bars keep a fixed width\n * and the open group shares what is left, so allow about 20px per other group and 12px per\n * step in the open group.\n */\nexport interface GroupedStepperProps extends StepperBaseProps {\n groups: StepperGroup[];\n steps?: never;\n}\n\n// Completed steps get a lightened accent, the active step the full accent,\n// upcoming steps a neutral border colour.\nconst ACTIVE_COLOR = 'var(--malachite-accent-foreground)';\nconst COMPLETED_COLOR = 'color-mix(in srgb, var(--malachite-accent-foreground), white 50%)';\nconst UPCOMING_COLOR = 'var(--malachite-card-border)';\n\nconst stepColor = (index: number, active: number): string =>\n index === active ? ACTIVE_COLOR : index < active ? COMPLETED_COLOR : UPCOMING_COLOR;\n\ninterface MarkProps {\n label: string;\n isCurrent: boolean;\n onClick?: () => void;\n className: string;\n style: React.CSSProperties;\n}\n\nconst Mark: React.FC<MarkProps> = ({ label, isCurrent, onClick, className, style }) =>\n onClick ? (\n <button\n type=\"button\"\n aria-label={label}\n aria-current={isCurrent ? 'step' : undefined}\n onClick={onClick}\n className={`${className} cursor-pointer p-0 ${FOCUS_RING}`}\n style={style}\n />\n ) : (\n <span className={className} style={style} />\n );\n\nconst groupedMarks = (\n groups: StepperGroup[],\n active: number,\n onChange: ((index: number) => void) | undefined,\n): React.ReactNode[] => {\n const starts = groups.map((_, index) =>\n groups.slice(0, index).reduce((total, group) => total + Math.max(0, group.steps), 0),\n );\n const currentGroup = groups.findIndex(\n (group, index) => active >= starts[index] && active < starts[index] + group.steps,\n );\n\n return groups.map((group, groupIndex) => {\n const start = starts[groupIndex];\n if (group.steps <= 0) {\n return null;\n }\n if (groupIndex !== currentGroup) {\n const isDone = groupIndex < currentGroup;\n return (\n <Mark\n key={groupIndex}\n label={`Go to ${group.label}`}\n isCurrent={false}\n onClick={onChange && (() => onChange(start))}\n className=\"h-[8px] w-[16px] shrink-0 rounded-[3px] border-2 border-solid\"\n style={{\n borderColor: isDone ? COMPLETED_COLOR : UPCOMING_COLOR,\n backgroundColor: isDone ? COMPLETED_COLOR : 'transparent',\n }}\n />\n );\n }\n\n return (\n <div\n key={groupIndex}\n className=\"mx-[6px] flex min-w-0 flex-1 gap-[4px] first:ml-0 last:mr-0\"\n >\n {Array.from({ length: Math.max(0, group.steps) }, (_, offset) => {\n const index = start + offset;\n return (\n <Mark\n key={index}\n label={`Go to step ${index + 1}`}\n isCurrent={index === active}\n onClick={onChange && (() => onChange(index))}\n className=\"h-[8px] min-w-0 flex-1 rounded-[3px] border-0\"\n style={{ backgroundColor: stepColor(index, active) }}\n />\n );\n })}\n </div>\n );\n });\n};\n\nconst Stepper: React.FC<StepperProps | GroupedStepperProps> = ({\n steps,\n groups,\n active,\n onChange,\n className = '',\n 'aria-label': ariaLabel,\n 'aria-labelledby': ariaLabelledBy,\n}) => {\n // A role is what makes aria-label apply at all, but an unnamed group is pure noise\n // in the a11y tree, so the role only appears alongside a name. progressbar would be\n // wrong either way: its contents are presentational and cannot expose the step buttons.\n const isNamed = Boolean(ariaLabel ?? ariaLabelledBy);\n const groupProps = {\n role: isNamed ? 'group' : undefined,\n 'aria-label': ariaLabel,\n 'aria-labelledby': ariaLabelledBy,\n };\n\n if (groups) {\n return (\n <div {...groupProps} className={`flex w-full items-center gap-[4px] ${className}`}>\n {groupedMarks(groups, active, onChange)}\n </div>\n );\n }\n\n const segments = Array.from({ length: Math.max(0, steps) }, (_, index) => index);\n\n return (\n <div {...groupProps} className={`flex gap-[4px] ${className}`}>\n {segments.map((index) => {\n const isActive = index === active;\n const base = `h-[8px] rounded-full transition-all ${isActive ? 'w-[28px]' : 'w-[16px]'}`;\n\n if (!onChange) {\n return (\n <span\n key={index}\n className={base}\n style={{ backgroundColor: stepColor(index, active) }}\n />\n );\n }\n\n return (\n <button\n key={index}\n type=\"button\"\n aria-label={`Go to step ${index + 1}`}\n aria-current={isActive ? 'step' : undefined}\n onClick={() => onChange(index)}\n className={`${base} border-0 p-0 cursor-pointer`}\n style={{ backgroundColor: stepColor(index, active) }}\n />\n );\n })}\n </div>\n );\n};\n\nexport default Stepper;\nexport { Stepper };\n"],"names":["ACTIVE_COLOR","COMPLETED_COLOR","UPCOMING_COLOR","stepColor","index","active","Mark","label","isCurrent","onClick","className","style","jsx","FOCUS_RING","groupedMarks","groups","onChange","starts","_","total","group","currentGroup","groupIndex","start","isDone","offset","Stepper","steps","ariaLabel","ariaLabelledBy","groupProps","segments","isActive","base"],"mappings":";;AAsCA,MAAMA,IAAe,sCACfC,IAAkB,qEAClBC,IAAiB,gCAEjBC,IAAY,CAACC,GAAeC,MAC9BD,MAAUC,IAASL,IAAeI,IAAQC,IAASJ,IAAkBC,GAUnEI,IAA4B,CAAC,EAAE,OAAAC,GAAO,WAAAC,GAAW,SAAAC,GAAS,WAAAC,GAAW,OAAAC,QACvEF,IACI,gBAAAG;AAAA,EAAC;AAAA,EAAA;AAAA,IACG,MAAK;AAAA,IACL,cAAYL;AAAA,IACZ,gBAAcC,IAAY,SAAS;AAAA,IACnC,SAAAC;AAAA,IACA,WAAW,GAAGC,CAAS,uBAAuBG,CAAU;AAAA,IACxD,OAAAF;AAAA,EAAA;AACJ,IAEA,gBAAAC,EAAC,QAAA,EAAK,WAAAF,GAAsB,OAAAC,EAAA,CAAc,GAG5CG,IAAe,CACjBC,GACAV,GACAW,MACoB;AACpB,QAAMC,IAASF,EAAO;AAAA,IAAI,CAACG,GAAGd,MAC1BW,EAAO,MAAM,GAAGX,CAAK,EAAE,OAAO,CAACe,GAAOC,MAAUD,IAAQ,KAAK,IAAI,GAAGC,EAAM,KAAK,GAAG,CAAC;AAAA,EAAA,GAEjFC,IAAeN,EAAO;AAAA,IACxB,CAACK,GAAOhB,MAAUC,KAAUY,EAAOb,CAAK,KAAKC,IAASY,EAAOb,CAAK,IAAIgB,EAAM;AAAA,EAAA;AAGhF,SAAOL,EAAO,IAAI,CAACK,GAAOE,MAAe;AACrC,UAAMC,IAAQN,EAAOK,CAAU;AAC/B,QAAIF,EAAM,SAAS;AACf,aAAO;AAEX,QAAIE,MAAeD,GAAc;AAC7B,YAAMG,IAASF,IAAaD;AAC5B,aACI,gBAAAT;AAAA,QAACN;AAAA,QAAA;AAAA,UAEG,OAAO,SAASc,EAAM,KAAK;AAAA,UAC3B,WAAW;AAAA,UACX,SAASJ,MAAa,MAAMA,EAASO,CAAK;AAAA,UAC1C,WAAU;AAAA,UACV,OAAO;AAAA,YACH,aAAaC,IAASvB,IAAkBC;AAAA,YACxC,iBAAiBsB,IAASvB,IAAkB;AAAA,UAAA;AAAA,QAChD;AAAA,QARKqB;AAAA,MAAA;AAAA,IAWjB;AAEA,WACI,gBAAAV;AAAA,MAAC;AAAA,MAAA;AAAA,QAEG,WAAU;AAAA,QAET,UAAA,MAAM,KAAK,EAAE,QAAQ,KAAK,IAAI,GAAGQ,EAAM,KAAK,EAAA,GAAK,CAACF,GAAGO,MAAW;AAC7D,gBAAMrB,IAAQmB,IAAQE;AACtB,iBACI,gBAAAb;AAAA,YAACN;AAAA,YAAA;AAAA,cAEG,OAAO,cAAcF,IAAQ,CAAC;AAAA,cAC9B,WAAWA,MAAUC;AAAA,cACrB,SAASW,MAAa,MAAMA,EAASZ,CAAK;AAAA,cAC1C,WAAU;AAAA,cACV,OAAO,EAAE,iBAAiBD,EAAUC,GAAOC,CAAM,EAAA;AAAA,YAAE;AAAA,YAL9CD;AAAA,UAAA;AAAA,QAQjB,CAAC;AAAA,MAAA;AAAA,MAfIkB;AAAA,IAAA;AAAA,EAkBjB,CAAC;AACL,GAEMI,IAAwD,CAAC;AAAA,EAC3D,OAAAC;AAAA,EACA,QAAAZ;AAAA,EACA,QAAAV;AAAA,EACA,UAAAW;AAAA,EACA,WAAAN,IAAY;AAAA,EACZ,cAAckB;AAAA,EACd,mBAAmBC;AACvB,MAAM;AAKF,QAAMC,IAAa;AAAA,IACf,MAFY,GAAQF,KAAaC,KAEjB,UAAU;AAAA,IAC1B,cAAcD;AAAA,IACd,mBAAmBC;AAAA,EAAA;AAGvB,MAAId;AACA,WACI,gBAAAH,EAAC,OAAA,EAAK,GAAGkB,GAAY,WAAW,sCAAsCpB,CAAS,IAC1E,UAAAI,EAAaC,GAAQV,GAAQW,CAAQ,GAC1C;AAIR,QAAMe,IAAW,MAAM,KAAK,EAAE,QAAQ,KAAK,IAAI,GAAGJ,CAAK,EAAA,GAAK,CAACT,GAAGd,MAAUA,CAAK;AAE/E,SACI,gBAAAQ,EAAC,OAAA,EAAK,GAAGkB,GAAY,WAAW,kBAAkBpB,CAAS,IACtD,UAAAqB,EAAS,IAAI,CAAC3B,MAAU;AACrB,UAAM4B,IAAW5B,MAAUC,GACrB4B,IAAO,uCAAuCD,IAAW,aAAa,UAAU;AAEtF,WAAKhB,IAWD,gBAAAJ;AAAA,MAAC;AAAA,MAAA;AAAA,QAEG,MAAK;AAAA,QACL,cAAY,cAAcR,IAAQ,CAAC;AAAA,QACnC,gBAAc4B,IAAW,SAAS;AAAA,QAClC,SAAS,MAAMhB,EAASZ,CAAK;AAAA,QAC7B,WAAW,GAAG6B,CAAI;AAAA,QAClB,OAAO,EAAE,iBAAiB9B,EAAUC,GAAOC,CAAM,EAAA;AAAA,MAAE;AAAA,MAN9CD;AAAA,IAAA,IAVL,gBAAAQ;AAAA,MAAC;AAAA,MAAA;AAAA,QAEG,WAAWqB;AAAA,QACX,OAAO,EAAE,iBAAiB9B,EAAUC,GAAOC,CAAM,EAAA;AAAA,MAAE;AAAA,MAF9CD;AAAA,IAAA;AAAA,EAkBrB,CAAC,EAAA,CACL;AAER;"}
|
|
@@ -35,7 +35,7 @@ const f = {
|
|
|
35
35
|
onClick: (e) => {
|
|
36
36
|
e.stopPropagation(), a(r.id);
|
|
37
37
|
},
|
|
38
|
-
className: "pointer-events-auto flex max-w-[480px] cursor-pointer items-start gap-[8px] rounded-
|
|
38
|
+
className: "pointer-events-auto flex max-w-[480px] cursor-pointer items-start gap-[8px] rounded-xl bg-[var(--malachite-tooltip-background,rgb(59,59,59))] px-[12px] py-[8px] text-[length:var(--malachite-font-size-medium,14px)] leading-[20px] text-[var(--malachite-tooltip-foreground,rgb(255,255,255))] [box-shadow:var(--malachite-tooltip-shadow,0px_0px_10px_0px_rgba(0,0,0,0.2))] animate-[malachite-toast-in_150ms_ease-out] motion-reduce:animate-none",
|
|
39
39
|
style: { overflowWrap: "anywhere" },
|
|
40
40
|
children: [
|
|
41
41
|
/* @__PURE__ */ l(d, { size: 16, className: `mt-[2px] shrink-0 ${f[r.variant]}` }),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ToastItem.js","sources":["../../src/components/ToastItem.tsx"],"sourcesContent":["import { useEffect, useRef, useState } from 'react';\nimport CheckCircleFilled from './CustomIcons/CheckCircleFilled';\nimport ErrorCircleFilled from './CustomIcons/ErrorCircleFilled';\nimport type { QueuedToast, ToastVariant } from './toastTypes';\n\n// The plain status tokens fall under 3:1 on the pill; the mix clears it in both themes.\nconst ICON_COLOR: Record<ToastVariant, string> = {\n success:\n 'text-[var(--malachite-text-success,rgb(2,122,66))] supports-[color:color-mix(in_srgb,red,red)]:text-[color-mix(in_srgb,var(--malachite-text-success,rgb(2,122,66))_70%,var(--malachite-tooltip-foreground,rgb(255,255,255)))]',\n error: 'text-[var(--malachite-text-destructive,rgb(237,53,54))] supports-[color:color-mix(in_srgb,red,red)]:text-[color-mix(in_srgb,var(--malachite-text-destructive,rgb(237,53,54))_70%,var(--malachite-tooltip-foreground,rgb(255,255,255)))]',\n};\n\ninterface ToastItemProps {\n toast: QueuedToast;\n duration: number;\n onDismiss: (id: number) => void;\n}\n\nconst ToastItem: React.FC<ToastItemProps> = ({ toast, duration, onDismiss }) => {\n const [paused, setPaused] = useState(false);\n const ref = useRef<HTMLDivElement>(null);\n const remaining = useRef(duration);\n\n // React's onPointerEnter never fires across this portal's shadow boundary.\n useEffect(() => {\n const el = ref.current;\n if (!el) {\n return;\n }\n const pause = () => setPaused(true);\n const resume = () => setPaused(false);\n el.addEventListener('pointerenter', pause);\n el.addEventListener('pointerleave', resume);\n return () => {\n el.removeEventListener('pointerenter', pause);\n el.removeEventListener('pointerleave', resume);\n };\n }, []);\n\n useEffect(() => {\n if (paused) {\n return;\n }\n const startedAt = Date.now();\n const timeout = setTimeout(() => onDismiss(toast.id), remaining.current);\n return () => {\n clearTimeout(timeout);\n remaining.current = Math.max(0, remaining.current - (Date.now() - startedAt));\n };\n }, [paused, onDismiss, toast.id]);\n\n const Icon = toast.variant === 'error' ? ErrorCircleFilled : CheckCircleFilled;\n\n return (\n // aria-hidden: the provider's live regions are the announced copy.\n <div\n ref={ref}\n aria-hidden=\"true\"\n data-toast={toast.variant}\n onClick={(event) => {\n // A portal bubbles through the React tree, to the provider's ancestors.\n event.stopPropagation();\n onDismiss(toast.id);\n }}\n className=\"pointer-events-auto flex max-w-[480px] cursor-pointer items-start gap-[8px] rounded-
|
|
1
|
+
{"version":3,"file":"ToastItem.js","sources":["../../src/components/ToastItem.tsx"],"sourcesContent":["import { useEffect, useRef, useState } from 'react';\nimport CheckCircleFilled from './CustomIcons/CheckCircleFilled';\nimport ErrorCircleFilled from './CustomIcons/ErrorCircleFilled';\nimport type { QueuedToast, ToastVariant } from './toastTypes';\n\n// The plain status tokens fall under 3:1 on the pill; the mix clears it in both themes.\nconst ICON_COLOR: Record<ToastVariant, string> = {\n success:\n 'text-[var(--malachite-text-success,rgb(2,122,66))] supports-[color:color-mix(in_srgb,red,red)]:text-[color-mix(in_srgb,var(--malachite-text-success,rgb(2,122,66))_70%,var(--malachite-tooltip-foreground,rgb(255,255,255)))]',\n error: 'text-[var(--malachite-text-destructive,rgb(237,53,54))] supports-[color:color-mix(in_srgb,red,red)]:text-[color-mix(in_srgb,var(--malachite-text-destructive,rgb(237,53,54))_70%,var(--malachite-tooltip-foreground,rgb(255,255,255)))]',\n};\n\ninterface ToastItemProps {\n toast: QueuedToast;\n duration: number;\n onDismiss: (id: number) => void;\n}\n\nconst ToastItem: React.FC<ToastItemProps> = ({ toast, duration, onDismiss }) => {\n const [paused, setPaused] = useState(false);\n const ref = useRef<HTMLDivElement>(null);\n const remaining = useRef(duration);\n\n // React's onPointerEnter never fires across this portal's shadow boundary.\n useEffect(() => {\n const el = ref.current;\n if (!el) {\n return;\n }\n const pause = () => setPaused(true);\n const resume = () => setPaused(false);\n el.addEventListener('pointerenter', pause);\n el.addEventListener('pointerleave', resume);\n return () => {\n el.removeEventListener('pointerenter', pause);\n el.removeEventListener('pointerleave', resume);\n };\n }, []);\n\n useEffect(() => {\n if (paused) {\n return;\n }\n const startedAt = Date.now();\n const timeout = setTimeout(() => onDismiss(toast.id), remaining.current);\n return () => {\n clearTimeout(timeout);\n remaining.current = Math.max(0, remaining.current - (Date.now() - startedAt));\n };\n }, [paused, onDismiss, toast.id]);\n\n const Icon = toast.variant === 'error' ? ErrorCircleFilled : CheckCircleFilled;\n\n return (\n // aria-hidden: the provider's live regions are the announced copy.\n <div\n ref={ref}\n aria-hidden=\"true\"\n data-toast={toast.variant}\n onClick={(event) => {\n // A portal bubbles through the React tree, to the provider's ancestors.\n event.stopPropagation();\n onDismiss(toast.id);\n }}\n className=\"pointer-events-auto flex max-w-[480px] cursor-pointer items-start gap-[8px] rounded-xl bg-[var(--malachite-tooltip-background,rgb(59,59,59))] px-[12px] py-[8px] text-[length:var(--malachite-font-size-medium,14px)] leading-[20px] text-[var(--malachite-tooltip-foreground,rgb(255,255,255))] [box-shadow:var(--malachite-tooltip-shadow,0px_0px_10px_0px_rgba(0,0,0,0.2))] animate-[malachite-toast-in_150ms_ease-out] motion-reduce:animate-none\"\n style={{ overflowWrap: 'anywhere' }}\n >\n <Icon size={16} className={`mt-[2px] shrink-0 ${ICON_COLOR[toast.variant]}`} />\n <span>{toast.message}</span>\n </div>\n );\n};\n\nexport default ToastItem;\n"],"names":["ICON_COLOR","ToastItem","toast","duration","onDismiss","paused","setPaused","useState","ref","useRef","remaining","useEffect","el","pause","resume","startedAt","timeout","Icon","ErrorCircleFilled","CheckCircleFilled","jsxs","event","jsx"],"mappings":";;;;AAMA,MAAMA,IAA2C;AAAA,EAC7C,SACI;AAAA,EACJ,OAAO;AACX,GAQMC,IAAsC,CAAC,EAAE,OAAAC,GAAO,UAAAC,GAAU,WAAAC,QAAgB;AAC5E,QAAM,CAACC,GAAQC,CAAS,IAAIC,EAAS,EAAK,GACpCC,IAAMC,EAAuB,IAAI,GACjCC,IAAYD,EAAON,CAAQ;AAGjC,EAAAQ,EAAU,MAAM;AACZ,UAAMC,IAAKJ,EAAI;AACf,QAAI,CAACI;AACD;AAEJ,UAAMC,IAAQ,MAAMP,EAAU,EAAI,GAC5BQ,IAAS,MAAMR,EAAU,EAAK;AACpC,WAAAM,EAAG,iBAAiB,gBAAgBC,CAAK,GACzCD,EAAG,iBAAiB,gBAAgBE,CAAM,GACnC,MAAM;AACT,MAAAF,EAAG,oBAAoB,gBAAgBC,CAAK,GAC5CD,EAAG,oBAAoB,gBAAgBE,CAAM;AAAA,IACjD;AAAA,EACJ,GAAG,CAAA,CAAE,GAELH,EAAU,MAAM;AACZ,QAAIN;AACA;AAEJ,UAAMU,IAAY,KAAK,IAAA,GACjBC,IAAU,WAAW,MAAMZ,EAAUF,EAAM,EAAE,GAAGQ,EAAU,OAAO;AACvE,WAAO,MAAM;AACT,mBAAaM,CAAO,GACpBN,EAAU,UAAU,KAAK,IAAI,GAAGA,EAAU,WAAW,KAAK,IAAA,IAAQK,EAAU;AAAA,IAChF;AAAA,EACJ,GAAG,CAACV,GAAQD,GAAWF,EAAM,EAAE,CAAC;AAEhC,QAAMe,IAAOf,EAAM,YAAY,UAAUgB,IAAoBC;AAE7D;AAAA;AAAA,IAEI,gBAAAC;AAAA,MAAC;AAAA,MAAA;AAAA,QACG,KAAAZ;AAAA,QACA,eAAY;AAAA,QACZ,cAAYN,EAAM;AAAA,QAClB,SAAS,CAACmB,MAAU;AAEhB,UAAAA,EAAM,gBAAA,GACNjB,EAAUF,EAAM,EAAE;AAAA,QACtB;AAAA,QACA,WAAU;AAAA,QACV,OAAO,EAAE,cAAc,WAAA;AAAA,QAEvB,UAAA;AAAA,UAAA,gBAAAoB,EAACL,GAAA,EAAK,MAAM,IAAI,WAAW,qBAAqBjB,EAAWE,EAAM,OAAO,CAAC,GAAA,CAAI;AAAA,UAC7E,gBAAAoB,EAAC,QAAA,EAAM,UAAApB,EAAM,QAAA,CAAQ;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA;AAAA;AAGjC;"}
|
package/dist/index.d.ts
CHANGED
|
@@ -2026,6 +2026,17 @@ export declare function getCurrentThemeMode(): 'light' | 'dark';
|
|
|
2026
2026
|
|
|
2027
2027
|
export declare const GlobeIcon: React.FC<IconProps_58>;
|
|
2028
2028
|
|
|
2029
|
+
/**
|
|
2030
|
+
* Steps in consecutive groups. The active step's group opens into one bar per step across
|
|
2031
|
+
* the full width, and every other group folds to a short bar. Folded bars keep a fixed width
|
|
2032
|
+
* and the open group shares what is left, so allow about 20px per other group and 12px per
|
|
2033
|
+
* step in the open group.
|
|
2034
|
+
*/
|
|
2035
|
+
export declare interface GroupedStepperProps extends StepperBaseProps {
|
|
2036
|
+
groups: StepperGroup[];
|
|
2037
|
+
steps?: never;
|
|
2038
|
+
}
|
|
2039
|
+
|
|
2029
2040
|
export declare const IconBadge: default_2.FC<IconBadgeProps>;
|
|
2030
2041
|
|
|
2031
2042
|
export declare interface IconBadgeProps {
|
|
@@ -3660,7 +3671,8 @@ export declare interface ModalProps {
|
|
|
3660
3671
|
children: default_2.ReactNode;
|
|
3661
3672
|
}
|
|
3662
3673
|
|
|
3663
|
-
|
|
3674
|
+
/** `fullscreen` fills the window, inside the overlay's margin, in both directions. */
|
|
3675
|
+
export declare type ModalSize = 'small' | 'medium' | 'large' | 'fullscreen';
|
|
3664
3676
|
|
|
3665
3677
|
export declare type ModalSurface = 'card' | 'none';
|
|
3666
3678
|
|
|
@@ -4363,6 +4375,11 @@ export declare type RightSideContent = {
|
|
|
4363
4375
|
type?: 'link';
|
|
4364
4376
|
text: string;
|
|
4365
4377
|
href: string;
|
|
4378
|
+
} | {
|
|
4379
|
+
/** An action on the form itself, such as showing a panel of help beside it. */
|
|
4380
|
+
type: 'button';
|
|
4381
|
+
text: string;
|
|
4382
|
+
onClick: () => void;
|
|
4366
4383
|
};
|
|
4367
4384
|
|
|
4368
4385
|
declare const Row: default_2.FC<KeyValueListRowProps>;
|
|
@@ -4723,6 +4740,18 @@ export declare interface SidebarSwitcherProps extends SidebarButtonAttributes {
|
|
|
4723
4740
|
*/
|
|
4724
4741
|
export declare const SidePanel: default_2.FC<SidePanelProps>;
|
|
4725
4742
|
|
|
4743
|
+
export declare interface SidePanelAside {
|
|
4744
|
+
/** Shown in the aside's header, and names the column for assistive tech. */
|
|
4745
|
+
title: string;
|
|
4746
|
+
/** Content pinned right in the aside's header (e.g. a stepper). */
|
|
4747
|
+
headerExtra?: default_2.ReactNode;
|
|
4748
|
+
/** Pinned at the bottom, level with the panel's footer. */
|
|
4749
|
+
footer?: default_2.ReactNode;
|
|
4750
|
+
/** Whether the aside's body gets default padding, as the panel's `bodyPadding`. Default: true. */
|
|
4751
|
+
bodyPadding?: boolean;
|
|
4752
|
+
children: default_2.ReactNode;
|
|
4753
|
+
}
|
|
4754
|
+
|
|
4726
4755
|
export declare const SidePanelNavFooter: default_2.FC<SidePanelNavFooterProps>;
|
|
4727
4756
|
|
|
4728
4757
|
export declare interface SidePanelNavFooterProps {
|
|
@@ -4775,6 +4804,11 @@ export declare interface SidePanelProps {
|
|
|
4775
4804
|
}[];
|
|
4776
4805
|
/** Routes root-relative links in `callouts` through the app's router; see `MarkdownBlock`. */
|
|
4777
4806
|
renderLink?: RenderLink;
|
|
4807
|
+
/** A full-height column opened to the left, as wide again as `maxWidth`. The panel's own
|
|
4808
|
+
* header, body and footer keep their width and place; the aside's header and footer line
|
|
4809
|
+
* up with them. The aside is never narrower than 360px: a window too narrow for both
|
|
4810
|
+
* columns scrolls the panel sideways. */
|
|
4811
|
+
aside?: SidePanelAside;
|
|
4778
4812
|
children: default_2.ReactNode;
|
|
4779
4813
|
}
|
|
4780
4814
|
|
|
@@ -4984,12 +5018,10 @@ export declare type StatusPillTone = 'success' | 'neutral' | 'warning' | 'error'
|
|
|
4984
5018
|
|
|
4985
5019
|
export declare const StepCurrentIcon: React.FC<IconProps_104>;
|
|
4986
5020
|
|
|
4987
|
-
export declare const Stepper: default_2.FC<StepperProps>;
|
|
5021
|
+
export declare const Stepper: default_2.FC<StepperProps | GroupedStepperProps>;
|
|
4988
5022
|
|
|
4989
|
-
|
|
4990
|
-
/**
|
|
4991
|
-
steps: number;
|
|
4992
|
-
/** Active step index (0-based). */
|
|
5023
|
+
declare interface StepperBaseProps {
|
|
5024
|
+
/** Active step index (0-based), counted across every group when grouped. */
|
|
4993
5025
|
active: number;
|
|
4994
5026
|
/** When provided, each step becomes a clickable control. */
|
|
4995
5027
|
onChange?: (index: number) => void;
|
|
@@ -4998,6 +5030,18 @@ export declare interface StepperProps {
|
|
|
4998
5030
|
'aria-labelledby'?: string;
|
|
4999
5031
|
}
|
|
5000
5032
|
|
|
5033
|
+
export declare interface StepperGroup {
|
|
5034
|
+
/** Names the group's folded mark, e.g. a section title. */
|
|
5035
|
+
label: string;
|
|
5036
|
+
steps: number;
|
|
5037
|
+
}
|
|
5038
|
+
|
|
5039
|
+
export declare interface StepperProps extends StepperBaseProps {
|
|
5040
|
+
/** Total number of steps. */
|
|
5041
|
+
steps: number;
|
|
5042
|
+
groups?: never;
|
|
5043
|
+
}
|
|
5044
|
+
|
|
5001
5045
|
export declare interface StepTabItem {
|
|
5002
5046
|
key: string;
|
|
5003
5047
|
label: string;
|