@allxsmith/bestax-bulma 5.67.2 → 5.67.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bestax.css +1 -1
- package/dist/bestax.css.map +1 -1
- package/dist/esm/components/Card.js +3 -14
- package/dist/esm/components/Card.js.map +1 -1
- package/dist/esm/components/Command.js +14 -3
- package/dist/esm/components/Command.js.map +1 -1
- package/dist/esm/components/ContextMenu.js +10 -10
- package/dist/esm/components/ContextMenu.js.map +1 -1
- package/dist/esm/components/Dialog.js +7 -3
- package/dist/esm/components/Dialog.js.map +1 -1
- package/dist/esm/components/HoverCard.js +2 -2
- package/dist/esm/components/HoverCard.js.map +1 -1
- package/dist/esm/components/Menu.js +2 -1
- package/dist/esm/components/Menu.js.map +1 -1
- package/dist/esm/components/Modal.js +11 -9
- package/dist/esm/components/Modal.js.map +1 -1
- package/dist/esm/components/Popover.js +2 -2
- package/dist/esm/components/Popover.js.map +1 -1
- package/dist/esm/components/Tabs.js +4 -3
- package/dist/esm/components/Tabs.js.map +1 -1
- package/dist/esm/elements/CopyButton.js +6 -3
- package/dist/esm/elements/CopyButton.js.map +1 -1
- package/dist/esm/elements/IconButton.js +3 -2
- package/dist/esm/elements/IconButton.js.map +1 -1
- package/dist/esm/elements/Stat.js +3 -2
- package/dist/esm/elements/Stat.js.map +1 -1
- package/dist/esm/form/ColorInputBase.js.map +1 -1
- package/dist/esm/form/Combobox.js +2 -2
- package/dist/esm/form/Combobox.js.map +1 -1
- package/dist/esm/form/DateInputBase.js.map +1 -1
- package/dist/esm/form/DateRangeInputBase.js.map +1 -1
- package/dist/esm/form/DateTimeInputBase.js.map +1 -1
- package/dist/esm/form/Field.js +6 -6
- package/dist/esm/form/Field.js.map +1 -1
- package/dist/esm/form/TimeInputBase.js.map +1 -1
- package/dist/esm/form/TreeSelect.js.map +1 -1
- package/dist/esm/form/_pickerInternals/Calendar.js +19 -12
- package/dist/esm/form/_pickerInternals/Calendar.js.map +1 -1
- package/dist/esm/form/_pickerInternals/PickerPopover.js +3 -3
- package/dist/esm/form/_pickerInternals/PickerPopover.js.map +1 -1
- package/dist/esm/helpers/Config.js.map +1 -1
- package/dist/esm/helpers/elementType.js +42 -0
- package/dist/esm/helpers/elementType.js.map +1 -0
- package/dist/esm/helpers/portal.js +57 -1
- package/dist/esm/helpers/portal.js.map +1 -1
- package/dist/esm/helpers/useAnchoredPosition.js +23 -12
- package/dist/esm/helpers/useAnchoredPosition.js.map +1 -1
- package/dist/esm/helpers/withSubComponents.js +1 -1
- package/dist/esm/helpers/withSubComponents.js.map +1 -1
- package/dist/esm/layout/AppFrame.js +4 -3
- package/dist/esm/layout/AppFrame.js.map +1 -1
- package/dist/esm/layout/Splitter.js +3 -2
- package/dist/esm/layout/Splitter.js.map +1 -1
- package/dist/extras.css +1 -1
- package/dist/extras.css.map +1 -1
- package/dist/index.cjs +196 -81
- package/dist/index.cjs.map +1 -1
- package/dist/types/components/HoverCard.d.ts +9 -8
- package/dist/types/components/Popover.d.ts +6 -5
- package/dist/types/form/ColorInputBase.d.ts +2 -1
- package/dist/types/form/Combobox.d.ts +6 -7
- package/dist/types/form/DateInputBase.d.ts +6 -2
- package/dist/types/form/DateRangeInputBase.d.ts +6 -2
- package/dist/types/form/DateTimeInputBase.d.ts +6 -2
- package/dist/types/form/TimeInputBase.d.ts +2 -1
- package/dist/types/form/TreeSelect.d.ts +1 -1
- package/dist/types/helpers/Config.d.ts +6 -3
- package/dist/types/helpers/elementType.d.ts +20 -0
- package/dist/types/helpers/portal.d.ts +44 -0
- package/dist/types/helpers/useAnchoredPosition.d.ts +3 -1
- package/dist/types/helpers/withSubComponents.d.ts +1 -1
- package/dist/types-cjs/components/HoverCard.d.ts +9 -8
- package/dist/types-cjs/components/Popover.d.ts +6 -5
- package/dist/types-cjs/form/ColorInputBase.d.ts +2 -1
- package/dist/types-cjs/form/Combobox.d.ts +6 -7
- package/dist/types-cjs/form/DateInputBase.d.ts +6 -2
- package/dist/types-cjs/form/DateRangeInputBase.d.ts +6 -2
- package/dist/types-cjs/form/DateTimeInputBase.d.ts +6 -2
- package/dist/types-cjs/form/TimeInputBase.d.ts +2 -1
- package/dist/types-cjs/form/TreeSelect.d.ts +1 -1
- package/dist/types-cjs/helpers/Config.d.ts +6 -3
- package/dist/types-cjs/helpers/elementType.d.ts +20 -0
- package/dist/types-cjs/helpers/portal.d.ts +44 -0
- package/dist/types-cjs/helpers/useAnchoredPosition.d.ts +3 -1
- package/dist/types-cjs/helpers/withSubComponents.d.ts +1 -1
- package/dist/versions/bestax-no-dark-mode.css +1 -1
- package/dist/versions/bestax-no-dark-mode.css.map +1 -1
- package/dist/versions/bestax-no-helpers-prefixed.css +1 -1
- package/dist/versions/bestax-no-helpers-prefixed.css.map +1 -1
- package/dist/versions/bestax-no-helpers.css +1 -1
- package/dist/versions/bestax-no-helpers.css.map +1 -1
- package/dist/versions/bestax-prefixed.css +1 -1
- package/dist/versions/bestax-prefixed.css.map +1 -1
- package/package.json +1 -1
- package/src/scss/components/_menu.scss +16 -2
- package/src/scss/form/_dateinput.scss +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Tabs.js","sources":["../../../src/components/Tabs.tsx"],"sourcesContent":["import React, {\n createContext,\n useContext,\n useEffect,\n useId,\n useRef,\n useState,\n useCallback,\n} from 'react';\nimport { classNames, usePrefixedClassNames } from '../helpers/classNames';\nimport { withSubComponents } from '../helpers/withSubComponents';\nimport { useBulmaClasses, BulmaClassesProps } from '../helpers/useBulmaClasses';\nimport { warnDeprecatedColorProp } from '../helpers/colorDeprecations';\nimport { Icon } from '../elements/Icon';\n\n// ---------------------------------------------------------------------------\n// Context\n// ---------------------------------------------------------------------------\n\ninterface TabRecord {\n index: number;\n id: string;\n disabled: boolean;\n}\n\ninterface PanelRecord {\n index: number;\n id: string;\n}\n\ninterface TabsContextValue {\n activeTab: number;\n setActiveTab: (index: number) => void;\n /** Prefix for the ids the tabs and panels generate. */\n baseId: string;\n /** The tab that holds `tabIndex={0}`, or `null` when every tab is disabled. */\n tabStop: number | null;\n /** Records which tab has focus, so the tab stop can follow it. */\n setFocusedTab: (index: number | null) => void;\n /** The id of the mounted tab for `index`, for its panel's `aria-labelledby`. */\n tabId: (index: number) => string | undefined;\n /** The id of the mounted panel for `index`, for its tab's `aria-controls`. */\n panelId: (index: number) => string | undefined;\n registerTab: (record: TabRecord) => () => void;\n registerPanel: (record: PanelRecord) => () => void;\n orientation: 'horizontal' | 'vertical';\n}\n\nconst TabsContext = createContext<TabsContextValue | null>(null);\n\nfunction useTabsContext(): TabsContextValue | null {\n return useContext(TabsContext);\n}\n\n/**\n * The tabs and panels currently mounted under one `Tabs`. Each registers from\n * an effect and removes itself on cleanup, so the links between them only\n * ever name elements that exist, wherever in the tree the panels sit.\n */\nfunction useRegistry<T>(): [T[], (record: T) => () => void] {\n const [records, setRecords] = useState<T[]>([]);\n const register = useCallback((record: T) => {\n setRecords(prev => [...prev, record]);\n return () => setRecords(prev => prev.filter(r => r !== record));\n }, []);\n return [records, register];\n}\n\n/**\n * The list a tab belongs to: its `role=\"tablist\"` ancestor, so an element\n * wrapped around a tab doesn't cut it off from the others, or its parent when\n * it sits outside a tab list.\n */\nfunction tabListOf(tab: HTMLElement): HTMLElement {\n return (tab.closest('[role=\"tablist\"]') ?? tab.parentElement) as HTMLElement;\n}\n\n/**\n * The tab a key press moves focus to, found among the tabs of the same list\n * in DOM order, skipping disabled ones and wrapping at either end.\n * `undefined` for a key the tab list does not handle, or when no enabled tab\n * is left to go to.\n */\nfunction findTargetTab(\n current: HTMLElement,\n key: string,\n vertical: boolean\n): HTMLElement | undefined {\n const tabs = Array.from(\n tabListOf(current).querySelectorAll<HTMLElement>('[role=\"tab\"]')\n );\n const isEnabled = (el: HTMLElement) =>\n el.getAttribute('aria-disabled') !== 'true';\n\n if (key === 'Home') return tabs.find(isEnabled);\n if (key === 'End') return [...tabs].reverse().find(isEnabled);\n\n let step: number;\n if (key === 'ArrowRight' || (vertical && key === 'ArrowDown')) step = 1;\n else if (key === 'ArrowLeft' || (vertical && key === 'ArrowUp')) step = -1;\n else return undefined;\n\n const start = tabs.indexOf(current);\n for (let i = 1; i <= tabs.length; i++) {\n const candidate =\n tabs[(((start + step * i) % tabs.length) + tabs.length) % tabs.length];\n if (isEnabled(candidate)) return candidate;\n }\n return undefined;\n}\n\n// ---------------------------------------------------------------------------\n// Tabs (root)\n// ---------------------------------------------------------------------------\n\n/**\n * Props for the Tabs component.\n */\nexport interface TabsProps\n extends\n Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange'>,\n Omit<BulmaClassesProps, 'color' | 'backgroundColor'> {\n /** Tab alignment. */\n align?: 'centered' | 'right' | 'left';\n /** Tab size. */\n size?: 'small' | 'medium' | 'large';\n /** Tabs expand to fill the horizontal space. */\n isFullwidth?: boolean;\n /** Tabs expand to fill the horizontal space. @deprecated Use `isFullwidth` instead — `isFullwidth` wins if both are set. */\n isFullWidth?: boolean;\n /** Tabs expand to fill the horizontal space. @deprecated Use `isFullwidth` instead — `isFullwidth` wins if both are set. */\n fullwidth?: boolean;\n /** Tabs use the boxed style. */\n boxed?: boolean;\n /** Tabs use the toggle style. */\n toggle?: boolean;\n /** Tabs use the rounded toggle style (only with `toggle`). */\n rounded?: boolean;\n /**\n * Bulma color for tab underlines and active state (renders `is-<color>`).\n *\n * Bulma ships no tabs color CSS, so this prop has never had a visual effect\n * for any value. Passing it logs a console warning in development.\n * @deprecated No `.tabs.is-<color>` CSS exists; the prop renders unstyled\n * and will be removed in the next major version.\n */\n color?:\n | 'primary'\n | 'link'\n | 'info'\n | 'success'\n | 'warning'\n | 'danger'\n | 'black'\n | 'dark'\n | 'light'\n | 'white';\n /** Controlled active tab index. */\n value?: number;\n /**\n * Callback when active tab changes. Called with the tab's `index` when a\n * `Tabs.Tab` is activated: clicked, or Enter or Space pressed on it.\n * Moving focus with the arrow keys, Home or End does not call it, because\n * activation is manual: focus moves freely and the selected tab changes\n * only when the user picks one.\n */\n onChange?: (index: number) => void;\n /** Initial active tab index (uncontrolled). */\n defaultValue?: number;\n /**\n * Renders tabs vertically. With a `Tabs.Content`, the tab list is also\n * marked `aria-orientation=\"vertical\"` and ArrowUp and ArrowDown move focus\n * between tabs, alongside ArrowLeft and ArrowRight (the layout stacks\n * horizontally on mobile).\n */\n vertical?: boolean;\n /** Side placement when `vertical` is true. */\n side?: 'left' | 'right';\n /** Makes tabs take up the full width equally. */\n expanded?: boolean;\n /** Additional CSS classes. */\n className?: string;\n /** Tab list and tab items. */\n children?: React.ReactNode;\n}\n\n/**\n * The `Tabs` component provides flexible and fully-featured Bulma tab navigation for your Bulma React UI.\n *\n * Built from `Tabs.Tab`, the tabs follow the WAI-ARIA tabs pattern with\n * manual activation: the tab list is a single tab stop, the arrow keys, Home\n * and End move focus between tabs (skipping disabled ones), and Enter or\n * Space activates the focused tab. Each tab names its `Tabs.Content.Item` in\n * `aria-controls`, and each panel names its tab in `aria-labelledby`. Built\n * from `Tabs.Item` with links inside, the tabs stay plain Bulma navigation.\n *\n * @function\n * @param {TabsProps} props - Props for the Tabs component.\n * @returns {JSX.Element} The rendered tabs component.\n * @see {@link https://bulma.io/documentation/components/tabs/ | Bulma Tabs documentation}\n *\n * @example\n * // Basic tabs\n * <Tabs>\n * <Tabs.List>\n * <Tabs.Tab index={0}>Pictures</Tabs.Tab>\n * <Tabs.Tab index={1}>Music</Tabs.Tab>\n * </Tabs.List>\n * <Tabs.Content>\n * <Tabs.Content.Item index={0}>Pictures content</Tabs.Content.Item>\n * <Tabs.Content.Item index={1}>Music content</Tabs.Content.Item>\n * </Tabs.Content>\n * </Tabs>\n */\nconst TabsComponent: React.FC<TabsProps> = ({\n align,\n size,\n isFullwidth,\n isFullWidth,\n fullwidth,\n boxed,\n toggle,\n rounded,\n color,\n value,\n onChange,\n defaultValue = 0,\n vertical,\n side,\n expanded,\n className,\n children,\n ...props\n}) => {\n warnDeprecatedColorProp(\n 'Tabs',\n color,\n 'Remove the prop; no replacement exists.'\n );\n\n const { bulmaHelperClasses, rest } = useBulmaClasses({ ...props });\n\n // Controlled vs uncontrolled state\n const isControlled = value !== undefined;\n const [internalTab, setInternalTab] = useState(defaultValue);\n const activeTab = isControlled ? value : internalTab;\n\n const setActiveTab = useCallback(\n (index: number) => {\n if (!isControlled) {\n setInternalTab(index);\n }\n onChange?.(index);\n },\n [isControlled, onChange]\n );\n\n const baseId = useId();\n const [tabs, registerTab] = useRegistry<TabRecord>();\n const [panels, registerPanel] = useRegistry<PanelRecord>();\n const [focusedTab, setFocusedTab] = useState<number | null>(null);\n\n // One tab stop for the whole list (roving tabindex). While focus is inside\n // the list the stop follows it, so Tab and Shift+Tab leave the list from\n // whichever tab has focus; once focus leaves, the stop is the selected tab\n // again. A disabled tab never holds it, and when the selected tab is\n // disabled or missing the enabled tab with the lowest index does, so the\n // list can always be reached. Before the tabs register (the server render\n // and the first client render), the selected tab holds it. The registry is\n // in the order the tabs last registered, which a tab changing its\n // `disabled` or `id` reshuffles, hence the sort.\n const enabledTabs = tabs\n .filter(t => !t.disabled)\n .sort((a, b) => a.index - b.index);\n const canHoldStop = (index: number | null) =>\n enabledTabs.some(t => t.index === index);\n let tabStop: number | null;\n if (canHoldStop(focusedTab)) tabStop = focusedTab;\n else if (tabs.length === 0 || canHoldStop(activeTab)) tabStop = activeTab;\n else tabStop = enabledTabs.length > 0 ? enabledTabs[0].index : null;\n\n // Check if children include TabsContent\n const childArray = React.Children.toArray(children);\n const hasContent = childArray.some(\n child => React.isValidElement(child) && child.type === TabsContent\n );\n\n const contextValue: TabsContextValue = {\n activeTab,\n setActiveTab,\n baseId,\n tabStop,\n setFocusedTab,\n tabId: index => tabs.find(t => t.index === index)?.id,\n panelId: index => panels.find(p => p.index === index)?.id,\n registerTab,\n registerPanel,\n orientation: hasContent && vertical ? 'vertical' : 'horizontal',\n };\n\n // Build classes for the .tabs div\n const tabsClasses = usePrefixedClassNames('tabs', {\n [`is-${align}`]: align,\n [`is-${size}`]: size,\n [`is-${color}`]: color,\n 'is-fullwidth': isFullwidth ?? isFullWidth ?? fullwidth,\n 'is-boxed': boxed,\n 'is-toggle': toggle,\n 'is-toggle-rounded': rounded,\n });\n\n // Hoisted unconditionally to respect rules-of-hooks. Modifiers gate themselves\n // via their truthy values — `tabs-root` is always prefixed, modifiers only\n // apply in the vertical-with-content branch.\n const rootClasses = usePrefixedClassNames('tabs-root', {\n 'is-vertical': hasContent && vertical,\n 'is-right': hasContent && vertical && side === 'right',\n 'is-expanded': hasContent && vertical && expanded,\n });\n\n if (hasContent && vertical) {\n const combinedRootClasses = classNames(\n rootClasses,\n bulmaHelperClasses,\n className\n );\n\n // Split children into list-like and content children\n const listChildren: React.ReactNode[] = [];\n const contentChildren: React.ReactNode[] = [];\n childArray.forEach(child => {\n if (React.isValidElement(child) && child.type === TabsContent) {\n contentChildren.push(child);\n } else {\n listChildren.push(child);\n }\n });\n\n return (\n <TabsContext.Provider value={contextValue}>\n <div className={combinedRootClasses} {...rest}>\n <div className={tabsClasses}>{listChildren}</div>\n {contentChildren}\n </div>\n </TabsContext.Provider>\n );\n }\n\n if (hasContent) {\n const combinedRootClasses = classNames(\n rootClasses,\n bulmaHelperClasses,\n className\n );\n\n const listChildren: React.ReactNode[] = [];\n const contentChildren: React.ReactNode[] = [];\n childArray.forEach(child => {\n if (React.isValidElement(child) && child.type === TabsContent) {\n contentChildren.push(child);\n } else {\n listChildren.push(child);\n }\n });\n\n return (\n <TabsContext.Provider value={contextValue}>\n <div className={combinedRootClasses} {...rest}>\n <div className={tabsClasses}>{listChildren}</div>\n {contentChildren}\n </div>\n </TabsContext.Provider>\n );\n }\n\n // No content children — backward compatible single .tabs div\n const combinedClasses = classNames(\n tabsClasses,\n bulmaHelperClasses,\n className\n );\n\n return (\n <TabsContext.Provider value={contextValue}>\n <div className={combinedClasses} {...rest}>\n {children}\n </div>\n </TabsContext.Provider>\n );\n};\n\n// ---------------------------------------------------------------------------\n// TabList\n// ---------------------------------------------------------------------------\n\n/**\n * Props for the TabList component.\n */\nexport interface TabListProps extends React.HTMLAttributes<HTMLUListElement> {\n /** Additional CSS classes. */\n className?: string;\n /** Tab elements. */\n children?: React.ReactNode;\n}\n\n/**\n * The `<ul>` container for tab items.\n *\n * @function\n * @param {TabListProps} props - Props for the TabList component.\n * @returns {JSX.Element} The rendered tab list.\n */\nexport const TabList: React.FC<TabListProps> = ({\n className,\n children,\n ...props\n}) => {\n const ctx = useTabsContext();\n return (\n <ul\n role=\"tablist\"\n aria-orientation={\n ctx?.orientation === 'vertical' ? 'vertical' : undefined\n }\n className={classNames(className) || undefined}\n {...props}\n >\n {children}\n </ul>\n );\n};\n\n// ---------------------------------------------------------------------------\n// Tab (new — context-aware)\n// ---------------------------------------------------------------------------\n\ntype IconLibrary = 'fa' | 'mdi' | 'ion' | 'material-icons' | 'material-symbols';\n\n/**\n * Props for the Tab component.\n * @extraProp {string} [id] - The tab's id, which its panel names in `aria-labelledby`. Generated inside a `Tabs` when omitted; a passed `id` is used and the panel follows it.\n * @extraProp {React.KeyboardEventHandler<HTMLLIElement>} [onKeyDown] - Called before the tab handles the key; call `event.preventDefault()` to stop the tab handling it.\n */\nexport interface TabProps extends Omit<\n React.LiHTMLAttributes<HTMLLIElement>,\n 'onClick'\n> {\n /** **Required.** Tab index for matching with content. */\n index: number;\n /**\n * Disables the tab. A disabled tab is marked `aria-disabled`, cannot be\n * activated, is skipped by the arrow keys, Home and End, and never holds\n * the tab list's tab stop. The bestax extras styles also dim it and turn\n * off its hover.\n */\n disabled?: boolean;\n /** Icon name for the tab. */\n icon?: string;\n /** Icon library to use. */\n iconLibrary?: IconLibrary;\n /** Icon style variant (e.g., 'solid', 'outlined', 'rounded'). */\n iconVariant?: string;\n /** Size of the tab icon. */\n iconSize?: 'small' | 'medium' | 'large';\n /** Additional icon modifiers. */\n iconFeatures?: string | string[];\n /** Additional CSS classes. */\n className?: string;\n /** Tab label content. */\n children?: React.ReactNode;\n}\n\n/**\n * Individual tab button. Consumes Tabs context for active state management.\n * Renders `<a>` internally — consumers provide only the label text/children.\n * Inside a `Tabs` it takes part in the tab list's keyboard support and links\n * to the `Tabs.Content.Item` with the same `index`.\n *\n * @function\n * @param {TabProps} props - Props for the Tab component.\n * @returns {JSX.Element} The rendered tab.\n */\nexport const Tab: React.FC<TabProps> = ({\n index,\n disabled,\n icon,\n iconLibrary,\n iconVariant,\n iconSize = 'small',\n iconFeatures,\n className,\n children,\n id,\n onKeyDown,\n onFocus,\n onBlur,\n ...props\n}) => {\n const ctx = useTabsContext();\n const isActive = ctx ? ctx.activeTab === index : false;\n const anchorRef = useRef<HTMLAnchorElement>(null);\n\n const activeClass = usePrefixedClassNames({ 'is-active': isActive });\n\n const tabId = id ?? (ctx ? `${ctx.baseId}-tab-${index}` : undefined);\n const registerTab = ctx?.registerTab;\n useEffect(() => {\n if (!registerTab || tabId === undefined) return undefined;\n return registerTab({ index, id: tabId, disabled: !!disabled });\n }, [registerTab, index, tabId, disabled]);\n\n // Outside a Tabs there is no list to rove, so every enabled tab stays in\n // the page tab order, as it always did.\n let tabIndex: number;\n if (disabled) tabIndex = -1;\n else if (ctx) tabIndex = ctx.tabStop === index ? 0 : -1;\n else tabIndex = 0;\n\n const handleClick = useCallback(\n (e: React.MouseEvent) => {\n e.preventDefault();\n if (!disabled && ctx) {\n ctx.setActiveTab(index);\n }\n },\n [disabled, ctx, index]\n );\n\n const handleKeyDown = (e: React.KeyboardEvent<HTMLLIElement>) => {\n onKeyDown?.(e);\n // A key on something inside the tab is that element's to answer.\n if (\n e.defaultPrevented ||\n e.target !== e.currentTarget ||\n e.altKey ||\n e.ctrlKey ||\n e.metaKey\n ) {\n return;\n }\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n // Activate through a click on the inner <a>, the element a pointer\n // clicks, so click listeners see keyboard activation the way they see\n // a native button's.\n (anchorRef.current as HTMLAnchorElement).click();\n return;\n }\n const target = findTargetTab(\n e.currentTarget,\n e.key,\n ctx?.orientation === 'vertical'\n );\n if (target) {\n e.preventDefault();\n target.focus();\n }\n };\n\n const handleFocus = (e: React.FocusEvent<HTMLLIElement>) => {\n onFocus?.(e);\n ctx?.setFocusedTab(index);\n };\n\n const handleBlur = (e: React.FocusEvent<HTMLLIElement>) => {\n onBlur?.(e);\n if (!tabListOf(e.currentTarget).contains(e.relatedTarget)) {\n ctx?.setFocusedTab(null);\n }\n };\n\n return (\n <li\n className={classNames(activeClass, className) || undefined}\n role=\"tab\"\n id={tabId}\n aria-selected={isActive}\n aria-controls={ctx?.panelId(index)}\n aria-disabled={disabled || undefined}\n tabIndex={tabIndex}\n onKeyDown={handleKeyDown}\n onFocus={handleFocus}\n onBlur={handleBlur}\n {...props}\n >\n <a\n ref={anchorRef}\n onClick={handleClick}\n aria-disabled={disabled || undefined}\n >\n {icon && (\n <Icon\n name={icon}\n library={iconLibrary}\n variant={iconVariant}\n size={iconSize}\n features={iconFeatures}\n />\n )}\n {children && <span>{children}</span>}\n </a>\n </li>\n );\n};\n\n// ---------------------------------------------------------------------------\n// TabItem (backward-compatible — no context)\n// ---------------------------------------------------------------------------\n\n/**\n * Props for the TabItem component (backward-compatible).\n */\nexport interface TabItemProps extends React.LiHTMLAttributes<HTMLLIElement> {\n /** Whether the tab is active. */\n active?: boolean;\n /** Additional CSS classes. */\n className?: string;\n /** Tab content. */\n children?: React.ReactNode;\n /** Click handler. */\n onClick?: React.MouseEventHandler<HTMLLIElement>;\n}\n\n/**\n * Each tab; accepts `active`, `onClick`, etc.\n *\n * @function\n * @param {TabItemProps} props - Props for the TabItem component.\n * @returns {JSX.Element} The rendered tab item.\n * @deprecated Use `Tabs.Tab` with an `index` prop instead.\n */\nexport const TabItem: React.FC<TabItemProps> = ({\n active,\n className,\n children,\n onClick,\n ...props\n}) => (\n <li\n className={\n classNames({ [usePrefixedClassNames('is-active')]: active }, className) ||\n undefined\n }\n onClick={onClick}\n {...props}\n >\n {children}\n </li>\n);\n\n// ---------------------------------------------------------------------------\n// TabsContent\n// ---------------------------------------------------------------------------\n\n/**\n * Props for the TabsContent component.\n */\nexport interface TabsContentProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Additional CSS classes. */\n className?: string;\n /** TabContentItem elements. */\n children?: React.ReactNode;\n}\n\n/**\n * Container for tab content panels. No custom props beyond `children` and standard `<div>` HTML attributes. Applies the `.tabs-content` class.\n *\n * @function\n * @param {TabsContentProps} props - Props for the TabsContent component.\n * @returns {JSX.Element} The rendered tabs content wrapper.\n */\nconst TabsContentComponent: React.FC<TabsContentProps> = ({\n className,\n children,\n ...props\n}) => {\n const contentClass = usePrefixedClassNames('tabs-content');\n return (\n <div className={classNames(contentClass, className)} {...props}>\n {children}\n </div>\n );\n};\n\n// ---------------------------------------------------------------------------\n// TabContentItem\n// ---------------------------------------------------------------------------\n\n/**\n * Props for the TabContentItem component.\n * @extraProp {string} [id] - The panel's id, which its tab names in `aria-controls`. Generated inside a `Tabs` when omitted; a passed `id` is used and the tab follows it.\n * @extraProp {number} [tabIndex] - The panel is not focusable by default. Pass `0` when it holds nothing focusable, so Tab moves from the tab list into it.\n */\nexport interface TabContentItemProps extends React.HTMLAttributes<HTMLDivElement> {\n /** **Required.** Tab index for matching with content. */\n index: number;\n /** Additional CSS classes. */\n className?: string;\n /** Panel content. */\n children?: React.ReactNode;\n}\n\n/**\n * Individual content panel. Shows/hides based on active tab from context.\n *\n * @function\n * @param {TabContentItemProps} props - Props for the TabContentItem component.\n * @returns {JSX.Element} The rendered tab content panel.\n */\nexport const TabContentItem: React.FC<TabContentItemProps> = ({\n index,\n className,\n children,\n id,\n ...props\n}) => {\n const ctx = useTabsContext();\n const isActive = ctx ? ctx.activeTab === index : false;\n\n const itemClass = usePrefixedClassNames('tabs-content-item', {\n 'is-active': isActive,\n });\n\n const panelId = id ?? (ctx ? `${ctx.baseId}-panel-${index}` : undefined);\n const registerPanel = ctx?.registerPanel;\n useEffect(() => {\n if (!registerPanel || panelId === undefined) return undefined;\n return registerPanel({ index, id: panelId });\n }, [registerPanel, index, panelId]);\n\n return (\n <div\n className={classNames(itemClass, className)}\n role=\"tabpanel\"\n id={panelId}\n aria-labelledby={ctx?.tabId(index)}\n aria-hidden={!isActive}\n {...props}\n >\n {children}\n </div>\n );\n};\n\n// ---------------------------------------------------------------------------\n// Static property attachment\n// ---------------------------------------------------------------------------\n\nexport const TabsContent = withSubComponents(TabsContentComponent, {\n Item: TabContentItem,\n});\n\nexport const Tabs = withSubComponents(\n TabsComponent,\n {\n List: TabList,\n Tab,\n Item: TabItem,\n Content: TabsContent,\n },\n 'Tabs'\n);\n\nexport default Tabs;\n"],"names":["_jsx","_jsxs"],"mappings":";;;;;;;;;;AAgDA,MAAM,WAAW,GAAG,aAAa,CAA0B,IAAI,CAAC;AAEhE,SAAS,cAAc,GAAA;AACrB,IAAA,OAAO,UAAU,CAAC,WAAW,CAAC;AAChC;AAEA;;;;AAIG;AACH,SAAS,WAAW,GAAA;IAClB,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAM,EAAE,CAAC;AAC/C,IAAA,MAAM,QAAQ,GAAG,WAAW,CAAC,CAAC,MAAS,KAAI;AACzC,QAAA,UAAU,CAAC,IAAI,IAAI,CAAC,GAAG,IAAI,EAAE,MAAM,CAAC,CAAC;QACrC,OAAO,MAAM,UAAU,CAAC,IAAI,IAAI,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,KAAK,MAAM,CAAC,CAAC;IACjE,CAAC,EAAE,EAAE,CAAC;AACN,IAAA,OAAO,CAAC,OAAO,EAAE,QAAQ,CAAC;AAC5B;AAEA;;;;AAIG;AACH,SAAS,SAAS,CAAC,GAAgB,EAAA;AACjC,IAAA,QAAQ,GAAG,CAAC,OAAO,CAAC,kBAAkB,CAAC,IAAI,GAAG,CAAC,aAAa;AAC9D;AAEA;;;;;AAKG;AACH,SAAS,aAAa,CACpB,OAAoB,EACpB,GAAW,EACX,QAAiB,EAAA;AAEjB,IAAA,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CACrB,SAAS,CAAC,OAAO,CAAC,CAAC,gBAAgB,CAAc,cAAc,CAAC,CACjE;AACD,IAAA,MAAM,SAAS,GAAG,CAAC,EAAe,KAChC,EAAE,CAAC,YAAY,CAAC,eAAe,CAAC,KAAK,MAAM;IAE7C,IAAI,GAAG,KAAK,MAAM;AAAE,QAAA,OAAO,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC;IAC/C,IAAI,GAAG,KAAK,KAAK;AAAE,QAAA,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC;AAE7D,IAAA,IAAI,IAAY;IAChB,IAAI,GAAG,KAAK,YAAY,KAAK,QAAQ,IAAI,GAAG,KAAK,WAAW,CAAC;QAAE,IAAI,GAAG,CAAC;SAClE,IAAI,GAAG,KAAK,WAAW,KAAK,QAAQ,IAAI,GAAG,KAAK,SAAS,CAAC;QAAE,IAAI,GAAG,EAAE;;AACrE,QAAA,OAAO,SAAS;IAErB,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC;AACnC,IAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;AACrC,QAAA,MAAM,SAAS,GACb,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG,IAAI,GAAG,CAAC,IAAI,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,MAAM,CAAC;QACxE,IAAI,SAAS,CAAC,SAAS,CAAC;AAAE,YAAA,OAAO,SAAS;IAC5C;AACA,IAAA,OAAO,SAAS;AAClB;AA6EA;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BG;AACH,MAAM,aAAa,GAAwB,CAAC,EAC1C,KAAK,EACL,IAAI,EACJ,WAAW,EACX,WAAW,EACX,SAAS,EACT,KAAK,EACL,MAAM,EACN,OAAO,EACP,KAAK,EACL,KAAK,EACL,QAAQ,EACR,YAAY,GAAG,CAAC,EAChB,QAAQ,EACR,IAAI,EACJ,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,GAAG,KAAK,EACT,KAAI;AACH,IAAA,uBAAuB,CACrB,MAAM,EACN,KAAK,EACL,yCAAyC,CAC1C;AAED,IAAA,MAAM,EAAE,kBAAkB,EAAE,IAAI,EAAE,GAAG,eAAe,CAAC,EAAE,GAAG,KAAK,EAAE,CAAC;;AAGlE,IAAA,MAAM,YAAY,GAAG,KAAK,KAAK,SAAS;IACxC,MAAM,CAAC,WAAW,EAAE,cAAc,CAAC,GAAG,QAAQ,CAAC,YAAY,CAAC;IAC5D,MAAM,SAAS,GAAG,YAAY,GAAG,KAAK,GAAG,WAAW;AAEpD,IAAA,MAAM,YAAY,GAAG,WAAW,CAC9B,CAAC,KAAa,KAAI;QAChB,IAAI,CAAC,YAAY,EAAE;YACjB,cAAc,CAAC,KAAK,CAAC;QACvB;AACA,QAAA,QAAQ,GAAG,KAAK,CAAC;AACnB,IAAA,CAAC,EACD,CAAC,YAAY,EAAE,QAAQ,CAAC,CACzB;AAED,IAAA,MAAM,MAAM,GAAG,KAAK,EAAE;IACtB,MAAM,CAAC,IAAI,EAAE,WAAW,CAAC,GAAG,WAAW,EAAa;IACpD,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,GAAG,WAAW,EAAe;IAC1D,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,GAAG,QAAQ,CAAgB,IAAI,CAAC;;;;;;;;;;IAWjE,MAAM,WAAW,GAAG;SACjB,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ;AACvB,SAAA,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC;IACpC,MAAM,WAAW,GAAG,CAAC,KAAoB,KACvC,WAAW,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,KAAK,KAAK,CAAC;AAC1C,IAAA,IAAI,OAAsB;IAC1B,IAAI,WAAW,CAAC,UAAU,CAAC;QAAE,OAAO,GAAG,UAAU;SAC5C,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,WAAW,CAAC,SAAS,CAAC;QAAE,OAAO,GAAG,SAAS;;AACpE,QAAA,OAAO,GAAG,WAAW,CAAC,MAAM,GAAG,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG,IAAI;;IAGnE,MAAM,UAAU,GAAG,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC,QAAQ,CAAC;IACnD,MAAM,UAAU,GAAG,UAAU,CAAC,IAAI,CAChC,KAAK,IAAI,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,IAAI,KAAK,WAAW,CACnE;AAED,IAAA,MAAM,YAAY,GAAqB;QACrC,SAAS;QACT,YAAY;QACZ,MAAM;QACN,OAAO;QACP,aAAa;QACb,KAAK,EAAE,KAAK,IAAI,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,KAAK,KAAK,CAAC,EAAE,EAAE;QACrD,OAAO,EAAE,KAAK,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,KAAK,KAAK,CAAC,EAAE,EAAE;QACzD,WAAW;QACX,aAAa;QACb,WAAW,EAAE,UAAU,IAAI,QAAQ,GAAG,UAAU,GAAG,YAAY;KAChE;;AAGD,IAAA,MAAM,WAAW,GAAG,qBAAqB,CAAC,MAAM,EAAE;AAChD,QAAA,CAAC,CAAA,GAAA,EAAM,KAAK,CAAA,CAAE,GAAG,KAAK;AACtB,QAAA,CAAC,CAAA,GAAA,EAAM,IAAI,CAAA,CAAE,GAAG,IAAI;AACpB,QAAA,CAAC,CAAA,GAAA,EAAM,KAAK,CAAA,CAAE,GAAG,KAAK;AACtB,QAAA,cAAc,EAAE,WAAW,IAAI,WAAW,IAAI,SAAS;AACvD,QAAA,UAAU,EAAE,KAAK;AACjB,QAAA,WAAW,EAAE,MAAM;AACnB,QAAA,mBAAmB,EAAE,OAAO;AAC7B,KAAA,CAAC;;;;AAKF,IAAA,MAAM,WAAW,GAAG,qBAAqB,CAAC,WAAW,EAAE;QACrD,aAAa,EAAE,UAAU,IAAI,QAAQ;AACrC,QAAA,UAAU,EAAE,UAAU,IAAI,QAAQ,IAAI,IAAI,KAAK,OAAO;AACtD,QAAA,aAAa,EAAE,UAAU,IAAI,QAAQ,IAAI,QAAQ;AAClD,KAAA,CAAC;AAEF,IAAA,IAAI,UAAU,IAAI,QAAQ,EAAE;QAC1B,MAAM,mBAAmB,GAAG,UAAU,CACpC,WAAW,EACX,kBAAkB,EAClB,SAAS,CACV;;QAGD,MAAM,YAAY,GAAsB,EAAE;QAC1C,MAAM,eAAe,GAAsB,EAAE;AAC7C,QAAA,UAAU,CAAC,OAAO,CAAC,KAAK,IAAG;AACzB,YAAA,IAAI,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,IAAI,KAAK,WAAW,EAAE;AAC7D,gBAAA,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC;YAC7B;iBAAO;AACL,gBAAA,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC;YAC1B;AACF,QAAA,CAAC,CAAC;AAEF,QAAA,QACEA,GAAA,CAAC,WAAW,CAAC,QAAQ,EAAA,EAAC,KAAK,EAAE,YAAY,EAAA,QAAA,EACvCC,IAAA,CAAA,KAAA,EAAA,EAAK,SAAS,EAAE,mBAAmB,EAAA,GAAM,IAAI,EAAA,QAAA,EAAA,CAC3CD,GAAA,CAAA,KAAA,EAAA,EAAK,SAAS,EAAE,WAAW,EAAA,QAAA,EAAG,YAAY,EAAA,CAAO,EAChD,eAAe,CAAA,EAAA,CACZ,EAAA,CACe;IAE3B;IAEA,IAAI,UAAU,EAAE;QACd,MAAM,mBAAmB,GAAG,UAAU,CACpC,WAAW,EACX,kBAAkB,EAClB,SAAS,CACV;QAED,MAAM,YAAY,GAAsB,EAAE;QAC1C,MAAM,eAAe,GAAsB,EAAE;AAC7C,QAAA,UAAU,CAAC,OAAO,CAAC,KAAK,IAAG;AACzB,YAAA,IAAI,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,IAAI,KAAK,WAAW,EAAE;AAC7D,gBAAA,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC;YAC7B;iBAAO;AACL,gBAAA,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC;YAC1B;AACF,QAAA,CAAC,CAAC;AAEF,QAAA,QACEA,GAAA,CAAC,WAAW,CAAC,QAAQ,EAAA,EAAC,KAAK,EAAE,YAAY,EAAA,QAAA,EACvCC,IAAA,CAAA,KAAA,EAAA,EAAK,SAAS,EAAE,mBAAmB,EAAA,GAAM,IAAI,EAAA,QAAA,EAAA,CAC3CD,GAAA,CAAA,KAAA,EAAA,EAAK,SAAS,EAAE,WAAW,EAAA,QAAA,EAAG,YAAY,EAAA,CAAO,EAChD,eAAe,CAAA,EAAA,CACZ,EAAA,CACe;IAE3B;;IAGA,MAAM,eAAe,GAAG,UAAU,CAChC,WAAW,EACX,kBAAkB,EAClB,SAAS,CACV;IAED,QACEA,IAAC,WAAW,CAAC,QAAQ,EAAA,EAAC,KAAK,EAAE,YAAY,EAAA,QAAA,EACvCA,aAAK,SAAS,EAAE,eAAe,EAAA,GAAM,IAAI,YACtC,QAAQ,EAAA,CACL,EAAA,CACe;AAE3B,CAAC;AAgBD;;;;;;AAMG;AACI,MAAM,OAAO,GAA2B,CAAC,EAC9C,SAAS,EACT,QAAQ,EACR,GAAG,KAAK,EACT,KAAI;AACH,IAAA,MAAM,GAAG,GAAG,cAAc,EAAE;AAC5B,IAAA,QACEA,GAAA,CAAA,IAAA,EAAA,EACE,IAAI,EAAC,SAAS,EAAA,kBAAA,EAEZ,GAAG,EAAE,WAAW,KAAK,UAAU,GAAG,UAAU,GAAG,SAAS,EAE1D,SAAS,EAAE,UAAU,CAAC,SAAS,CAAC,IAAI,SAAS,KACzC,KAAK,EAAA,QAAA,EAER,QAAQ,EAAA,CACN;AAET;AA0CA;;;;;;;;;AASG;AACI,MAAM,GAAG,GAAuB,CAAC,EACtC,KAAK,EACL,QAAQ,EACR,IAAI,EACJ,WAAW,EACX,WAAW,EACX,QAAQ,GAAG,OAAO,EAClB,YAAY,EACZ,SAAS,EACT,QAAQ,EACR,EAAE,EACF,SAAS,EACT,OAAO,EACP,MAAM,EACN,GAAG,KAAK,EACT,KAAI;AACH,IAAA,MAAM,GAAG,GAAG,cAAc,EAAE;AAC5B,IAAA,MAAM,QAAQ,GAAG,GAAG,GAAG,GAAG,CAAC,SAAS,KAAK,KAAK,GAAG,KAAK;AACtD,IAAA,MAAM,SAAS,GAAG,MAAM,CAAoB,IAAI,CAAC;IAEjD,MAAM,WAAW,GAAG,qBAAqB,CAAC,EAAE,WAAW,EAAE,QAAQ,EAAE,CAAC;IAEpE,MAAM,KAAK,GAAG,EAAE,KAAK,GAAG,GAAG,CAAA,EAAG,GAAG,CAAC,MAAM,QAAQ,KAAK,CAAA,CAAE,GAAG,SAAS,CAAC;AACpE,IAAA,MAAM,WAAW,GAAG,GAAG,EAAE,WAAW;IACpC,SAAS,CAAC,MAAK;AACb,QAAA,IAAI,CAAC,WAAW,IAAI,KAAK,KAAK,SAAS;AAAE,YAAA,OAAO,SAAS;AACzD,QAAA,OAAO,WAAW,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC;IAChE,CAAC,EAAE,CAAC,WAAW,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC;;;AAIzC,IAAA,IAAI,QAAgB;AACpB,IAAA,IAAI,QAAQ;QAAE,QAAQ,GAAG,EAAE;AACtB,SAAA,IAAI,GAAG;AAAE,QAAA,QAAQ,GAAG,GAAG,CAAC,OAAO,KAAK,KAAK,GAAG,CAAC,GAAG,EAAE;;QAClD,QAAQ,GAAG,CAAC;AAEjB,IAAA,MAAM,WAAW,GAAG,WAAW,CAC7B,CAAC,CAAmB,KAAI;QACtB,CAAC,CAAC,cAAc,EAAE;AAClB,QAAA,IAAI,CAAC,QAAQ,IAAI,GAAG,EAAE;AACpB,YAAA,GAAG,CAAC,YAAY,CAAC,KAAK,CAAC;QACzB;IACF,CAAC,EACD,CAAC,QAAQ,EAAE,GAAG,EAAE,KAAK,CAAC,CACvB;AAED,IAAA,MAAM,aAAa,GAAG,CAAC,CAAqC,KAAI;AAC9D,QAAA,SAAS,GAAG,CAAC,CAAC;;QAEd,IACE,CAAC,CAAC,gBAAgB;AAClB,YAAA,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,aAAa;AAC5B,YAAA,CAAC,CAAC,MAAM;AACR,YAAA,CAAC,CAAC,OAAO;YACT,CAAC,CAAC,OAAO,EACT;YACA;QACF;AACA,QAAA,IAAI,CAAC,CAAC,GAAG,KAAK,OAAO,IAAI,CAAC,CAAC,GAAG,KAAK,GAAG,EAAE;YACtC,CAAC,CAAC,cAAc,EAAE;;;;AAIjB,YAAA,SAAS,CAAC,OAA6B,CAAC,KAAK,EAAE;YAChD;QACF;AACA,QAAA,MAAM,MAAM,GAAG,aAAa,CAC1B,CAAC,CAAC,aAAa,EACf,CAAC,CAAC,GAAG,EACL,GAAG,EAAE,WAAW,KAAK,UAAU,CAChC;QACD,IAAI,MAAM,EAAE;YACV,CAAC,CAAC,cAAc,EAAE;YAClB,MAAM,CAAC,KAAK,EAAE;QAChB;AACF,IAAA,CAAC;AAED,IAAA,MAAM,WAAW,GAAG,CAAC,CAAkC,KAAI;AACzD,QAAA,OAAO,GAAG,CAAC,CAAC;AACZ,QAAA,GAAG,EAAE,aAAa,CAAC,KAAK,CAAC;AAC3B,IAAA,CAAC;AAED,IAAA,MAAM,UAAU,GAAG,CAAC,CAAkC,KAAI;AACxD,QAAA,MAAM,GAAG,CAAC,CAAC;AACX,QAAA,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,aAAa,CAAC,EAAE;AACzD,YAAA,GAAG,EAAE,aAAa,CAAC,IAAI,CAAC;QAC1B;AACF,IAAA,CAAC;IAED,QACEA,YACE,SAAS,EAAE,UAAU,CAAC,WAAW,EAAE,SAAS,CAAC,IAAI,SAAS,EAC1D,IAAI,EAAC,KAAK,EACV,EAAE,EAAE,KAAK,EAAA,eAAA,EACM,QAAQ,EAAA,eAAA,EACR,GAAG,EAAE,OAAO,CAAC,KAAK,CAAC,EAAA,eAAA,EACnB,QAAQ,IAAI,SAAS,EACpC,QAAQ,EAAE,QAAQ,EAClB,SAAS,EAAE,aAAa,EACxB,OAAO,EAAE,WAAW,EACpB,MAAM,EAAE,UAAU,EAAA,GACd,KAAK,EAAA,QAAA,EAETC,IAAA,CAAA,GAAA,EAAA,EACE,GAAG,EAAE,SAAS,EACd,OAAO,EAAE,WAAW,EAAA,eAAA,EACL,QAAQ,IAAI,SAAS,EAAA,QAAA,EAAA,CAEnC,IAAI,KACHD,GAAA,CAAC,IAAI,EAAA,EACH,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,WAAW,EACpB,OAAO,EAAE,WAAW,EACpB,IAAI,EAAE,QAAQ,EACd,QAAQ,EAAE,YAAY,EAAA,CACtB,CACH,EACA,QAAQ,IAAIA,GAAA,CAAA,MAAA,EAAA,EAAA,QAAA,EAAO,QAAQ,EAAA,CAAQ,CAAA,EAAA,CAClC,EAAA,CACD;AAET;AAoBA;;;;;;;AAOG;AACI,MAAM,OAAO,GAA2B,CAAC,EAC9C,MAAM,EACN,SAAS,EACT,QAAQ,EACR,OAAO,EACP,GAAG,KAAK,EACT,MACCA,YACE,SAAS,EACP,UAAU,CAAC,EAAE,CAAC,qBAAqB,CAAC,WAAW,CAAC,GAAG,MAAM,EAAE,EAAE,SAAS,CAAC;QACvE,SAAS,EAEX,OAAO,EAAE,OAAO,EAAA,GACZ,KAAK,EAAA,QAAA,EAER,QAAQ,EAAA,CACN;AAiBP;;;;;;AAMG;AACH,MAAM,oBAAoB,GAA+B,CAAC,EACxD,SAAS,EACT,QAAQ,EACR,GAAG,KAAK,EACT,KAAI;AACH,IAAA,MAAM,YAAY,GAAG,qBAAqB,CAAC,cAAc,CAAC;AAC1D,IAAA,QACEA,GAAA,CAAA,KAAA,EAAA,EAAK,SAAS,EAAE,UAAU,CAAC,YAAY,EAAE,SAAS,CAAC,KAAM,KAAK,EAAA,QAAA,EAC3D,QAAQ,EAAA,CACL;AAEV,CAAC;AAoBD;;;;;;AAMG;AACI,MAAM,cAAc,GAAkC,CAAC,EAC5D,KAAK,EACL,SAAS,EACT,QAAQ,EACR,EAAE,EACF,GAAG,KAAK,EACT,KAAI;AACH,IAAA,MAAM,GAAG,GAAG,cAAc,EAAE;AAC5B,IAAA,MAAM,QAAQ,GAAG,GAAG,GAAG,GAAG,CAAC,SAAS,KAAK,KAAK,GAAG,KAAK;AAEtD,IAAA,MAAM,SAAS,GAAG,qBAAqB,CAAC,mBAAmB,EAAE;AAC3D,QAAA,WAAW,EAAE,QAAQ;AACtB,KAAA,CAAC;IAEF,MAAM,OAAO,GAAG,EAAE,KAAK,GAAG,GAAG,CAAA,EAAG,GAAG,CAAC,MAAM,UAAU,KAAK,CAAA,CAAE,GAAG,SAAS,CAAC;AACxE,IAAA,MAAM,aAAa,GAAG,GAAG,EAAE,aAAa;IACxC,SAAS,CAAC,MAAK;AACb,QAAA,IAAI,CAAC,aAAa,IAAI,OAAO,KAAK,SAAS;AAAE,YAAA,OAAO,SAAS;QAC7D,OAAO,aAAa,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,OAAO,EAAE,CAAC;IAC9C,CAAC,EAAE,CAAC,aAAa,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;AAEnC,IAAA,QACEA,GAAA,CAAA,KAAA,EAAA,EACE,SAAS,EAAE,UAAU,CAAC,SAAS,EAAE,SAAS,CAAC,EAC3C,IAAI,EAAC,UAAU,EACf,EAAE,EAAE,OAAO,EAAA,iBAAA,EACM,GAAG,EAAE,KAAK,CAAC,KAAK,CAAC,EAAA,aAAA,EACrB,CAAC,QAAQ,KAClB,KAAK,EAAA,QAAA,EAER,QAAQ,EAAA,CACL;AAEV;AAEA;AACA;AACA;AAEO,MAAM,WAAW,GAAG,iBAAiB,CAAC,oBAAoB,EAAE;AACjE,IAAA,IAAI,EAAE,cAAc;AACrB,CAAA;AAEM,MAAM,IAAI,GAAG,iBAAiB,CACnC,aAAa,EACb;AACE,IAAA,IAAI,EAAE,OAAO;IACb,GAAG;AACH,IAAA,IAAI,EAAE,OAAO;AACb,IAAA,OAAO,EAAE,WAAW;CACrB,EACD,MAAM;;"}
|
|
1
|
+
{"version":3,"file":"Tabs.js","sources":["../../../src/components/Tabs.tsx"],"sourcesContent":["import React, {\n createContext,\n useContext,\n useEffect,\n useId,\n useRef,\n useState,\n useCallback,\n} from 'react';\nimport { classNames, usePrefixedClassNames } from '../helpers/classNames';\nimport { withSubComponents } from '../helpers/withSubComponents';\nimport { isElementOf } from '../helpers/elementType';\nimport { useBulmaClasses, BulmaClassesProps } from '../helpers/useBulmaClasses';\nimport { warnDeprecatedColorProp } from '../helpers/colorDeprecations';\nimport { Icon } from '../elements/Icon';\n\n// ---------------------------------------------------------------------------\n// Context\n// ---------------------------------------------------------------------------\n\ninterface TabRecord {\n index: number;\n id: string;\n disabled: boolean;\n}\n\ninterface PanelRecord {\n index: number;\n id: string;\n}\n\ninterface TabsContextValue {\n activeTab: number;\n setActiveTab: (index: number) => void;\n /** Prefix for the ids the tabs and panels generate. */\n baseId: string;\n /** The tab that holds `tabIndex={0}`, or `null` when every tab is disabled. */\n tabStop: number | null;\n /** Records which tab has focus, so the tab stop can follow it. */\n setFocusedTab: (index: number | null) => void;\n /** The id of the mounted tab for `index`, for its panel's `aria-labelledby`. */\n tabId: (index: number) => string | undefined;\n /** The id of the mounted panel for `index`, for its tab's `aria-controls`. */\n panelId: (index: number) => string | undefined;\n registerTab: (record: TabRecord) => () => void;\n registerPanel: (record: PanelRecord) => () => void;\n orientation: 'horizontal' | 'vertical';\n}\n\nconst TabsContext = createContext<TabsContextValue | null>(null);\n\nfunction useTabsContext(): TabsContextValue | null {\n return useContext(TabsContext);\n}\n\n/**\n * The tabs and panels currently mounted under one `Tabs`. Each registers from\n * an effect and removes itself on cleanup, so the links between them only\n * ever name elements that exist, wherever in the tree the panels sit.\n */\nfunction useRegistry<T>(): [T[], (record: T) => () => void] {\n const [records, setRecords] = useState<T[]>([]);\n const register = useCallback((record: T) => {\n setRecords(prev => [...prev, record]);\n return () => setRecords(prev => prev.filter(r => r !== record));\n }, []);\n return [records, register];\n}\n\n/**\n * The list a tab belongs to: its `role=\"tablist\"` ancestor, so an element\n * wrapped around a tab doesn't cut it off from the others, or its parent when\n * it sits outside a tab list.\n */\nfunction tabListOf(tab: HTMLElement): HTMLElement {\n return (tab.closest('[role=\"tablist\"]') ?? tab.parentElement) as HTMLElement;\n}\n\n/**\n * The tab a key press moves focus to, found among the tabs of the same list\n * in DOM order, skipping disabled ones and wrapping at either end.\n * `undefined` for a key the tab list does not handle, or when no enabled tab\n * is left to go to.\n */\nfunction findTargetTab(\n current: HTMLElement,\n key: string,\n vertical: boolean\n): HTMLElement | undefined {\n const tabs = Array.from(\n tabListOf(current).querySelectorAll<HTMLElement>('[role=\"tab\"]')\n );\n const isEnabled = (el: HTMLElement) =>\n el.getAttribute('aria-disabled') !== 'true';\n\n if (key === 'Home') return tabs.find(isEnabled);\n if (key === 'End') return [...tabs].reverse().find(isEnabled);\n\n let step: number;\n if (key === 'ArrowRight' || (vertical && key === 'ArrowDown')) step = 1;\n else if (key === 'ArrowLeft' || (vertical && key === 'ArrowUp')) step = -1;\n else return undefined;\n\n const start = tabs.indexOf(current);\n for (let i = 1; i <= tabs.length; i++) {\n const candidate =\n tabs[(((start + step * i) % tabs.length) + tabs.length) % tabs.length];\n if (isEnabled(candidate)) return candidate;\n }\n return undefined;\n}\n\n// ---------------------------------------------------------------------------\n// Tabs (root)\n// ---------------------------------------------------------------------------\n\n/**\n * Props for the Tabs component.\n */\nexport interface TabsProps\n extends\n Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange'>,\n Omit<BulmaClassesProps, 'color' | 'backgroundColor'> {\n /** Tab alignment. */\n align?: 'centered' | 'right' | 'left';\n /** Tab size. */\n size?: 'small' | 'medium' | 'large';\n /** Tabs expand to fill the horizontal space. */\n isFullwidth?: boolean;\n /** Tabs expand to fill the horizontal space. @deprecated Use `isFullwidth` instead — `isFullwidth` wins if both are set. */\n isFullWidth?: boolean;\n /** Tabs expand to fill the horizontal space. @deprecated Use `isFullwidth` instead — `isFullwidth` wins if both are set. */\n fullwidth?: boolean;\n /** Tabs use the boxed style. */\n boxed?: boolean;\n /** Tabs use the toggle style. */\n toggle?: boolean;\n /** Tabs use the rounded toggle style (only with `toggle`). */\n rounded?: boolean;\n /**\n * Bulma color for tab underlines and active state (renders `is-<color>`).\n *\n * Bulma ships no tabs color CSS, so this prop has never had a visual effect\n * for any value. Passing it logs a console warning in development.\n * @deprecated No `.tabs.is-<color>` CSS exists; the prop renders unstyled\n * and will be removed in the next major version.\n */\n color?:\n | 'primary'\n | 'link'\n | 'info'\n | 'success'\n | 'warning'\n | 'danger'\n | 'black'\n | 'dark'\n | 'light'\n | 'white';\n /** Controlled active tab index. */\n value?: number;\n /**\n * Callback when active tab changes. Called with the tab's `index` when a\n * `Tabs.Tab` is activated: clicked, or Enter or Space pressed on it.\n * Moving focus with the arrow keys, Home or End does not call it, because\n * activation is manual: focus moves freely and the selected tab changes\n * only when the user picks one.\n */\n onChange?: (index: number) => void;\n /** Initial active tab index (uncontrolled). */\n defaultValue?: number;\n /**\n * Renders tabs vertically. With a `Tabs.Content`, the tab list is also\n * marked `aria-orientation=\"vertical\"` and ArrowUp and ArrowDown move focus\n * between tabs, alongside ArrowLeft and ArrowRight (the layout stacks\n * horizontally on mobile).\n */\n vertical?: boolean;\n /** Side placement when `vertical` is true. */\n side?: 'left' | 'right';\n /** Makes tabs take up the full width equally. */\n expanded?: boolean;\n /** Additional CSS classes. */\n className?: string;\n /** Tab list and tab items. */\n children?: React.ReactNode;\n}\n\n/**\n * The `Tabs` component provides flexible and fully-featured Bulma tab navigation for your Bulma React UI.\n *\n * Built from `Tabs.Tab`, the tabs follow the WAI-ARIA tabs pattern with\n * manual activation: the tab list is a single tab stop, the arrow keys, Home\n * and End move focus between tabs (skipping disabled ones), and Enter or\n * Space activates the focused tab. Each tab names its `Tabs.Content.Item` in\n * `aria-controls`, and each panel names its tab in `aria-labelledby`. Built\n * from `Tabs.Item` with links inside, the tabs stay plain Bulma navigation.\n *\n * @function\n * @param {TabsProps} props - Props for the Tabs component.\n * @returns {JSX.Element} The rendered tabs component.\n * @see {@link https://bulma.io/documentation/components/tabs/ | Bulma Tabs documentation}\n *\n * @example\n * // Basic tabs\n * <Tabs>\n * <Tabs.List>\n * <Tabs.Tab index={0}>Pictures</Tabs.Tab>\n * <Tabs.Tab index={1}>Music</Tabs.Tab>\n * </Tabs.List>\n * <Tabs.Content>\n * <Tabs.Content.Item index={0}>Pictures content</Tabs.Content.Item>\n * <Tabs.Content.Item index={1}>Music content</Tabs.Content.Item>\n * </Tabs.Content>\n * </Tabs>\n */\nconst TabsComponent: React.FC<TabsProps> = ({\n align,\n size,\n isFullwidth,\n isFullWidth,\n fullwidth,\n boxed,\n toggle,\n rounded,\n color,\n value,\n onChange,\n defaultValue = 0,\n vertical,\n side,\n expanded,\n className,\n children,\n ...props\n}) => {\n warnDeprecatedColorProp(\n 'Tabs',\n color,\n 'Remove the prop; no replacement exists.'\n );\n\n const { bulmaHelperClasses, rest } = useBulmaClasses({ ...props });\n\n // Controlled vs uncontrolled state\n const isControlled = value !== undefined;\n const [internalTab, setInternalTab] = useState(defaultValue);\n const activeTab = isControlled ? value : internalTab;\n\n const setActiveTab = useCallback(\n (index: number) => {\n if (!isControlled) {\n setInternalTab(index);\n }\n onChange?.(index);\n },\n [isControlled, onChange]\n );\n\n const baseId = useId();\n const [tabs, registerTab] = useRegistry<TabRecord>();\n const [panels, registerPanel] = useRegistry<PanelRecord>();\n const [focusedTab, setFocusedTab] = useState<number | null>(null);\n\n // One tab stop for the whole list (roving tabindex). While focus is inside\n // the list the stop follows it, so Tab and Shift+Tab leave the list from\n // whichever tab has focus; once focus leaves, the stop is the selected tab\n // again. A disabled tab never holds it, and when the selected tab is\n // disabled or missing the enabled tab with the lowest index does, so the\n // list can always be reached. Before the tabs register (the server render\n // and the first client render), the selected tab holds it. The registry is\n // in the order the tabs last registered, which a tab changing its\n // `disabled` or `id` reshuffles, hence the sort.\n const enabledTabs = tabs\n .filter(t => !t.disabled)\n .sort((a, b) => a.index - b.index);\n const canHoldStop = (index: number | null) =>\n enabledTabs.some(t => t.index === index);\n let tabStop: number | null;\n if (canHoldStop(focusedTab)) tabStop = focusedTab;\n else if (tabs.length === 0 || canHoldStop(activeTab)) tabStop = activeTab;\n else tabStop = enabledTabs.length > 0 ? enabledTabs[0].index : null;\n\n // Check if children include TabsContent\n const childArray = React.Children.toArray(children);\n const hasContent = childArray.some(child => isElementOf(child, TabsContent));\n\n const contextValue: TabsContextValue = {\n activeTab,\n setActiveTab,\n baseId,\n tabStop,\n setFocusedTab,\n tabId: index => tabs.find(t => t.index === index)?.id,\n panelId: index => panels.find(p => p.index === index)?.id,\n registerTab,\n registerPanel,\n orientation: hasContent && vertical ? 'vertical' : 'horizontal',\n };\n\n // Build classes for the .tabs div\n const tabsClasses = usePrefixedClassNames('tabs', {\n [`is-${align}`]: align,\n [`is-${size}`]: size,\n [`is-${color}`]: color,\n 'is-fullwidth': isFullwidth ?? isFullWidth ?? fullwidth,\n 'is-boxed': boxed,\n 'is-toggle': toggle,\n 'is-toggle-rounded': rounded,\n });\n\n // Hoisted unconditionally to respect rules-of-hooks. Modifiers gate themselves\n // via their truthy values — `tabs-root` is always prefixed, modifiers only\n // apply in the vertical-with-content branch.\n const rootClasses = usePrefixedClassNames('tabs-root', {\n 'is-vertical': hasContent && vertical,\n 'is-right': hasContent && vertical && side === 'right',\n 'is-expanded': hasContent && vertical && expanded,\n });\n\n if (hasContent && vertical) {\n const combinedRootClasses = classNames(\n rootClasses,\n bulmaHelperClasses,\n className\n );\n\n // Split children into list-like and content children\n const listChildren: React.ReactNode[] = [];\n const contentChildren: React.ReactNode[] = [];\n childArray.forEach(child => {\n if (isElementOf(child, TabsContent)) {\n contentChildren.push(child);\n } else {\n listChildren.push(child);\n }\n });\n\n return (\n <TabsContext.Provider value={contextValue}>\n <div className={combinedRootClasses} {...rest}>\n <div className={tabsClasses}>{listChildren}</div>\n {contentChildren}\n </div>\n </TabsContext.Provider>\n );\n }\n\n if (hasContent) {\n const combinedRootClasses = classNames(\n rootClasses,\n bulmaHelperClasses,\n className\n );\n\n const listChildren: React.ReactNode[] = [];\n const contentChildren: React.ReactNode[] = [];\n childArray.forEach(child => {\n if (isElementOf(child, TabsContent)) {\n contentChildren.push(child);\n } else {\n listChildren.push(child);\n }\n });\n\n return (\n <TabsContext.Provider value={contextValue}>\n <div className={combinedRootClasses} {...rest}>\n <div className={tabsClasses}>{listChildren}</div>\n {contentChildren}\n </div>\n </TabsContext.Provider>\n );\n }\n\n // No content children — backward compatible single .tabs div\n const combinedClasses = classNames(\n tabsClasses,\n bulmaHelperClasses,\n className\n );\n\n return (\n <TabsContext.Provider value={contextValue}>\n <div className={combinedClasses} {...rest}>\n {children}\n </div>\n </TabsContext.Provider>\n );\n};\n\n// ---------------------------------------------------------------------------\n// TabList\n// ---------------------------------------------------------------------------\n\n/**\n * Props for the TabList component.\n */\nexport interface TabListProps extends React.HTMLAttributes<HTMLUListElement> {\n /** Additional CSS classes. */\n className?: string;\n /** Tab elements. */\n children?: React.ReactNode;\n}\n\n/**\n * The `<ul>` container for tab items.\n *\n * @function\n * @param {TabListProps} props - Props for the TabList component.\n * @returns {JSX.Element} The rendered tab list.\n */\nexport const TabList: React.FC<TabListProps> = ({\n className,\n children,\n ...props\n}) => {\n const ctx = useTabsContext();\n return (\n <ul\n role=\"tablist\"\n aria-orientation={\n ctx?.orientation === 'vertical' ? 'vertical' : undefined\n }\n className={classNames(className) || undefined}\n {...props}\n >\n {children}\n </ul>\n );\n};\n\n// ---------------------------------------------------------------------------\n// Tab (new — context-aware)\n// ---------------------------------------------------------------------------\n\ntype IconLibrary = 'fa' | 'mdi' | 'ion' | 'material-icons' | 'material-symbols';\n\n/**\n * Props for the Tab component.\n * @extraProp {string} [id] - The tab's id, which its panel names in `aria-labelledby`. Generated inside a `Tabs` when omitted; a passed `id` is used and the panel follows it.\n * @extraProp {React.KeyboardEventHandler<HTMLLIElement>} [onKeyDown] - Called before the tab handles the key; call `event.preventDefault()` to stop the tab handling it.\n */\nexport interface TabProps extends Omit<\n React.LiHTMLAttributes<HTMLLIElement>,\n 'onClick'\n> {\n /** **Required.** Tab index for matching with content. */\n index: number;\n /**\n * Disables the tab. A disabled tab is marked `aria-disabled`, cannot be\n * activated, is skipped by the arrow keys, Home and End, and never holds\n * the tab list's tab stop. The bestax extras styles also dim it and turn\n * off its hover.\n */\n disabled?: boolean;\n /** Icon name for the tab. */\n icon?: string;\n /** Icon library to use. */\n iconLibrary?: IconLibrary;\n /** Icon style variant (e.g., 'solid', 'outlined', 'rounded'). */\n iconVariant?: string;\n /** Size of the tab icon. */\n iconSize?: 'small' | 'medium' | 'large';\n /** Additional icon modifiers. */\n iconFeatures?: string | string[];\n /** Additional CSS classes. */\n className?: string;\n /** Tab label content. */\n children?: React.ReactNode;\n}\n\n/**\n * Individual tab button. Consumes Tabs context for active state management.\n * Renders `<a>` internally — consumers provide only the label text/children.\n * Inside a `Tabs` it takes part in the tab list's keyboard support and links\n * to the `Tabs.Content.Item` with the same `index`.\n *\n * @function\n * @param {TabProps} props - Props for the Tab component.\n * @returns {JSX.Element} The rendered tab.\n */\nexport const Tab: React.FC<TabProps> = ({\n index,\n disabled,\n icon,\n iconLibrary,\n iconVariant,\n iconSize = 'small',\n iconFeatures,\n className,\n children,\n id,\n onKeyDown,\n onFocus,\n onBlur,\n ...props\n}) => {\n const ctx = useTabsContext();\n const isActive = ctx ? ctx.activeTab === index : false;\n const anchorRef = useRef<HTMLAnchorElement>(null);\n\n const activeClass = usePrefixedClassNames({ 'is-active': isActive });\n\n const tabId = id ?? (ctx ? `${ctx.baseId}-tab-${index}` : undefined);\n const registerTab = ctx?.registerTab;\n useEffect(() => {\n if (!registerTab || tabId === undefined) return undefined;\n return registerTab({ index, id: tabId, disabled: !!disabled });\n }, [registerTab, index, tabId, disabled]);\n\n // Outside a Tabs there is no list to rove, so every enabled tab stays in\n // the page tab order, as it always did.\n let tabIndex: number;\n if (disabled) tabIndex = -1;\n else if (ctx) tabIndex = ctx.tabStop === index ? 0 : -1;\n else tabIndex = 0;\n\n const handleClick = useCallback(\n (e: React.MouseEvent) => {\n e.preventDefault();\n if (!disabled && ctx) {\n ctx.setActiveTab(index);\n }\n },\n [disabled, ctx, index]\n );\n\n const handleKeyDown = (e: React.KeyboardEvent<HTMLLIElement>) => {\n onKeyDown?.(e);\n // A key on something inside the tab is that element's to answer.\n if (\n e.defaultPrevented ||\n e.target !== e.currentTarget ||\n e.altKey ||\n e.ctrlKey ||\n e.metaKey\n ) {\n return;\n }\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n // Activate through a click on the inner <a>, the element a pointer\n // clicks, so click listeners see keyboard activation the way they see\n // a native button's.\n (anchorRef.current as HTMLAnchorElement).click();\n return;\n }\n const target = findTargetTab(\n e.currentTarget,\n e.key,\n ctx?.orientation === 'vertical'\n );\n if (target) {\n e.preventDefault();\n target.focus();\n }\n };\n\n const handleFocus = (e: React.FocusEvent<HTMLLIElement>) => {\n onFocus?.(e);\n ctx?.setFocusedTab(index);\n };\n\n const handleBlur = (e: React.FocusEvent<HTMLLIElement>) => {\n onBlur?.(e);\n if (!tabListOf(e.currentTarget).contains(e.relatedTarget)) {\n ctx?.setFocusedTab(null);\n }\n };\n\n return (\n <li\n className={classNames(activeClass, className) || undefined}\n role=\"tab\"\n id={tabId}\n aria-selected={isActive}\n aria-controls={ctx?.panelId(index)}\n aria-disabled={disabled || undefined}\n tabIndex={tabIndex}\n onKeyDown={handleKeyDown}\n onFocus={handleFocus}\n onBlur={handleBlur}\n {...props}\n >\n <a\n ref={anchorRef}\n onClick={handleClick}\n aria-disabled={disabled || undefined}\n >\n {icon && (\n <Icon\n name={icon}\n library={iconLibrary}\n variant={iconVariant}\n size={iconSize}\n features={iconFeatures}\n />\n )}\n {children && <span>{children}</span>}\n </a>\n </li>\n );\n};\n\n// ---------------------------------------------------------------------------\n// TabItem (backward-compatible — no context)\n// ---------------------------------------------------------------------------\n\n/**\n * Props for the TabItem component (backward-compatible).\n */\nexport interface TabItemProps extends React.LiHTMLAttributes<HTMLLIElement> {\n /** Whether the tab is active. */\n active?: boolean;\n /** Additional CSS classes. */\n className?: string;\n /** Tab content. */\n children?: React.ReactNode;\n /** Click handler. */\n onClick?: React.MouseEventHandler<HTMLLIElement>;\n}\n\n/**\n * Each tab; accepts `active`, `onClick`, etc.\n *\n * @function\n * @param {TabItemProps} props - Props for the TabItem component.\n * @returns {JSX.Element} The rendered tab item.\n * @deprecated Use `Tabs.Tab` with an `index` prop instead.\n */\nexport const TabItem: React.FC<TabItemProps> = ({\n active,\n className,\n children,\n onClick,\n ...props\n}) => (\n <li\n className={\n classNames({ [usePrefixedClassNames('is-active')]: active }, className) ||\n undefined\n }\n onClick={onClick}\n {...props}\n >\n {children}\n </li>\n);\n\n// ---------------------------------------------------------------------------\n// TabsContent\n// ---------------------------------------------------------------------------\n\n/**\n * Props for the TabsContent component.\n */\nexport interface TabsContentProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Additional CSS classes. */\n className?: string;\n /** TabContentItem elements. */\n children?: React.ReactNode;\n}\n\n/**\n * Container for tab content panels. No custom props beyond `children` and standard `<div>` HTML attributes. Applies the `.tabs-content` class.\n *\n * @function\n * @param {TabsContentProps} props - Props for the TabsContent component.\n * @returns {JSX.Element} The rendered tabs content wrapper.\n */\nconst TabsContentComponent: React.FC<TabsContentProps> = ({\n className,\n children,\n ...props\n}) => {\n const contentClass = usePrefixedClassNames('tabs-content');\n return (\n <div className={classNames(contentClass, className)} {...props}>\n {children}\n </div>\n );\n};\n\n// ---------------------------------------------------------------------------\n// TabContentItem\n// ---------------------------------------------------------------------------\n\n/**\n * Props for the TabContentItem component.\n * @extraProp {string} [id] - The panel's id, which its tab names in `aria-controls`. Generated inside a `Tabs` when omitted; a passed `id` is used and the tab follows it.\n * @extraProp {number} [tabIndex] - The panel is not focusable by default. Pass `0` when it holds nothing focusable, so Tab moves from the tab list into it.\n */\nexport interface TabContentItemProps extends React.HTMLAttributes<HTMLDivElement> {\n /** **Required.** Tab index for matching with content. */\n index: number;\n /** Additional CSS classes. */\n className?: string;\n /** Panel content. */\n children?: React.ReactNode;\n}\n\n/**\n * Individual content panel. Shows/hides based on active tab from context.\n *\n * @function\n * @param {TabContentItemProps} props - Props for the TabContentItem component.\n * @returns {JSX.Element} The rendered tab content panel.\n */\nexport const TabContentItem: React.FC<TabContentItemProps> = ({\n index,\n className,\n children,\n id,\n ...props\n}) => {\n const ctx = useTabsContext();\n const isActive = ctx ? ctx.activeTab === index : false;\n\n const itemClass = usePrefixedClassNames('tabs-content-item', {\n 'is-active': isActive,\n });\n\n const panelId = id ?? (ctx ? `${ctx.baseId}-panel-${index}` : undefined);\n const registerPanel = ctx?.registerPanel;\n useEffect(() => {\n if (!registerPanel || panelId === undefined) return undefined;\n return registerPanel({ index, id: panelId });\n }, [registerPanel, index, panelId]);\n\n return (\n <div\n className={classNames(itemClass, className)}\n role=\"tabpanel\"\n id={panelId}\n aria-labelledby={ctx?.tabId(index)}\n aria-hidden={!isActive}\n {...props}\n >\n {children}\n </div>\n );\n};\n\n// ---------------------------------------------------------------------------\n// Static property attachment\n// ---------------------------------------------------------------------------\n\nexport const TabsContent = withSubComponents(TabsContentComponent, {\n Item: TabContentItem,\n});\n\nexport const Tabs = withSubComponents(\n TabsComponent,\n {\n List: TabList,\n Tab,\n Item: TabItem,\n Content: TabsContent,\n },\n 'Tabs'\n);\n\nexport default Tabs;\n"],"names":["_jsx","_jsxs"],"mappings":"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"}
|
|
@@ -78,9 +78,12 @@ const CopyButton = forwardRef(function CopyButton({ value, duration = 2000, show
|
|
|
78
78
|
// the button takes focus or a click, rather than once as it mounts,
|
|
79
79
|
// since Modal and Sidebar keep their content mounted while closed, and
|
|
80
80
|
// only an open one is `aria-modal`. A region that moves starts empty, so
|
|
81
|
-
// it doesn't say the last outcome again.
|
|
82
|
-
//
|
|
83
|
-
//
|
|
81
|
+
// it doesn't say the last outcome again. The popups inside a Modal,
|
|
82
|
+
// Dialog or Command.Dialog portal into it on their own, but this lookup
|
|
83
|
+
// stays: it finds a modal dialog the library didn't render too, such as
|
|
84
|
+
// a `<dialog>` opened with `showModal()`, and unlike theirs, it wins over
|
|
85
|
+
// a `portalContainer`, since a live region outside the dialog says
|
|
86
|
+
// nothing.
|
|
84
87
|
const [dialog, setDialog] = useState(null);
|
|
85
88
|
const placeRegion = (button) => {
|
|
86
89
|
const next = closestModalDialog(button);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CopyButton.js","sources":["../../../src/elements/CopyButton.tsx"],"sourcesContent":["import React, { forwardRef, useRef, useState } from 'react';\nimport { Button, type ButtonOwnProps } from './Button';\nimport { Icon } from './Icon';\nimport { IconButton, type IconButtonProps } from './IconButton';\nimport { buttonType } from '../helpers/buttonType';\nimport { CheckIcon } from '../helpers/checkIcon';\nimport { Portal } from '../helpers/portal';\nimport { StatusRegion } from '../helpers/statusRegion';\nimport { useClipboard } from '../helpers/useClipboard';\n\n/**\n * The strings CopyButton shows and announces, each with an English default. A\n * key left out, or set to an empty string, keeps its default.\n */\nexport interface CopyButtonLabels {\n /**\n * The button's label, and so its name, until a copy. With `showLabel` off\n * it's the name in both states, given as `aria-label`.\n * @defaultValue 'Copy'\n */\n copy?: string;\n /**\n * The label shown for `duration` after a copy, and so the name meanwhile.\n * Not used with `showLabel` off, where only the glyph changes.\n * @defaultValue 'Copied'\n */\n copied?: string;\n /**\n * Announced after a copy.\n * @defaultValue 'Copied to the clipboard'\n */\n copiedStatus?: string;\n /**\n * Announced when the copy fails.\n * @defaultValue \"Couldn't copy to the clipboard\"\n */\n failedStatus?: string;\n}\n\n/**\n * Props for the CopyButton component. The helper props, the other button\n * attributes and the `ref` land on the `<button>`.\n *\n * @extraProp {'button' | 'submit' | 'reset'} [type=button] - `button` unless you pass `submit` or `reset`.\n * @extraProp {React.MouseEventHandler<HTMLButtonElement>} [onClick] - Runs before the copy. An `onClick` that calls `preventDefault()` stops the copy, and neither `onCopiedChange` nor `onCopyError` fires.\n * @extraProp {React.Ref<HTMLButtonElement>} [ref] - Ref forwarded to the `<button>`.\n */\nexport interface CopyButtonProps\n extends\n Omit<ButtonOwnProps, 'children'>,\n Omit<\n React.ButtonHTMLAttributes<HTMLButtonElement>,\n keyof ButtonOwnProps | 'value'\n > {\n /**\n * The text it copies. This is not the `<button>`'s form value, which it\n * doesn't render.\n */\n value: string;\n /**\n * How long the \"Copied\" label stays, in milliseconds. Another copy\n * restarts it. `0` turns the reset off, as Toast's `0` turns off its\n * auto-close, so \"Copied\" stays until a later copy fails. A missing or `NaN`\n * value uses the default, a negative one ends it at once, and `Infinity`,\n * or anything longer than the longest delay `setTimeout` takes (about 24.8\n * days), is that longest delay.\n * @defaultValue 2000\n */\n duration?: number;\n /**\n * Shows the label beside the icon, which is then the button's name: it\n * carries no `aria-label`, so the name reads \"Copy\" and then \"Copied\" as\n * the words on it do. Turned off, it's a square icon-only button named by\n * `labels.copy` through `aria-label` in both states, and only the glyph\n * changes. An `aria-label` you pass yourself names it in either form.\n * @defaultValue true\n */\n showLabel?: boolean;\n /** Translations for the label and the announcements. */\n labels?: CopyButtonLabels;\n /**\n * Called with `true` after each copy that worked, a repeat included, and\n * with `false` when `duration` runs out or a later copy fails.\n */\n onCopiedChange?: (copied: boolean) => void;\n /**\n * Called when neither the Clipboard API nor the fallback could write. A\n * copy field can select its text from here, so the reader can copy it\n * themselves.\n */\n onCopyError?: (error: Error) => void;\n}\n\n/** Two sheets, one over the other. */\nconst CopyGlyph = () => (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinejoin=\"round\"\n width=\"1em\"\n height=\"1em\"\n focusable=\"false\"\n >\n <path d=\"M10.5 3.5v-1a1 1 0 0 0-1-1h-6a1 1 0 0 0-1 1v7a1 1 0 0 0 1 1h1\" />\n <rect x=\"5.5\" y=\"5.5\" width=\"8\" height=\"9\" rx=\"1\" />\n </svg>\n);\n\n/** One copy's outcome, as the status region announces it. */\ninterface Announcement {\n id: string;\n text: string;\n}\n\nconst describeAnnouncement = (announcement: Announcement) => announcement.text;\nconst noAnnouncements: Announcement[] = [];\n\n/** What marks an open modal dialog in ARIA. */\nconst modalDialog = '[aria-modal=\"true\"]';\n\n/**\n * The open modal dialog an element is in: one marked `aria-modal=\"true\"`, or\n * a `<dialog>` opened with `showModal()`, which makes the rest of the page\n * inert. An engine without the `:modal` selector throws on the whole list,\n * so it then looks for the ARIA alone.\n *\n * @param element - Where to look up from.\n * @returns The nearest open modal dialog around it, or `null`.\n */\nfunction closestModalDialog(element: Element): HTMLElement | null {\n try {\n return element.closest<HTMLElement>(`${modalDialog}, dialog:modal`);\n } catch {\n return element.closest<HTMLElement>(modalDialog);\n }\n}\n\n/**\n * The `CopyButton` component renders a button that copies a piece of text to the clipboard, says \"Copied\" for a moment, and announces the result.\n *\n * It's a `Button` with a copy icon and \"Copy\", and takes everything `Button`\n * takes. A click writes `value` through the Clipboard API, or through the\n * older copy command where that's missing or refused, and reports a failure\n * rather than showing \"Copied\". Each outcome is announced through a polite\n * status region portaled out of the button's place, so it stays the last\n * child of a `Control` or `Buttons`: into the open modal dialog the button is\n * in, otherwise where `Portal` puts portaled content (the `ConfigProvider`'s\n * `portalContainer`, or the end of the page). With `showLabel={false}` it's an\n * `IconButton`. For a trigger of your own, use `useClipboard`, which it's\n * built on.\n *\n * @function\n * @param {CopyButtonProps} props - Props for the CopyButton component.\n * @param {React.Ref<HTMLButtonElement>} ref - Forwarded ref to the `<button>`.\n * @returns {JSX.Element} The rendered button.\n * @see {@link https://www.w3.org/WAI/WCAG22/Understanding/status-messages.html | WCAG 4.1.3 Status Messages}\n *\n * @example\n * <CopyButton value=\"https://bestax.io\" />\n *\n * @example\n * <CopyButton value={apiKey} showLabel={false} size=\"small\" />\n */\nexport const CopyButton = forwardRef<HTMLButtonElement, CopyButtonProps>(\n function CopyButton(\n {\n value,\n duration = 2000,\n showLabel = true,\n labels,\n onCopiedChange,\n onCopyError,\n onClick,\n onFocus,\n ...props\n },\n ref\n ) {\n const copyLabel = labels?.copy || 'Copy';\n const copiedLabel = labels?.copied || 'Copied';\n const copiedStatus = labels?.copiedStatus || 'Copied to the clipboard';\n const failedStatus =\n labels?.failedStatus || \"Couldn't copy to the clipboard\";\n\n // The latest outcome. Each gets a new id, so the region announces a\n // repeat too.\n const [announcements, setAnnouncements] = useState(noAnnouncements);\n const countRef = useRef(0);\n const announce = (text: string) => {\n countRef.current += 1;\n setAnnouncements([{ id: String(countRef.current), text }]);\n };\n\n // The open modal dialog the button is in, which the region goes inside:\n // a screen reader ignores a live region behind one. It's looked up when\n // the button takes focus or a click, rather than once as it mounts,\n // since Modal and Sidebar keep their content mounted while closed, and\n // only an open one is `aria-modal`. A region that moves starts empty, so\n // it doesn't say the last outcome again. This lookup can go once the\n // overlays hand their content their own root as its portal container\n // (#1471).\n const [dialog, setDialog] = useState<HTMLElement | null>(null);\n const placeRegion = (button: HTMLElement) => {\n const next = closestModalDialog(button);\n if (next === dialog) return;\n setDialog(next);\n setAnnouncements(noAnnouncements);\n };\n\n // The hook fires nothing once this has unmounted, so neither does the\n // announcement.\n const { copy, copied } = useClipboard({\n duration,\n onCopiedChange: next => {\n if (next) announce(copiedStatus);\n onCopiedChange?.(next);\n },\n onCopyError: error => {\n announce(failedStatus);\n onCopyError?.(error);\n },\n });\n\n // The caller's onClick runs first, so a `preventDefault()` there stops\n // the copy, as it stops ToggleButton's toggle.\n const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {\n onClick?.(event);\n if (event.defaultPrevented) return;\n placeRegion(event.currentTarget);\n void copy(value);\n };\n\n const handleFocus = (event: React.FocusEvent<HTMLButtonElement>) => {\n onFocus?.(event);\n placeRegion(event.currentTarget);\n };\n\n const glyph = <Icon>{copied ? <CheckIcon /> : <CopyGlyph />}</Icon>;\n\n // The props are spread first, so it stays a `<button>` that doesn't\n // submit by accident, whatever a JavaScript caller or a spread object\n // passes along.\n const button = showLabel ? (\n <Button\n {...props}\n ref={ref}\n as=\"button\"\n type={buttonType(props.type)}\n onClick={handleClick}\n onFocus={handleFocus}\n >\n {glyph}\n <span>{copied ? copiedLabel : copyLabel}</span>\n </Button>\n ) : (\n // IconButton resolves `type` itself, from the same `buttonType`. Its\n // types refuse `aria-label` beside `aria-labelledby`, which a caller\n // here may pass, and the browser then names it by `aria-labelledby`.\n <IconButton\n {...({\n ...props,\n 'aria-label': props['aria-label'] || copyLabel,\n } as IconButtonProps)}\n ref={ref}\n as=\"button\"\n onClick={handleClick}\n onFocus={handleFocus}\n >\n {glyph}\n </IconButton>\n );\n\n return (\n <>\n {button}\n <Portal container={dialog || undefined}>\n <StatusRegion items={announcements} describe={describeAnnouncement} />\n </Portal>\n </>\n );\n }\n);\n\nCopyButton.displayName = 'CopyButton';\n\nexport default CopyButton;\n"],"names":["_jsxs","_jsx","describeAnnouncement","noAnnouncements","_Fragment"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CopyButton.js","sources":["../../../src/elements/CopyButton.tsx"],"sourcesContent":["import React, { forwardRef, useRef, useState } from 'react';\nimport { Button, type ButtonOwnProps } from './Button';\nimport { Icon } from './Icon';\nimport { IconButton, type IconButtonProps } from './IconButton';\nimport { buttonType } from '../helpers/buttonType';\nimport { CheckIcon } from '../helpers/checkIcon';\nimport { Portal } from '../helpers/portal';\nimport { StatusRegion } from '../helpers/statusRegion';\nimport { useClipboard } from '../helpers/useClipboard';\n\n/**\n * The strings CopyButton shows and announces, each with an English default. A\n * key left out, or set to an empty string, keeps its default.\n */\nexport interface CopyButtonLabels {\n /**\n * The button's label, and so its name, until a copy. With `showLabel` off\n * it's the name in both states, given as `aria-label`.\n * @defaultValue 'Copy'\n */\n copy?: string;\n /**\n * The label shown for `duration` after a copy, and so the name meanwhile.\n * Not used with `showLabel` off, where only the glyph changes.\n * @defaultValue 'Copied'\n */\n copied?: string;\n /**\n * Announced after a copy.\n * @defaultValue 'Copied to the clipboard'\n */\n copiedStatus?: string;\n /**\n * Announced when the copy fails.\n * @defaultValue \"Couldn't copy to the clipboard\"\n */\n failedStatus?: string;\n}\n\n/**\n * Props for the CopyButton component. The helper props, the other button\n * attributes and the `ref` land on the `<button>`.\n *\n * @extraProp {'button' | 'submit' | 'reset'} [type=button] - `button` unless you pass `submit` or `reset`.\n * @extraProp {React.MouseEventHandler<HTMLButtonElement>} [onClick] - Runs before the copy. An `onClick` that calls `preventDefault()` stops the copy, and neither `onCopiedChange` nor `onCopyError` fires.\n * @extraProp {React.Ref<HTMLButtonElement>} [ref] - Ref forwarded to the `<button>`.\n */\nexport interface CopyButtonProps\n extends\n Omit<ButtonOwnProps, 'children'>,\n Omit<\n React.ButtonHTMLAttributes<HTMLButtonElement>,\n keyof ButtonOwnProps | 'value'\n > {\n /**\n * The text it copies. This is not the `<button>`'s form value, which it\n * doesn't render.\n */\n value: string;\n /**\n * How long the \"Copied\" label stays, in milliseconds. Another copy\n * restarts it. `0` turns the reset off, as Toast's `0` turns off its\n * auto-close, so \"Copied\" stays until a later copy fails. A missing or `NaN`\n * value uses the default, a negative one ends it at once, and `Infinity`,\n * or anything longer than the longest delay `setTimeout` takes (about 24.8\n * days), is that longest delay.\n * @defaultValue 2000\n */\n duration?: number;\n /**\n * Shows the label beside the icon, which is then the button's name: it\n * carries no `aria-label`, so the name reads \"Copy\" and then \"Copied\" as\n * the words on it do. Turned off, it's a square icon-only button named by\n * `labels.copy` through `aria-label` in both states, and only the glyph\n * changes. An `aria-label` you pass yourself names it in either form.\n * @defaultValue true\n */\n showLabel?: boolean;\n /** Translations for the label and the announcements. */\n labels?: CopyButtonLabels;\n /**\n * Called with `true` after each copy that worked, a repeat included, and\n * with `false` when `duration` runs out or a later copy fails.\n */\n onCopiedChange?: (copied: boolean) => void;\n /**\n * Called when neither the Clipboard API nor the fallback could write. A\n * copy field can select its text from here, so the reader can copy it\n * themselves.\n */\n onCopyError?: (error: Error) => void;\n}\n\n/** Two sheets, one over the other. */\nconst CopyGlyph = () => (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinejoin=\"round\"\n width=\"1em\"\n height=\"1em\"\n focusable=\"false\"\n >\n <path d=\"M10.5 3.5v-1a1 1 0 0 0-1-1h-6a1 1 0 0 0-1 1v7a1 1 0 0 0 1 1h1\" />\n <rect x=\"5.5\" y=\"5.5\" width=\"8\" height=\"9\" rx=\"1\" />\n </svg>\n);\n\n/** One copy's outcome, as the status region announces it. */\ninterface Announcement {\n id: string;\n text: string;\n}\n\nconst describeAnnouncement = (announcement: Announcement) => announcement.text;\nconst noAnnouncements: Announcement[] = [];\n\n/** What marks an open modal dialog in ARIA. */\nconst modalDialog = '[aria-modal=\"true\"]';\n\n/**\n * The open modal dialog an element is in: one marked `aria-modal=\"true\"`, or\n * a `<dialog>` opened with `showModal()`, which makes the rest of the page\n * inert. An engine without the `:modal` selector throws on the whole list,\n * so it then looks for the ARIA alone.\n *\n * @param element - Where to look up from.\n * @returns The nearest open modal dialog around it, or `null`.\n */\nfunction closestModalDialog(element: Element): HTMLElement | null {\n try {\n return element.closest<HTMLElement>(`${modalDialog}, dialog:modal`);\n } catch {\n return element.closest<HTMLElement>(modalDialog);\n }\n}\n\n/**\n * The `CopyButton` component renders a button that copies a piece of text to the clipboard, says \"Copied\" for a moment, and announces the result.\n *\n * It's a `Button` with a copy icon and \"Copy\", and takes everything `Button`\n * takes. A click writes `value` through the Clipboard API, or through the\n * older copy command where that's missing or refused, and reports a failure\n * rather than showing \"Copied\". Each outcome is announced through a polite\n * status region portaled out of the button's place, so it stays the last\n * child of a `Control` or `Buttons`: into the open modal dialog the button is\n * in, otherwise where `Portal` puts portaled content (the `ConfigProvider`'s\n * `portalContainer`, or the end of the page). With `showLabel={false}` it's an\n * `IconButton`. For a trigger of your own, use `useClipboard`, which it's\n * built on.\n *\n * @function\n * @param {CopyButtonProps} props - Props for the CopyButton component.\n * @param {React.Ref<HTMLButtonElement>} ref - Forwarded ref to the `<button>`.\n * @returns {JSX.Element} The rendered button.\n * @see {@link https://www.w3.org/WAI/WCAG22/Understanding/status-messages.html | WCAG 4.1.3 Status Messages}\n *\n * @example\n * <CopyButton value=\"https://bestax.io\" />\n *\n * @example\n * <CopyButton value={apiKey} showLabel={false} size=\"small\" />\n */\nexport const CopyButton = forwardRef<HTMLButtonElement, CopyButtonProps>(\n function CopyButton(\n {\n value,\n duration = 2000,\n showLabel = true,\n labels,\n onCopiedChange,\n onCopyError,\n onClick,\n onFocus,\n ...props\n },\n ref\n ) {\n const copyLabel = labels?.copy || 'Copy';\n const copiedLabel = labels?.copied || 'Copied';\n const copiedStatus = labels?.copiedStatus || 'Copied to the clipboard';\n const failedStatus =\n labels?.failedStatus || \"Couldn't copy to the clipboard\";\n\n // The latest outcome. Each gets a new id, so the region announces a\n // repeat too.\n const [announcements, setAnnouncements] = useState(noAnnouncements);\n const countRef = useRef(0);\n const announce = (text: string) => {\n countRef.current += 1;\n setAnnouncements([{ id: String(countRef.current), text }]);\n };\n\n // The open modal dialog the button is in, which the region goes inside:\n // a screen reader ignores a live region behind one. It's looked up when\n // the button takes focus or a click, rather than once as it mounts,\n // since Modal and Sidebar keep their content mounted while closed, and\n // only an open one is `aria-modal`. A region that moves starts empty, so\n // it doesn't say the last outcome again. The popups inside a Modal,\n // Dialog or Command.Dialog portal into it on their own, but this lookup\n // stays: it finds a modal dialog the library didn't render too, such as\n // a `<dialog>` opened with `showModal()`, and unlike theirs, it wins over\n // a `portalContainer`, since a live region outside the dialog says\n // nothing.\n const [dialog, setDialog] = useState<HTMLElement | null>(null);\n const placeRegion = (button: HTMLElement) => {\n const next = closestModalDialog(button);\n if (next === dialog) return;\n setDialog(next);\n setAnnouncements(noAnnouncements);\n };\n\n // The hook fires nothing once this has unmounted, so neither does the\n // announcement.\n const { copy, copied } = useClipboard({\n duration,\n onCopiedChange: next => {\n if (next) announce(copiedStatus);\n onCopiedChange?.(next);\n },\n onCopyError: error => {\n announce(failedStatus);\n onCopyError?.(error);\n },\n });\n\n // The caller's onClick runs first, so a `preventDefault()` there stops\n // the copy, as it stops ToggleButton's toggle.\n const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {\n onClick?.(event);\n if (event.defaultPrevented) return;\n placeRegion(event.currentTarget);\n void copy(value);\n };\n\n const handleFocus = (event: React.FocusEvent<HTMLButtonElement>) => {\n onFocus?.(event);\n placeRegion(event.currentTarget);\n };\n\n const glyph = <Icon>{copied ? <CheckIcon /> : <CopyGlyph />}</Icon>;\n\n // The props are spread first, so it stays a `<button>` that doesn't\n // submit by accident, whatever a JavaScript caller or a spread object\n // passes along.\n const button = showLabel ? (\n <Button\n {...props}\n ref={ref}\n as=\"button\"\n type={buttonType(props.type)}\n onClick={handleClick}\n onFocus={handleFocus}\n >\n {glyph}\n <span>{copied ? copiedLabel : copyLabel}</span>\n </Button>\n ) : (\n // IconButton resolves `type` itself, from the same `buttonType`. Its\n // types refuse `aria-label` beside `aria-labelledby`, which a caller\n // here may pass, and the browser then names it by `aria-labelledby`.\n <IconButton\n {...({\n ...props,\n 'aria-label': props['aria-label'] || copyLabel,\n } as IconButtonProps)}\n ref={ref}\n as=\"button\"\n onClick={handleClick}\n onFocus={handleFocus}\n >\n {glyph}\n </IconButton>\n );\n\n return (\n <>\n {button}\n <Portal container={dialog || undefined}>\n <StatusRegion items={announcements} describe={describeAnnouncement} />\n </Portal>\n </>\n );\n }\n);\n\nCopyButton.displayName = 'CopyButton';\n\nexport default CopyButton;\n"],"names":["_jsxs","_jsx","describeAnnouncement","noAnnouncements","_Fragment"],"mappings":";;;;;;;;;;;;;AA6FA;AACA,MAAM,SAAS,GAAG,OAChBA,IAAA,CAAA,KAAA,EAAA,EACE,KAAK,EAAC,4BAA4B,EAClC,OAAO,EAAC,WAAW,EACnB,IAAI,EAAC,MAAM,EACX,MAAM,EAAC,cAAc,EACrB,WAAW,EAAC,KAAK,EACjB,cAAc,EAAC,OAAO,EACtB,KAAK,EAAC,KAAK,EACX,MAAM,EAAC,KAAK,EACZ,SAAS,EAAC,OAAO,aAEjBC,GAAA,CAAA,MAAA,EAAA,EAAM,CAAC,EAAC,+DAA+D,GAAG,EAC1EA,GAAA,CAAA,MAAA,EAAA,EAAM,CAAC,EAAC,KAAK,EAAC,CAAC,EAAC,KAAK,EAAC,KAAK,EAAC,GAAG,EAAC,MAAM,EAAC,GAAG,EAAC,EAAE,EAAC,GAAG,EAAA,CAAG,CAAA,EAAA,CAChD,CACP;AAQD,MAAMC,sBAAoB,GAAG,CAAC,YAA0B,KAAK,YAAY,CAAC,IAAI;AAC9E,MAAMC,iBAAe,GAAmB,EAAE;AAE1C;AACA,MAAM,WAAW,GAAG,qBAAqB;AAEzC;;;;;;;;AAQG;AACH,SAAS,kBAAkB,CAAC,OAAgB,EAAA;AAC1C,IAAA,IAAI;QACF,OAAO,OAAO,CAAC,OAAO,CAAc,GAAG,WAAW,CAAA,cAAA,CAAgB,CAAC;IACrE;AAAE,IAAA,MAAM;AACN,QAAA,OAAO,OAAO,CAAC,OAAO,CAAc,WAAW,CAAC;IAClD;AACF;AAEA;;;;;;;;;;;;;;;;;;;;;;;;;AAyBG;AACI,MAAM,UAAU,GAAG,UAAU,CAClC,SAAS,UAAU,CACjB,EACE,KAAK,EACL,QAAQ,GAAG,IAAI,EACf,SAAS,GAAG,IAAI,EAChB,MAAM,EACN,cAAc,EACd,WAAW,EACX,OAAO,EACP,OAAO,EACP,GAAG,KAAK,EACT,EACD,GAAG,EAAA;AAEH,IAAA,MAAM,SAAS,GAAG,MAAM,EAAE,IAAI,IAAI,MAAM;AACxC,IAAA,MAAM,WAAW,GAAG,MAAM,EAAE,MAAM,IAAI,QAAQ;AAC9C,IAAA,MAAM,YAAY,GAAG,MAAM,EAAE,YAAY,IAAI,yBAAyB;AACtE,IAAA,MAAM,YAAY,GAChB,MAAM,EAAE,YAAY,IAAI,gCAAgC;;;IAI1D,MAAM,CAAC,aAAa,EAAE,gBAAgB,CAAC,GAAG,QAAQ,CAACA,iBAAe,CAAC;AACnE,IAAA,MAAM,QAAQ,GAAG,MAAM,CAAC,CAAC,CAAC;AAC1B,IAAA,MAAM,QAAQ,GAAG,CAAC,IAAY,KAAI;AAChC,QAAA,QAAQ,CAAC,OAAO,IAAI,CAAC;AACrB,QAAA,gBAAgB,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;AAC5D,IAAA,CAAC;;;;;;;;;;;;IAaD,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,CAAqB,IAAI,CAAC;AAC9D,IAAA,MAAM,WAAW,GAAG,CAAC,MAAmB,KAAI;AAC1C,QAAA,MAAM,IAAI,GAAG,kBAAkB,CAAC,MAAM,CAAC;QACvC,IAAI,IAAI,KAAK,MAAM;YAAE;QACrB,SAAS,CAAC,IAAI,CAAC;QACf,gBAAgB,CAACA,iBAAe,CAAC;AACnC,IAAA,CAAC;;;AAID,IAAA,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,YAAY,CAAC;QACpC,QAAQ;QACR,cAAc,EAAE,IAAI,IAAG;AACrB,YAAA,IAAI,IAAI;gBAAE,QAAQ,CAAC,YAAY,CAAC;AAChC,YAAA,cAAc,GAAG,IAAI,CAAC;QACxB,CAAC;QACD,WAAW,EAAE,KAAK,IAAG;YACnB,QAAQ,CAAC,YAAY,CAAC;AACtB,YAAA,WAAW,GAAG,KAAK,CAAC;QACtB,CAAC;AACF,KAAA,CAAC;;;AAIF,IAAA,MAAM,WAAW,GAAG,CAAC,KAA0C,KAAI;AACjE,QAAA,OAAO,GAAG,KAAK,CAAC;QAChB,IAAI,KAAK,CAAC,gBAAgB;YAAE;AAC5B,QAAA,WAAW,CAAC,KAAK,CAAC,aAAa,CAAC;AAChC,QAAA,KAAK,IAAI,CAAC,KAAK,CAAC;AAClB,IAAA,CAAC;AAED,IAAA,MAAM,WAAW,GAAG,CAAC,KAA0C,KAAI;AACjE,QAAA,OAAO,GAAG,KAAK,CAAC;AAChB,QAAA,WAAW,CAAC,KAAK,CAAC,aAAa,CAAC;AAClC,IAAA,CAAC;AAED,IAAA,MAAM,KAAK,GAAGF,GAAA,CAAC,IAAI,EAAA,EAAA,QAAA,EAAE,MAAM,GAAGA,IAAC,SAAS,EAAA,EAAA,CAAG,GAAGA,IAAC,SAAS,EAAA,EAAA,CAAG,GAAQ;;;;IAKnE,MAAM,MAAM,GAAG,SAAS,IACtBD,IAAA,CAAC,MAAM,EAAA,EAAA,GACD,KAAK,EACT,GAAG,EAAE,GAAG,EACR,EAAE,EAAC,QAAQ,EACX,IAAI,EAAE,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,EAC5B,OAAO,EAAE,WAAW,EACpB,OAAO,EAAE,WAAW,EAAA,QAAA,EAAA,CAEnB,KAAK,EACNC,GAAA,CAAA,MAAA,EAAA,EAAA,QAAA,EAAO,MAAM,GAAG,WAAW,GAAG,SAAS,EAAA,CAAQ,CAAA,EAAA,CACxC,KAKTA,GAAA,CAAC,UAAU,EAAA,EAAA,GACJ;AACH,YAAA,GAAG,KAAK;AACR,YAAA,YAAY,EAAE,KAAK,CAAC,YAAY,CAAC,IAAI,SAAS;AAC3B,SAAA,EACrB,GAAG,EAAE,GAAG,EACR,EAAE,EAAC,QAAQ,EACX,OAAO,EAAE,WAAW,EACpB,OAAO,EAAE,WAAW,YAEnB,KAAK,EAAA,CACK,CACd;IAED,QACED,IAAA,CAAAI,QAAA,EAAA,EAAA,QAAA,EAAA,CACG,MAAM,EACPH,GAAA,CAAC,MAAM,EAAA,EAAC,SAAS,EAAE,MAAM,IAAI,SAAS,EAAA,QAAA,EACpCA,GAAA,CAAC,YAAY,EAAA,EAAC,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAEC,sBAAoB,EAAA,CAAI,EAAA,CAC/D,CAAA,EAAA,CACR;AAEP,CAAC;AAGH,UAAU,CAAC,WAAW,GAAG,YAAY;;"}
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
/* @allxsmith/bestax-bulma — AI agents: see AGENTS.md in the package root, or https://bestax.io/llms.txt */
|
|
2
2
|
'use client';
|
|
3
3
|
import { jsx } from 'react/jsx-runtime';
|
|
4
|
-
import
|
|
4
|
+
import { forwardRef, useEffect } from 'react';
|
|
5
5
|
import { Button } from './Button.js';
|
|
6
6
|
import { Icon } from './Icon.js';
|
|
7
7
|
import { buttonType } from '../helpers/buttonType.js';
|
|
8
8
|
import { usePrefixedClassNames, classNames } from '../helpers/classNames.js';
|
|
9
9
|
import { isDev, warnOnce } from '../helpers/devWarnings.js';
|
|
10
10
|
import { flattenChildren } from '../helpers/flattenChildren.js';
|
|
11
|
+
import { isElementOf } from '../helpers/elementType.js';
|
|
11
12
|
|
|
12
13
|
/** Whether a string has something in it besides spaces. */
|
|
13
14
|
const hasText = (value) => typeof value === 'string' && value.trim() !== '';
|
|
@@ -24,7 +25,7 @@ function iconOf(node) {
|
|
|
24
25
|
if (items.length === 0)
|
|
25
26
|
return null;
|
|
26
27
|
const [only] = items;
|
|
27
|
-
if (items.length === 1 &&
|
|
28
|
+
if (items.length === 1 && isElementOf(only, Icon)) {
|
|
28
29
|
return only;
|
|
29
30
|
}
|
|
30
31
|
return jsx(Icon, { children: items });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"IconButton.js","sources":["../../../src/elements/IconButton.tsx"],"sourcesContent":["import React, { forwardRef, useEffect } from 'react';\nimport { Button, type ButtonOwnProps, type ButtonProps } from './Button';\nimport { Icon } from './Icon';\nimport { buttonType } from '../helpers/buttonType';\nimport { classNames, usePrefixedClassNames } from '../helpers/classNames';\nimport { isDev, warnOnce } from '../helpers/devWarnings';\nimport { flattenChildren } from '../helpers/flattenChildren';\nimport type { PolymorphicComponent } from '../helpers/polymorphic';\n\n/**\n * The IconButton component's own props: everything `Button` takes, plus the\n * icon.\n */\nexport interface IconButtonOwnProps extends ButtonOwnProps {\n /**\n * The glyph. A string is an `Icon` name, drawn from the icon library\n * `ConfigProvider` sets. An `<Icon>` renders as given, so it can carry its\n * own `variant` or `library`. Any other node, such as an SVG or a component\n * from an icon package, goes inside an `Icon`, which gives it the `.icon`\n * wrapper Bulma keeps square. It's decorative: the button's name says what\n * it does. Give the glyph here or as `children`, not both: with both, it\n * draws `icon` and warns in development. With neither, such as an empty\n * string from `cond ? 'trash' : ''`, the button is an empty square, and that\n * warns in development too.\n */\n icon?: React.ReactNode;\n /**\n * The glyph as children, the way Mantine, MUI and Chakra take it: an\n * `<Icon>` renders as given, and any other node, such as an SVG, goes\n * inside an `Icon`, as with `icon`. Left out when `icon` is given, which\n * warns in development. Text warns too, since `aria-label` replaces it as\n * the name: a button with text is a `Button`.\n */\n children?: React.ReactNode;\n /**\n * Makes the button a circle the size of its square. The circle comes from\n * bestax's CSS, in `bestax.css` and its variants, or in `extras.css` next\n * to your own Bulma. Without it, Bulma's `is-rounded` draws a pill a little\n * wider than it is tall.\n */\n isRounded?: boolean;\n}\n\n/** Names an IconButton by the text of another element. */\nexport interface IconButtonLabelledBy {\n /** The id of the element whose text names the button, in place of `aria-label`. */\n 'aria-labelledby': string;\n /**\n * Not with `aria-labelledby`, which already names the button.\n * Declared so the two can't be passed together.\n */\n 'aria-label'?: undefined;\n}\n\n/** Names an IconButton with `aria-label`, the usual way. */\nexport interface IconButtonLabel {\n /**\n * The button's name: what a screen reader announces and what a voice\n * control user says to press it. Required, or `aria-labelledby` in its\n * place, since an icon has no text to name it by. Name the action\n * (\"Delete\"), not the picture (\"Trash\"). An empty or blank one warns in\n * development.\n */\n 'aria-label': string;\n /** Not with `aria-label`. Declared so the two can't be passed together. */\n 'aria-labelledby'?: undefined;\n}\n\n/**\n * How an IconButton gets its name: `aria-label`, or `aria-labelledby` in its\n * place. `IconButtonLabel` comes last because TypeScript names the union's\n * last member when neither is given: under \"No overload matches this call\",\n * each overload's error ends \"Property ''aria-label'' is missing … but\n * required in type 'IconButtonLabel'\".\n */\nexport type IconButtonName = IconButtonLabelledBy | IconButtonLabel;\n\n/**\n * Props for the IconButton component. The DOM attributes and the `ref` follow\n * `as`, exactly as they do on `Button`.\n *\n * @extraProp {string} [type='button'] - On a `<button>`, `'button'`, `'submit'` or `'reset'`, and `'button'` unless you pass one of the other two, so it never submits a form by accident. With another `as`, whatever that element or component takes, passed through as given, such as an `<a>`'s MIME type.\n * @extraProp {PolymorphicRef<React.ElementType>} [ref] - Ref forwarded to the element `as` renders, typed from `as`.\n */\nexport type IconButtonProps<T extends React.ElementType = 'button'> =\n IconButtonOwnProps &\n IconButtonName &\n Omit<\n React.ComponentPropsWithoutRef<T>,\n keyof IconButtonOwnProps | 'aria-label' | 'aria-labelledby' | 'as'\n > & {\n /**\n * Render as a `<button>`, an `<a>`, or a custom component such as a\n * router `Link`, as on `Button`.\n * @defaultValue 'button'\n */\n as?: T;\n };\n\n/**\n * The shape the implementation destructures. The public contract is the\n * generic `IconButtonProps<T>` above, and the body can't see through `T`, so\n * it reads the widest form of the props it touches.\n */\ntype IconButtonImplProps = IconButtonOwnProps & {\n as?: React.ElementType;\n type?: unknown;\n 'aria-label'?: unknown;\n 'aria-labelledby'?: unknown;\n};\n\n/** Whether a string has something in it besides spaces. */\nconst hasText = (value: unknown) =>\n typeof value === 'string' && value.trim() !== '';\n\n/** Whether a string is only spaces, which JSX leaves between elements. */\nconst isBlank = (node: React.ReactNode) =>\n typeof node === 'string' && node.trim() === '';\n\n/**\n * The glyph in one `.icon`, which is what Bulma keeps square: an `<Icon>` as\n * given, Fragments opened up and blank text left out, and anything else\n * inside an `Icon`. Null when there's nothing to draw, so an empty Fragment\n * or array counts as no glyph.\n */\nfunction iconOf(node: React.ReactNode) {\n const items = flattenChildren(node).filter(item => !isBlank(item));\n if (items.length === 0) return null;\n const [only] = items;\n if (items.length === 1 && React.isValidElement(only) && only.type === Icon) {\n return only;\n }\n return <Icon>{items}</Icon>;\n}\n\n/** The glyph `icon` draws: a string is an `Icon` name, the rest as `iconOf`. */\nfunction iconFromProp(icon: React.ReactNode) {\n if (typeof icon !== 'string') return iconOf(icon);\n return hasText(icon) ? <Icon name={icon} /> : null;\n}\n\n/**\n * The `IconButton` component renders a button that shows only an icon, and requires a name for it.\n *\n * It renders through `Button`, so it takes everything `Button` takes, and\n * adds the icon and a name the types require: `aria-label`, or\n * `aria-labelledby` in its place. Bulma keeps a button whose only child is an\n * `.icon` square at every size, and `isRounded` makes it a circle. It has no\n * tooltip of its own: wrap it in `Tooltip` for a description.\n *\n * @function\n * @param {IconButtonProps} props - Props for the IconButton component.\n * @param {React.Ref} ref - Forwarded ref to the element `as` renders.\n * @returns {JSX.Element} The rendered button, anchor, or custom element.\n * @see {@link https://bulma.io/documentation/elements/button/ | Bulma Button documentation}\n *\n * @example\n * <IconButton icon=\"trash\" aria-label=\"Delete\" color=\"danger\" />\n *\n * @example\n * <IconButton aria-label=\"Settings\">\n * <Icon name=\"cog\" />\n * </IconButton>\n */\nexport const IconButton = forwardRef(function IconButton(\n props: IconButtonProps,\n ref: React.Ref<HTMLElement>\n) {\n const { icon, children, className, ...rest } = props as IconButtonImplProps;\n\n const iconButtonClass = usePrefixedClassNames('icon-button');\n\n const unnamed =\n !hasText(rest['aria-label']) && !hasText(rest['aria-labelledby']);\n // Each way of giving the glyph counts only if it draws something, so an\n // empty Fragment or array as `icon` doesn't hide real children, and an\n // empty one as children doesn't warn.\n const fromIcon = iconFromProp(icon);\n const fromChildren = iconOf(children);\n const both = fromIcon !== null && fromChildren !== null;\n // One glyph, from `icon` or else from `children`, so the button's only\n // child is one `.icon`, which Bulma keeps square, and round with\n // `isRounded`.\n const glyph = fromIcon ?? fromChildren;\n const empty = glyph === null;\n // Only top-level text, Fragments opened up, and only in development, where\n // the warning below can say something about it.\n const textInside =\n isDev() &&\n flattenChildren(children).some(\n child => typeof child === 'number' || hasText(child)\n );\n useEffect(() => {\n if (unnamed) {\n warnOnce(\n 'IconButton:unnamed',\n '[bestax-bulma] <IconButton> has no accessible name. Pass ' +\n 'aria-label naming what it does (\"Delete\"), or aria-labelledby, ' +\n 'so a screen reader has something to announce.'\n );\n }\n if (empty) {\n warnOnce(\n 'IconButton:no-glyph',\n '[bestax-bulma] <IconButton> has no glyph: neither icon nor ' +\n 'children draws one, so it renders an empty square. Pass an icon ' +\n 'name, an <Icon> or another node.'\n );\n }\n if (both) {\n warnOnce(\n 'IconButton:icon-and-children',\n '[bestax-bulma] <IconButton> was given both icon and children. It ' +\n 'draws icon and leaves the children out: give the glyph one way.'\n );\n }\n if (textInside) {\n warnOnce(\n 'IconButton:text',\n '[bestax-bulma] <IconButton> was given text as children. Its ' +\n 'aria-label replaces the text as its name, so a button with text ' +\n 'is a <Button>.'\n );\n }\n }, [unnamed, empty, both, textInside]);\n\n // `type` is a button's only on a `<button>`. On an `<a>` it's the MIME hint,\n // and a custom component owns what it means, so those get it as given.\n const tag = rest.as ?? 'button';\n const typeProp =\n tag === 'button' ? { type: buttonType(rest.type) } : undefined;\n\n return (\n <Button\n // IconButton is polymorphic through to Button, which resolves the\n // element from `as`. TS can't infer that through the spread, so the\n // props and the ref are read at Button's default element.\n {...(rest as ButtonProps)}\n {...typeProp}\n ref={ref as React.Ref<HTMLButtonElement>}\n className={classNames(iconButtonClass, className)}\n >\n {glyph}\n </Button>\n );\n}) as PolymorphicComponent<IconButtonOwnProps & IconButtonName, 'button'>;\n\nIconButton.displayName = 'IconButton';\n\nexport default IconButton;\n"],"names":["_jsx"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"IconButton.js","sources":["../../../src/elements/IconButton.tsx"],"sourcesContent":["import React, { forwardRef, useEffect } from 'react';\nimport { Button, type ButtonOwnProps, type ButtonProps } from './Button';\nimport { Icon } from './Icon';\nimport { buttonType } from '../helpers/buttonType';\nimport { classNames, usePrefixedClassNames } from '../helpers/classNames';\nimport { isDev, warnOnce } from '../helpers/devWarnings';\nimport { flattenChildren } from '../helpers/flattenChildren';\nimport { isElementOf } from '../helpers/elementType';\nimport type { PolymorphicComponent } from '../helpers/polymorphic';\n\n/**\n * The IconButton component's own props: everything `Button` takes, plus the\n * icon.\n */\nexport interface IconButtonOwnProps extends ButtonOwnProps {\n /**\n * The glyph. A string is an `Icon` name, drawn from the icon library\n * `ConfigProvider` sets. An `<Icon>` renders as given, so it can carry its\n * own `variant` or `library`. Any other node, such as an SVG or a component\n * from an icon package, goes inside an `Icon`, which gives it the `.icon`\n * wrapper Bulma keeps square. It's decorative: the button's name says what\n * it does. Give the glyph here or as `children`, not both: with both, it\n * draws `icon` and warns in development. With neither, such as an empty\n * string from `cond ? 'trash' : ''`, the button is an empty square, and that\n * warns in development too.\n */\n icon?: React.ReactNode;\n /**\n * The glyph as children, the way Mantine, MUI and Chakra take it: an\n * `<Icon>` renders as given, and any other node, such as an SVG, goes\n * inside an `Icon`, as with `icon`. Left out when `icon` is given, which\n * warns in development. Text warns too, since `aria-label` replaces it as\n * the name: a button with text is a `Button`.\n */\n children?: React.ReactNode;\n /**\n * Makes the button a circle the size of its square. The circle comes from\n * bestax's CSS, in `bestax.css` and its variants, or in `extras.css` next\n * to your own Bulma. Without it, Bulma's `is-rounded` draws a pill a little\n * wider than it is tall.\n */\n isRounded?: boolean;\n}\n\n/** Names an IconButton by the text of another element. */\nexport interface IconButtonLabelledBy {\n /** The id of the element whose text names the button, in place of `aria-label`. */\n 'aria-labelledby': string;\n /**\n * Not with `aria-labelledby`, which already names the button.\n * Declared so the two can't be passed together.\n */\n 'aria-label'?: undefined;\n}\n\n/** Names an IconButton with `aria-label`, the usual way. */\nexport interface IconButtonLabel {\n /**\n * The button's name: what a screen reader announces and what a voice\n * control user says to press it. Required, or `aria-labelledby` in its\n * place, since an icon has no text to name it by. Name the action\n * (\"Delete\"), not the picture (\"Trash\"). An empty or blank one warns in\n * development.\n */\n 'aria-label': string;\n /** Not with `aria-label`. Declared so the two can't be passed together. */\n 'aria-labelledby'?: undefined;\n}\n\n/**\n * How an IconButton gets its name: `aria-label`, or `aria-labelledby` in its\n * place. `IconButtonLabel` comes last because TypeScript names the union's\n * last member when neither is given: under \"No overload matches this call\",\n * each overload's error ends \"Property ''aria-label'' is missing … but\n * required in type 'IconButtonLabel'\".\n */\nexport type IconButtonName = IconButtonLabelledBy | IconButtonLabel;\n\n/**\n * Props for the IconButton component. The DOM attributes and the `ref` follow\n * `as`, exactly as they do on `Button`.\n *\n * @extraProp {string} [type='button'] - On a `<button>`, `'button'`, `'submit'` or `'reset'`, and `'button'` unless you pass one of the other two, so it never submits a form by accident. With another `as`, whatever that element or component takes, passed through as given, such as an `<a>`'s MIME type.\n * @extraProp {PolymorphicRef<React.ElementType>} [ref] - Ref forwarded to the element `as` renders, typed from `as`.\n */\nexport type IconButtonProps<T extends React.ElementType = 'button'> =\n IconButtonOwnProps &\n IconButtonName &\n Omit<\n React.ComponentPropsWithoutRef<T>,\n keyof IconButtonOwnProps | 'aria-label' | 'aria-labelledby' | 'as'\n > & {\n /**\n * Render as a `<button>`, an `<a>`, or a custom component such as a\n * router `Link`, as on `Button`.\n * @defaultValue 'button'\n */\n as?: T;\n };\n\n/**\n * The shape the implementation destructures. The public contract is the\n * generic `IconButtonProps<T>` above, and the body can't see through `T`, so\n * it reads the widest form of the props it touches.\n */\ntype IconButtonImplProps = IconButtonOwnProps & {\n as?: React.ElementType;\n type?: unknown;\n 'aria-label'?: unknown;\n 'aria-labelledby'?: unknown;\n};\n\n/** Whether a string has something in it besides spaces. */\nconst hasText = (value: unknown) =>\n typeof value === 'string' && value.trim() !== '';\n\n/** Whether a string is only spaces, which JSX leaves between elements. */\nconst isBlank = (node: React.ReactNode) =>\n typeof node === 'string' && node.trim() === '';\n\n/**\n * The glyph in one `.icon`, which is what Bulma keeps square: an `<Icon>` as\n * given, Fragments opened up and blank text left out, and anything else\n * inside an `Icon`. Null when there's nothing to draw, so an empty Fragment\n * or array counts as no glyph.\n */\nfunction iconOf(node: React.ReactNode) {\n const items = flattenChildren(node).filter(item => !isBlank(item));\n if (items.length === 0) return null;\n const [only] = items;\n if (items.length === 1 && isElementOf(only, Icon)) {\n return only;\n }\n return <Icon>{items}</Icon>;\n}\n\n/** The glyph `icon` draws: a string is an `Icon` name, the rest as `iconOf`. */\nfunction iconFromProp(icon: React.ReactNode) {\n if (typeof icon !== 'string') return iconOf(icon);\n return hasText(icon) ? <Icon name={icon} /> : null;\n}\n\n/**\n * The `IconButton` component renders a button that shows only an icon, and requires a name for it.\n *\n * It renders through `Button`, so it takes everything `Button` takes, and\n * adds the icon and a name the types require: `aria-label`, or\n * `aria-labelledby` in its place. Bulma keeps a button whose only child is an\n * `.icon` square at every size, and `isRounded` makes it a circle. It has no\n * tooltip of its own: wrap it in `Tooltip` for a description.\n *\n * @function\n * @param {IconButtonProps} props - Props for the IconButton component.\n * @param {React.Ref} ref - Forwarded ref to the element `as` renders.\n * @returns {JSX.Element} The rendered button, anchor, or custom element.\n * @see {@link https://bulma.io/documentation/elements/button/ | Bulma Button documentation}\n *\n * @example\n * <IconButton icon=\"trash\" aria-label=\"Delete\" color=\"danger\" />\n *\n * @example\n * <IconButton aria-label=\"Settings\">\n * <Icon name=\"cog\" />\n * </IconButton>\n */\nexport const IconButton = forwardRef(function IconButton(\n props: IconButtonProps,\n ref: React.Ref<HTMLElement>\n) {\n const { icon, children, className, ...rest } = props as IconButtonImplProps;\n\n const iconButtonClass = usePrefixedClassNames('icon-button');\n\n const unnamed =\n !hasText(rest['aria-label']) && !hasText(rest['aria-labelledby']);\n // Each way of giving the glyph counts only if it draws something, so an\n // empty Fragment or array as `icon` doesn't hide real children, and an\n // empty one as children doesn't warn.\n const fromIcon = iconFromProp(icon);\n const fromChildren = iconOf(children);\n const both = fromIcon !== null && fromChildren !== null;\n // One glyph, from `icon` or else from `children`, so the button's only\n // child is one `.icon`, which Bulma keeps square, and round with\n // `isRounded`.\n const glyph = fromIcon ?? fromChildren;\n const empty = glyph === null;\n // Only top-level text, Fragments opened up, and only in development, where\n // the warning below can say something about it.\n const textInside =\n isDev() &&\n flattenChildren(children).some(\n child => typeof child === 'number' || hasText(child)\n );\n useEffect(() => {\n if (unnamed) {\n warnOnce(\n 'IconButton:unnamed',\n '[bestax-bulma] <IconButton> has no accessible name. Pass ' +\n 'aria-label naming what it does (\"Delete\"), or aria-labelledby, ' +\n 'so a screen reader has something to announce.'\n );\n }\n if (empty) {\n warnOnce(\n 'IconButton:no-glyph',\n '[bestax-bulma] <IconButton> has no glyph: neither icon nor ' +\n 'children draws one, so it renders an empty square. Pass an icon ' +\n 'name, an <Icon> or another node.'\n );\n }\n if (both) {\n warnOnce(\n 'IconButton:icon-and-children',\n '[bestax-bulma] <IconButton> was given both icon and children. It ' +\n 'draws icon and leaves the children out: give the glyph one way.'\n );\n }\n if (textInside) {\n warnOnce(\n 'IconButton:text',\n '[bestax-bulma] <IconButton> was given text as children. Its ' +\n 'aria-label replaces the text as its name, so a button with text ' +\n 'is a <Button>.'\n );\n }\n }, [unnamed, empty, both, textInside]);\n\n // `type` is a button's only on a `<button>`. On an `<a>` it's the MIME hint,\n // and a custom component owns what it means, so those get it as given.\n const tag = rest.as ?? 'button';\n const typeProp =\n tag === 'button' ? { type: buttonType(rest.type) } : undefined;\n\n return (\n <Button\n // IconButton is polymorphic through to Button, which resolves the\n // element from `as`. TS can't infer that through the spread, so the\n // props and the ref are read at Button's default element.\n {...(rest as ButtonProps)}\n {...typeProp}\n ref={ref as React.Ref<HTMLButtonElement>}\n className={classNames(iconButtonClass, className)}\n >\n {glyph}\n </Button>\n );\n}) as PolymorphicComponent<IconButtonOwnProps & IconButtonName, 'button'>;\n\nIconButton.displayName = 'IconButton';\n\nexport default IconButton;\n"],"names":["_jsx"],"mappings":"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"}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
/* @allxsmith/bestax-bulma — AI agents: see AGENTS.md in the package root, or https://bestax.io/llms.txt */
|
|
2
2
|
'use client';
|
|
3
3
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
4
|
-
import
|
|
4
|
+
import { forwardRef, useContext, useEffect, useMemo } from 'react';
|
|
5
5
|
import { usePrefixedClassNames, classNames } from '../helpers/classNames.js';
|
|
6
6
|
import { useBulmaClasses } from '../helpers/useBulmaClasses.js';
|
|
7
7
|
import { withSubComponents } from '../helpers/withSubComponents.js';
|
|
8
8
|
import { isGiven } from '../helpers/isGiven.js';
|
|
9
9
|
import { flattenChildren } from '../helpers/flattenChildren.js';
|
|
10
|
+
import { isElementOf } from '../helpers/elementType.js';
|
|
10
11
|
import { useNumberFormat } from '../helpers/localeFormat.js';
|
|
11
12
|
import { warnOnce } from '../helpers/devWarnings.js';
|
|
12
13
|
import { NumberFormatter } from './NumberFormatter.js';
|
|
@@ -163,7 +164,7 @@ const StatRoot = forwardRef(({ className, label, value, format, locale, change,
|
|
|
163
164
|
// warning below know what's there. A part rendered from inside another
|
|
164
165
|
// component isn't seen.
|
|
165
166
|
const parts = flattenChildren(children);
|
|
166
|
-
const hasPart = (type) => parts.some(part =>
|
|
167
|
+
const hasPart = (type) => parts.some(part => isElementOf(part, type));
|
|
167
168
|
const hasValuePart = hasPart(StatValue);
|
|
168
169
|
const labelled = isGiven(label) || hasPart(StatLabel);
|
|
169
170
|
// Without a label, the figure has no name and the values no term.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Stat.js","sources":["../../../src/elements/Stat.tsx"],"sourcesContent":["import React, { forwardRef, useContext, useEffect, useMemo } from 'react';\nimport { classNames, usePrefixedClassNames } from '../helpers/classNames';\nimport {\n useBulmaClasses,\n BulmaClassesProps,\n validColors,\n} from '../helpers/useBulmaClasses';\nimport { withSubComponents } from '../helpers/withSubComponents';\nimport { isGiven } from '../helpers/isGiven';\nimport { flattenChildren } from '../helpers/flattenChildren';\nimport { useNumberFormat } from '../helpers/localeFormat';\nimport { warnOnce } from '../helpers/devWarnings';\nimport { NumberFormatter } from './NumberFormatter';\nimport { Skeleton } from './Skeleton';\nimport { StatScopeContext } from './statContext';\n\n/** The colors a Stat's change can take, the union `messageColor` takes. */\nexport type StatChangeColor =\n 'primary' | 'link' | 'info' | 'success' | 'warning' | 'danger';\n\n/** How a change is written when no format is given: a signed percentage. */\nconst DEFAULT_CHANGE_FORMAT: Intl.NumberFormatOptions = {\n style: 'percent',\n signDisplay: 'exceptZero',\n maximumFractionDigits: 1,\n};\n\n/**\n * Props for the Stat component. The helper props, the other attributes and\n * the `ref` land on its root: a `<div>` inside `Stats`, or its own `<dl>` on\n * its own.\n *\n * @extraProp {React.Ref<HTMLElement>} [ref] - Ref forwarded to the root, a `<div>` inside `Stats` or a `<dl>` on its own.\n */\nexport interface StatProps\n extends\n Omit<React.HTMLAttributes<HTMLElement>, 'color'>,\n Omit<BulmaClassesProps, 'color' | 'backgroundColor'> {\n /** Additional CSS classes to apply. */\n className?: string;\n /**\n * What the figure is (\"Revenue\"), as a `Stat.Label` before the children.\n * A Stat needs one, here or as a `Stat.Label` child: it's the figure's\n * name for assistive tech, and a definition list's values belong to a\n * term. A Stat with neither a `label` nor a `Stat.Label` child warns in\n * development.\n */\n label?: React.ReactNode;\n /**\n * The figure, as a `Stat.Value` after the children. A number is written by\n * `NumberFormatter` with `format` and `locale`; anything else, such as\n * \"N/A\", renders as given, and `0` renders. `undefined`, `null`, a boolean\n * and `''` leave it empty, and its `<dd>` still renders, one line of the\n * figure tall, while the Stat has a label, so the label keeps its\n * definition while the figure is missing. A `Stat.Value` child takes its\n * place: with one, `value` and its `<dd>` are left out.\n */\n value?: React.ReactNode;\n /**\n * `Intl.NumberFormat` options for a number `value`, as NumberFormatter's\n * `format`. A `value` that isn't a number ignores it.\n */\n format?: Intl.NumberFormatOptions;\n /**\n * The locale for `value` and `change`, as NumberFormatter's `locale`.\n * Unset, it's the `LocaleProvider`'s around it, and with neither, the\n * runtime's own, so give one, here or through a `LocaleProvider`, on a\n * page rendered on the server.\n */\n locale?: string;\n /**\n * The change, as a `Stat.Change` after the value. With the default format\n * it's a fraction (`0.12` for 12%); with your own `changeFormat`, it's in\n * that format's terms. It's written with its sign, so the direction is in\n * the text.\n */\n change?: number;\n /**\n * `Intl.NumberFormat` options for `change`. Yours replace the default, and\n * `signDisplay: 'exceptZero'` is added unless you set a `signDisplay`. With\n * `signDisplay: 'never'`, or `'negative'` on a rise, the text carries no\n * direction: the arrow is hidden from assistive tech and the tone is color\n * alone, so keep a sign, or say the direction in `description`.\n * @defaultValue { style: 'percent', signDisplay: 'exceptZero', maximumFractionDigits: 1 }\n */\n changeFormat?: Intl.NumberFormatOptions;\n /**\n * The change's color. Unset, it's the sign's: `success` up, `danger` down,\n * and none at zero. Set it where up is bad news, such as a cost or an\n * error rate.\n */\n changeColor?: StatChangeColor;\n /** A note under the figure (\"since last month\"), as a `Stat.Description` last. */\n description?: React.ReactNode;\n /**\n * The figure's size, a step of Bulma's size scale. The label, the change\n * and the note stay small.\n */\n size?: 'small' | 'medium' | 'large';\n /**\n * Shows a `Skeleton` in place of `value`, one line of the figure tall,\n * and sets `aria-busy` on the Stat. The label, the change and the note\n * render as given. With a `Stat.Value` child, it only sets `aria-busy`:\n * the child renders as given, so put a `Skeleton` in it yourself.\n */\n loading?: boolean;\n /**\n * `Stat.Label`, `Stat.Value`, `Stat.Change` and `Stat.Description`, for\n * anything the props don't cover, such as a unit or an info tip. They\n * render between `label` and `value`. They're this Stat's own parts, so\n * not another Stat: that would be a list inside this one's. The Stat finds\n * a `Stat.Label` or `Stat.Value` among them by type, so one rendered from\n * inside a component of your own isn't seen.\n */\n children?: React.ReactNode;\n /**\n * Text color helper. It colors the figure; the label, the change and the\n * note keep their own colors. Inside `Stats`, a Stat without one takes the\n * row's.\n */\n textColor?: (typeof validColors)[number] | 'inherit' | 'current';\n /** Background color helper. */\n bgColor?: (typeof validColors)[number] | 'inherit' | 'current';\n}\n\n/**\n * Props for Stat.Label, what the figure is. The helper props, the other\n * attributes and the `ref` land on its `<dt>`.\n *\n * @extraProp {React.Ref<HTMLElement>} [ref] - Ref forwarded to the `<dt>`.\n */\nexport interface StatLabelProps\n extends\n Omit<React.HTMLAttributes<HTMLElement>, 'color'>,\n Omit<BulmaClassesProps, 'color' | 'backgroundColor'> {\n /** Additional CSS classes to apply. */\n className?: string;\n /**\n * The label. A button may sit in it, such as one that opens a `Popover`\n * about the figure, but not a heading, which HTML doesn't allow here.\n */\n children?: React.ReactNode;\n /** Text color helper. */\n textColor?: (typeof validColors)[number] | 'inherit' | 'current';\n /** Background color helper. */\n bgColor?: (typeof validColors)[number] | 'inherit' | 'current';\n}\n\n/**\n * Props for Stat.Value, the figure. The helper props, the other attributes\n * and the `ref` land on its `<dd>`.\n *\n * @extraProp {React.Ref<HTMLElement>} [ref] - Ref forwarded to the `<dd>`.\n */\nexport interface StatValueProps\n extends\n Omit<React.HTMLAttributes<HTMLElement>, 'color'>,\n Omit<BulmaClassesProps, 'color' | 'backgroundColor'> {\n /** Additional CSS classes to apply. */\n className?: string;\n /**\n * The figure, written as you give it, so write a number with\n * `NumberFormatter`.\n */\n children?: React.ReactNode;\n /** Text color helper. */\n textColor?: (typeof validColors)[number] | 'inherit' | 'current';\n /** Background color helper. */\n bgColor?: (typeof validColors)[number] | 'inherit' | 'current';\n}\n\n/**\n * Props for Stat.Description, a note under the figure. The helper props, the\n * other attributes and the `ref` land on its `<dd>`.\n *\n * @extraProp {React.Ref<HTMLElement>} [ref] - Ref forwarded to the `<dd>`.\n */\nexport interface StatDescriptionProps\n extends\n Omit<React.HTMLAttributes<HTMLElement>, 'color'>,\n Omit<BulmaClassesProps, 'color' | 'backgroundColor'> {\n /** Additional CSS classes to apply. */\n className?: string;\n /** The note, such as \"since last month\". */\n children?: React.ReactNode;\n /** Text color helper. */\n textColor?: (typeof validColors)[number] | 'inherit' | 'current';\n /** Background color helper. */\n bgColor?: (typeof validColors)[number] | 'inherit' | 'current';\n}\n\n/**\n * Props for Stat.Change, a change written with its sign. The helper props,\n * the other attributes and the `ref` land on its `<dd>`.\n *\n * @extraProp {React.Ref<HTMLElement>} [ref] - Ref forwarded to the `<dd>`.\n */\nexport interface StatChangeProps\n extends\n Omit<React.HTMLAttributes<HTMLElement>, 'color' | 'children'>,\n Omit<BulmaClassesProps, 'color' | 'backgroundColor'> {\n /** Additional CSS classes to apply. */\n className?: string;\n /**\n * The change, as Stat's `change`. One that rounds to zero in its format,\n * such as `0.0004` at one decimal place of a percentage, reads as zero, so\n * it gets no arrow and no color of its own.\n */\n value: number;\n /**\n * As Stat's `changeFormat`: yours replace the default, and\n * `signDisplay: 'exceptZero'` is added unless you set a `signDisplay`.\n * @defaultValue { style: 'percent', signDisplay: 'exceptZero', maximumFractionDigits: 1 }\n */\n format?: Intl.NumberFormatOptions;\n /** As Stat's `locale`. Unset, the Stat's. */\n locale?: string;\n /**\n * As Stat's `changeColor`. Unset, it's the sign's: `success` up, `danger`\n * down, and none at zero. Set, it applies at zero too.\n */\n color?: StatChangeColor;\n}\n\n/**\n * What the figure is: a `<dt>`, small and in the weaker text color.\n *\n * @function\n * @param {StatLabelProps} props - Props for Stat.Label.\n * @param {React.Ref<HTMLElement>} ref - Forwarded ref to the `<dt>`.\n * @returns {JSX.Element} The rendered label.\n *\n * @example\n * <Stat.Label>Revenue</Stat.Label>\n */\nexport const StatLabel = forwardRef<HTMLElement, StatLabelProps>(\n ({ className, textColor, bgColor, children, ...props }, ref) => {\n const { bulmaHelperClasses, rest } = useBulmaClasses({\n color: textColor,\n backgroundColor: bgColor,\n ...props,\n });\n const labelClass = usePrefixedClassNames('stat-label');\n return (\n <dt\n ref={ref}\n className={classNames(labelClass, bulmaHelperClasses, className)}\n {...rest}\n >\n {children}\n </dt>\n );\n }\n);\n\nStatLabel.displayName = 'Stat.Label';\n\n/**\n * The figure: a `<dd>`, large, in the Stat's color, with digits of one width so a figure that updates doesn't shift.\n *\n * @function\n * @param {StatValueProps} props - Props for Stat.Value.\n * @param {React.Ref<HTMLElement>} ref - Forwarded ref to the `<dd>`.\n * @returns {JSX.Element} The rendered figure.\n *\n * @example\n * <Stat.Value>\n * <NumberFormatter value={182} format={{ style: 'unit', unit: 'millisecond' }} />\n * </Stat.Value>\n */\nexport const StatValue = forwardRef<HTMLElement, StatValueProps>(\n ({ className, textColor, bgColor, children, ...props }, ref) => {\n const { bulmaHelperClasses, rest } = useBulmaClasses({\n color: textColor,\n backgroundColor: bgColor,\n ...props,\n });\n const valueClass = usePrefixedClassNames('stat-value');\n return (\n <dd\n ref={ref}\n className={classNames(valueClass, bulmaHelperClasses, className)}\n {...rest}\n >\n {children}\n </dd>\n );\n }\n);\n\nStatValue.displayName = 'Stat.Value';\n\n/**\n * A note under the figure: a `<dd>`, small and in the weaker text color.\n *\n * @function\n * @param {StatDescriptionProps} props - Props for Stat.Description.\n * @param {React.Ref<HTMLElement>} ref - Forwarded ref to the `<dd>`.\n * @returns {JSX.Element} The rendered note.\n *\n * @example\n * <Stat.Description>since last month</Stat.Description>\n */\nexport const StatDescription = forwardRef<HTMLElement, StatDescriptionProps>(\n ({ className, textColor, bgColor, children, ...props }, ref) => {\n const { bulmaHelperClasses, rest } = useBulmaClasses({\n color: textColor,\n backgroundColor: bgColor,\n ...props,\n });\n const descriptionClass = usePrefixedClassNames('stat-description');\n return (\n <dd\n ref={ref}\n className={classNames(descriptionClass, bulmaHelperClasses, className)}\n {...rest}\n >\n {children}\n </dd>\n );\n }\n);\n\nStatDescription.displayName = 'Stat.Description';\n\n/**\n * A change written with its sign, such as \"+12%\", after an arrow that repeats the direction: a `<dd>`, green when it's up and red when it's down unless `color` says otherwise.\n *\n * The sign is in the text, written by `Intl` for the locale, so a screen\n * reader hears the direction; the arrow is hidden from assistive tech, and\n * the color repeats the direction for sighted readers rather than carry it.\n *\n * @function\n * @param {StatChangeProps} props - Props for Stat.Change.\n * @param {React.Ref<HTMLElement>} ref - Forwarded ref to the `<dd>`.\n * @returns {JSX.Element} The rendered change.\n *\n * @example\n * <Stat.Change value={-0.08} color=\"success\" />\n */\nexport const StatChange = forwardRef<HTMLElement, StatChangeProps>(\n ({ className, value, format, locale, color, ...props }, ref) => {\n const { bulmaHelperClasses, rest } = useBulmaClasses(props);\n const scope = useContext(StatScopeContext);\n const ownLocale = locale || scope.locale;\n const options = format\n ? { ...format, signDisplay: format.signDisplay || 'exceptZero' }\n : DEFAULT_CHANGE_FORMAT;\n // A change that rounds to zero gets no arrow and no tone, which would\n // contradict a figure of zero. `Intl` writes no sign with 'exceptZero'\n // only when the value rounds to zero, by whatever rounding the options\n // ask for, so the change written that way is zero's text exactly then.\n const formatNumber = useNumberFormat(ownLocale);\n const signed = { ...options, signDisplay: 'exceptZero' as const };\n const direction =\n formatNumber(value, signed) === formatNumber(0, signed)\n ? 0\n : Math.sign(value);\n const tone =\n color ||\n (direction > 0 ? 'success' : direction < 0 ? 'danger' : undefined);\n const changeClass = usePrefixedClassNames('stat-change', {\n [`is-${tone}`]: !!tone,\n });\n return (\n <dd\n ref={ref}\n className={classNames(changeClass, bulmaHelperClasses, className)}\n {...rest}\n >\n {(direction > 0 || direction < 0) && (\n <svg viewBox=\"0 0 16 16\" width=\"1em\" height=\"1em\" aria-hidden=\"true\">\n <path\n d={\n direction > 0\n ? 'M8 13V3M3.5 7.5 8 3l4.5 4.5'\n : 'M8 3v10M3.5 8.5 8 13l4.5-4.5'\n }\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.75\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n )}\n <NumberFormatter value={value} format={options} locale={ownLocale} />\n </dd>\n );\n }\n);\n\nStatChange.displayName = 'Stat.Change';\n\n/**\n * The `Stat` component shows a figure with its name, and often a change and a note, such as a dashboard's revenue or an account's storage used.\n *\n * It's a label and its values in a description list: a `<dt>` for the\n * label, and a `<dd>` each for the figure, the change and the note. On its\n * own it renders its own `<dl>`; inside `Stats` it's a `<div>` in the row's.\n * A number `value` is written by `NumberFormatter`, so it follows a\n * `LocaleProvider`. A change is written with its sign (\"+12%\"), so the\n * direction is in the text, beside a decorative arrow and a color.\n *\n * @function\n * @param {StatProps} props - Props for the Stat component.\n * @param {React.Ref<HTMLElement>} ref - Forwarded ref to the root.\n * @returns {JSX.Element} The rendered stat.\n *\n * @example\n * <Stat\n * label=\"Revenue\"\n * value={8456.4}\n * format={{ style: 'currency', currency: 'USD' }}\n * change={0.12}\n * description=\"since last month\"\n * />\n */\nconst StatRoot = forwardRef<HTMLElement, StatProps>(\n (\n {\n className,\n label,\n value,\n format,\n locale,\n change,\n changeFormat,\n changeColor,\n description,\n size,\n loading,\n textColor,\n bgColor,\n children,\n ...props\n },\n ref\n ) => {\n const scope = useContext(StatScopeContext);\n const { bulmaHelperClasses, rest } = useBulmaClasses({\n color: textColor || scope.textColor,\n backgroundColor: bgColor,\n ...props,\n });\n const statClass = usePrefixedClassNames('stat', {\n [`is-${size}`]: !!size,\n });\n // The parts given as children, by type, so the figure's `<dd>` and the\n // warning below know what's there. A part rendered from inside another\n // component isn't seen.\n const parts = flattenChildren(children);\n const hasPart = (type: unknown) =>\n parts.some(part => React.isValidElement(part) && part.type === type);\n const hasValuePart = hasPart(StatValue);\n const labelled = isGiven(label) || hasPart(StatLabel);\n // Without a label, the figure has no name and the values no term.\n useEffect(() => {\n if (!labelled) {\n warnOnce(\n 'Stat:unlabelled',\n '[bestax-bulma] <Stat> has no label. Pass label, or a Stat.Label ' +\n 'child, so the figure has a name for assistive tech and its ' +\n 'values have a term in the list.'\n );\n }\n }, [labelled]);\n // Its parts take its locale and leave the row, so a Stat inside one of\n // them, in a `<dd>`, renders a `<dl>` of its own.\n const inner = useMemo(() => ({ row: false, locale }), [locale]);\n const Root = scope.row ? 'div' : 'dl';\n const figure = loading ? (\n <Skeleton />\n ) : typeof value === 'number' ? (\n <NumberFormatter value={value} format={format} locale={locale} />\n ) : isGiven(value) ? (\n value\n ) : null;\n // A `Stat.Value` child is the figure. Without one, the figure's `<dd>`\n // renders for a figure, or for a label, empty while the figure is\n // missing, so the label keeps its definition.\n const showsFigure = !hasValuePart && (figure !== null || labelled);\n return (\n <Root\n ref={ref as React.Ref<HTMLDivElement & HTMLDListElement>}\n className={classNames(statClass, bulmaHelperClasses, className)}\n {...rest}\n aria-busy={loading || rest['aria-busy']}\n >\n <StatScopeContext.Provider value={inner}>\n {isGiven(label) && <StatLabel>{label}</StatLabel>}\n {children}\n {showsFigure && <StatValue>{figure}</StatValue>}\n {typeof change === 'number' && (\n <StatChange\n value={change}\n format={changeFormat}\n color={changeColor}\n />\n )}\n {isGiven(description) && (\n <StatDescription>{description}</StatDescription>\n )}\n </StatScopeContext.Provider>\n </Root>\n );\n }\n);\n\nexport const Stat = withSubComponents(\n StatRoot,\n {\n Label: StatLabel,\n Value: StatValue,\n Change: StatChange,\n Description: StatDescription,\n },\n 'Stat'\n);\n\nexport default Stat;\n"],"names":["_jsx","_jsxs"],"mappings":";;;;;;;;;;;;;;;AAoBA;AACA,MAAM,qBAAqB,GAA6B;AACtD,IAAA,KAAK,EAAE,SAAS;AAChB,IAAA,WAAW,EAAE,YAAY;AACzB,IAAA,qBAAqB,EAAE,CAAC;CACzB;AAuMD;;;;;;;;;;AAUG;MACU,SAAS,GAAG,UAAU,CACjC,CAAC,EAAE,SAAS,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,KAAK,EAAE,EAAE,GAAG,KAAI;AAC7D,IAAA,MAAM,EAAE,kBAAkB,EAAE,IAAI,EAAE,GAAG,eAAe,CAAC;AACnD,QAAA,KAAK,EAAE,SAAS;AAChB,QAAA,eAAe,EAAE,OAAO;AACxB,QAAA,GAAG,KAAK;AACT,KAAA,CAAC;AACF,IAAA,MAAM,UAAU,GAAG,qBAAqB,CAAC,YAAY,CAAC;IACtD,QACEA,YACE,GAAG,EAAE,GAAG,EACR,SAAS,EAAE,UAAU,CAAC,UAAU,EAAE,kBAAkB,EAAE,SAAS,CAAC,EAAA,GAC5D,IAAI,EAAA,QAAA,EAEP,QAAQ,EAAA,CACN;AAET,CAAC;AAGH,SAAS,CAAC,WAAW,GAAG,YAAY;AAEpC;;;;;;;;;;;;AAYG;MACU,SAAS,GAAG,UAAU,CACjC,CAAC,EAAE,SAAS,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,KAAK,EAAE,EAAE,GAAG,KAAI;AAC7D,IAAA,MAAM,EAAE,kBAAkB,EAAE,IAAI,EAAE,GAAG,eAAe,CAAC;AACnD,QAAA,KAAK,EAAE,SAAS;AAChB,QAAA,eAAe,EAAE,OAAO;AACxB,QAAA,GAAG,KAAK;AACT,KAAA,CAAC;AACF,IAAA,MAAM,UAAU,GAAG,qBAAqB,CAAC,YAAY,CAAC;IACtD,QACEA,YACE,GAAG,EAAE,GAAG,EACR,SAAS,EAAE,UAAU,CAAC,UAAU,EAAE,kBAAkB,EAAE,SAAS,CAAC,EAAA,GAC5D,IAAI,EAAA,QAAA,EAEP,QAAQ,EAAA,CACN;AAET,CAAC;AAGH,SAAS,CAAC,WAAW,GAAG,YAAY;AAEpC;;;;;;;;;;AAUG;MACU,eAAe,GAAG,UAAU,CACvC,CAAC,EAAE,SAAS,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,KAAK,EAAE,EAAE,GAAG,KAAI;AAC7D,IAAA,MAAM,EAAE,kBAAkB,EAAE,IAAI,EAAE,GAAG,eAAe,CAAC;AACnD,QAAA,KAAK,EAAE,SAAS;AAChB,QAAA,eAAe,EAAE,OAAO;AACxB,QAAA,GAAG,KAAK;AACT,KAAA,CAAC;AACF,IAAA,MAAM,gBAAgB,GAAG,qBAAqB,CAAC,kBAAkB,CAAC;IAClE,QACEA,YACE,GAAG,EAAE,GAAG,EACR,SAAS,EAAE,UAAU,CAAC,gBAAgB,EAAE,kBAAkB,EAAE,SAAS,CAAC,EAAA,GAClE,IAAI,EAAA,QAAA,EAEP,QAAQ,EAAA,CACN;AAET,CAAC;AAGH,eAAe,CAAC,WAAW,GAAG,kBAAkB;AAEhD;;;;;;;;;;;;;;AAcG;AACI,MAAM,UAAU,GAAG,UAAU,CAClC,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,KAAK,EAAE,EAAE,GAAG,KAAI;IAC7D,MAAM,EAAE,kBAAkB,EAAE,IAAI,EAAE,GAAG,eAAe,CAAC,KAAK,CAAC;AAC3D,IAAA,MAAM,KAAK,GAAG,UAAU,CAAC,gBAAgB,CAAC;AAC1C,IAAA,MAAM,SAAS,GAAG,MAAM,IAAI,KAAK,CAAC,MAAM;IACxC,MAAM,OAAO,GAAG;AACd,UAAE,EAAE,GAAG,MAAM,EAAE,WAAW,EAAE,MAAM,CAAC,WAAW,IAAI,YAAY;UAC5D,qBAAqB;;;;;AAKzB,IAAA,MAAM,YAAY,GAAG,eAAe,CAAC,SAAS,CAAC;IAC/C,MAAM,MAAM,GAAG,EAAE,GAAG,OAAO,EAAE,WAAW,EAAE,YAAqB,EAAE;AACjE,IAAA,MAAM,SAAS,GACb,YAAY,CAAC,KAAK,EAAE,MAAM,CAAC,KAAK,YAAY,CAAC,CAAC,EAAE,MAAM;AACpD,UAAE;AACF,UAAE,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC;IACtB,MAAM,IAAI,GACR,KAAK;SACJ,SAAS,GAAG,CAAC,GAAG,SAAS,GAAG,SAAS,GAAG,CAAC,GAAG,QAAQ,GAAG,SAAS,CAAC;AACpE,IAAA,MAAM,WAAW,GAAG,qBAAqB,CAAC,aAAa,EAAE;AACvD,QAAA,CAAC,MAAM,IAAI,CAAA,CAAE,GAAG,CAAC,CAAC,IAAI;AACvB,KAAA,CAAC;IACF,QACEC,aACE,GAAG,EAAE,GAAG,EACR,SAAS,EAAE,UAAU,CAAC,WAAW,EAAE,kBAAkB,EAAE,SAAS,CAAC,EAAA,GAC7D,IAAI,aAEP,CAAC,SAAS,GAAG,CAAC,IAAI,SAAS,GAAG,CAAC,MAC9BD,GAAA,CAAA,KAAA,EAAA,EAAK,OAAO,EAAC,WAAW,EAAC,KAAK,EAAC,KAAK,EAAC,MAAM,EAAC,KAAK,EAAA,aAAA,EAAa,MAAM,EAAA,QAAA,EAClEA,cACE,CAAC,EACC,SAAS,GAAG;AACV,0BAAE;AACF,0BAAE,8BAA8B,EAEpC,IAAI,EAAC,MAAM,EACX,MAAM,EAAC,cAAc,EACrB,WAAW,EAAC,MAAM,EAClB,aAAa,EAAC,OAAO,EACrB,cAAc,EAAC,OAAO,EAAA,CACtB,EAAA,CACE,CACP,EACDA,GAAA,CAAC,eAAe,EAAA,EAAC,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAA,CAAI,CAAA,EAAA,CAClE;AAET,CAAC;AAGH,UAAU,CAAC,WAAW,GAAG,aAAa;AAEtC;;;;;;;;;;;;;;;;;;;;;;;AAuBG;AACH,MAAM,QAAQ,GAAG,UAAU,CACzB,CACE,EACE,SAAS,EACT,KAAK,EACL,KAAK,EACL,MAAM,EACN,MAAM,EACN,MAAM,EACN,YAAY,EACZ,WAAW,EACX,WAAW,EACX,IAAI,EACJ,OAAO,EACP,SAAS,EACT,OAAO,EACP,QAAQ,EACR,GAAG,KAAK,EACT,EACD,GAAG,KACD;AACF,IAAA,MAAM,KAAK,GAAG,UAAU,CAAC,gBAAgB,CAAC;AAC1C,IAAA,MAAM,EAAE,kBAAkB,EAAE,IAAI,EAAE,GAAG,eAAe,CAAC;AACnD,QAAA,KAAK,EAAE,SAAS,IAAI,KAAK,CAAC,SAAS;AACnC,QAAA,eAAe,EAAE,OAAO;AACxB,QAAA,GAAG,KAAK;AACT,KAAA,CAAC;AACF,IAAA,MAAM,SAAS,GAAG,qBAAqB,CAAC,MAAM,EAAE;AAC9C,QAAA,CAAC,MAAM,IAAI,CAAA,CAAE,GAAG,CAAC,CAAC,IAAI;AACvB,KAAA,CAAC;;;;AAIF,IAAA,MAAM,KAAK,GAAG,eAAe,CAAC,QAAQ,CAAC;AACvC,IAAA,MAAM,OAAO,GAAG,CAAC,IAAa,KAC5B,KAAK,CAAC,IAAI,CAAC,IAAI,IAAI,KAAK,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC;AACtE,IAAA,MAAM,YAAY,GAAG,OAAO,CAAC,SAAS,CAAC;IACvC,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,OAAO,CAAC,SAAS,CAAC;;IAErD,SAAS,CAAC,MAAK;QACb,IAAI,CAAC,QAAQ,EAAE;YACb,QAAQ,CACN,iBAAiB,EACjB,kEAAkE;gBAChE,6DAA6D;AAC7D,gBAAA,iCAAiC,CACpC;QACH;AACF,IAAA,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC;;;IAGd,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC;AAC/D,IAAA,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,GAAG,KAAK,GAAG,IAAI;IACrC,MAAM,MAAM,GAAG,OAAO,IACpBA,GAAA,CAAC,QAAQ,EAAA,EAAA,CAAG,IACV,OAAO,KAAK,KAAK,QAAQ,IAC3BA,GAAA,CAAC,eAAe,EAAA,EAAC,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAA,CAAI,IAC/D,OAAO,CAAC,KAAK,CAAC,IAChB,KAAK,IACH,IAAI;;;;AAIR,IAAA,MAAM,WAAW,GAAG,CAAC,YAAY,KAAK,MAAM,KAAK,IAAI,IAAI,QAAQ,CAAC;IAClE,QACEA,IAAC,IAAI,EAAA,EACH,GAAG,EAAE,GAAmD,EACxD,SAAS,EAAE,UAAU,CAAC,SAAS,EAAE,kBAAkB,EAAE,SAAS,CAAC,EAAA,GAC3D,IAAI,EAAA,WAAA,EACG,OAAO,IAAI,IAAI,CAAC,WAAW,CAAC,EAAA,QAAA,EAEvCC,IAAA,CAAC,gBAAgB,CAAC,QAAQ,IAAC,KAAK,EAAE,KAAK,EAAA,QAAA,EAAA,CACpC,OAAO,CAAC,KAAK,CAAC,IAAID,GAAA,CAAC,SAAS,EAAA,EAAA,QAAA,EAAE,KAAK,EAAA,CAAa,EAChD,QAAQ,EACR,WAAW,IAAIA,GAAA,CAAC,SAAS,cAAE,MAAM,EAAA,CAAa,EAC9C,OAAO,MAAM,KAAK,QAAQ,KACzBA,GAAA,CAAC,UAAU,IACT,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,YAAY,EACpB,KAAK,EAAE,WAAW,EAAA,CAClB,CACH,EACA,OAAO,CAAC,WAAW,CAAC,KACnBA,GAAA,CAAC,eAAe,EAAA,EAAA,QAAA,EAAE,WAAW,GAAmB,CACjD,CAAA,EAAA,CACyB,EAAA,CACvB;AAEX,CAAC,CACF;AAEM,MAAM,IAAI,GAAG,iBAAiB,CACnC,QAAQ,EACR;AACE,IAAA,KAAK,EAAE,SAAS;AAChB,IAAA,KAAK,EAAE,SAAS;AAChB,IAAA,MAAM,EAAE,UAAU;AAClB,IAAA,WAAW,EAAE,eAAe;CAC7B,EACD,MAAM;;"}
|
|
1
|
+
{"version":3,"file":"Stat.js","sources":["../../../src/elements/Stat.tsx"],"sourcesContent":["import React, { forwardRef, useContext, useEffect, useMemo } from 'react';\nimport { classNames, usePrefixedClassNames } from '../helpers/classNames';\nimport {\n useBulmaClasses,\n BulmaClassesProps,\n validColors,\n} from '../helpers/useBulmaClasses';\nimport { withSubComponents } from '../helpers/withSubComponents';\nimport { isGiven } from '../helpers/isGiven';\nimport { flattenChildren } from '../helpers/flattenChildren';\nimport { isElementOf } from '../helpers/elementType';\nimport { useNumberFormat } from '../helpers/localeFormat';\nimport { warnOnce } from '../helpers/devWarnings';\nimport { NumberFormatter } from './NumberFormatter';\nimport { Skeleton } from './Skeleton';\nimport { StatScopeContext } from './statContext';\n\n/** The colors a Stat's change can take, the union `messageColor` takes. */\nexport type StatChangeColor =\n 'primary' | 'link' | 'info' | 'success' | 'warning' | 'danger';\n\n/** How a change is written when no format is given: a signed percentage. */\nconst DEFAULT_CHANGE_FORMAT: Intl.NumberFormatOptions = {\n style: 'percent',\n signDisplay: 'exceptZero',\n maximumFractionDigits: 1,\n};\n\n/**\n * Props for the Stat component. The helper props, the other attributes and\n * the `ref` land on its root: a `<div>` inside `Stats`, or its own `<dl>` on\n * its own.\n *\n * @extraProp {React.Ref<HTMLElement>} [ref] - Ref forwarded to the root, a `<div>` inside `Stats` or a `<dl>` on its own.\n */\nexport interface StatProps\n extends\n Omit<React.HTMLAttributes<HTMLElement>, 'color'>,\n Omit<BulmaClassesProps, 'color' | 'backgroundColor'> {\n /** Additional CSS classes to apply. */\n className?: string;\n /**\n * What the figure is (\"Revenue\"), as a `Stat.Label` before the children.\n * A Stat needs one, here or as a `Stat.Label` child: it's the figure's\n * name for assistive tech, and a definition list's values belong to a\n * term. A Stat with neither a `label` nor a `Stat.Label` child warns in\n * development.\n */\n label?: React.ReactNode;\n /**\n * The figure, as a `Stat.Value` after the children. A number is written by\n * `NumberFormatter` with `format` and `locale`; anything else, such as\n * \"N/A\", renders as given, and `0` renders. `undefined`, `null`, a boolean\n * and `''` leave it empty, and its `<dd>` still renders, one line of the\n * figure tall, while the Stat has a label, so the label keeps its\n * definition while the figure is missing. A `Stat.Value` child takes its\n * place: with one, `value` and its `<dd>` are left out.\n */\n value?: React.ReactNode;\n /**\n * `Intl.NumberFormat` options for a number `value`, as NumberFormatter's\n * `format`. A `value` that isn't a number ignores it.\n */\n format?: Intl.NumberFormatOptions;\n /**\n * The locale for `value` and `change`, as NumberFormatter's `locale`.\n * Unset, it's the `LocaleProvider`'s around it, and with neither, the\n * runtime's own, so give one, here or through a `LocaleProvider`, on a\n * page rendered on the server.\n */\n locale?: string;\n /**\n * The change, as a `Stat.Change` after the value. With the default format\n * it's a fraction (`0.12` for 12%); with your own `changeFormat`, it's in\n * that format's terms. It's written with its sign, so the direction is in\n * the text.\n */\n change?: number;\n /**\n * `Intl.NumberFormat` options for `change`. Yours replace the default, and\n * `signDisplay: 'exceptZero'` is added unless you set a `signDisplay`. With\n * `signDisplay: 'never'`, or `'negative'` on a rise, the text carries no\n * direction: the arrow is hidden from assistive tech and the tone is color\n * alone, so keep a sign, or say the direction in `description`.\n * @defaultValue { style: 'percent', signDisplay: 'exceptZero', maximumFractionDigits: 1 }\n */\n changeFormat?: Intl.NumberFormatOptions;\n /**\n * The change's color. Unset, it's the sign's: `success` up, `danger` down,\n * and none at zero. Set it where up is bad news, such as a cost or an\n * error rate.\n */\n changeColor?: StatChangeColor;\n /** A note under the figure (\"since last month\"), as a `Stat.Description` last. */\n description?: React.ReactNode;\n /**\n * The figure's size, a step of Bulma's size scale. The label, the change\n * and the note stay small.\n */\n size?: 'small' | 'medium' | 'large';\n /**\n * Shows a `Skeleton` in place of `value`, one line of the figure tall,\n * and sets `aria-busy` on the Stat. The label, the change and the note\n * render as given. With a `Stat.Value` child, it only sets `aria-busy`:\n * the child renders as given, so put a `Skeleton` in it yourself.\n */\n loading?: boolean;\n /**\n * `Stat.Label`, `Stat.Value`, `Stat.Change` and `Stat.Description`, for\n * anything the props don't cover, such as a unit or an info tip. They\n * render between `label` and `value`. They're this Stat's own parts, so\n * not another Stat: that would be a list inside this one's. The Stat finds\n * a `Stat.Label` or `Stat.Value` among them by type, so one rendered from\n * inside a component of your own isn't seen.\n */\n children?: React.ReactNode;\n /**\n * Text color helper. It colors the figure; the label, the change and the\n * note keep their own colors. Inside `Stats`, a Stat without one takes the\n * row's.\n */\n textColor?: (typeof validColors)[number] | 'inherit' | 'current';\n /** Background color helper. */\n bgColor?: (typeof validColors)[number] | 'inherit' | 'current';\n}\n\n/**\n * Props for Stat.Label, what the figure is. The helper props, the other\n * attributes and the `ref` land on its `<dt>`.\n *\n * @extraProp {React.Ref<HTMLElement>} [ref] - Ref forwarded to the `<dt>`.\n */\nexport interface StatLabelProps\n extends\n Omit<React.HTMLAttributes<HTMLElement>, 'color'>,\n Omit<BulmaClassesProps, 'color' | 'backgroundColor'> {\n /** Additional CSS classes to apply. */\n className?: string;\n /**\n * The label. A button may sit in it, such as one that opens a `Popover`\n * about the figure, but not a heading, which HTML doesn't allow here.\n */\n children?: React.ReactNode;\n /** Text color helper. */\n textColor?: (typeof validColors)[number] | 'inherit' | 'current';\n /** Background color helper. */\n bgColor?: (typeof validColors)[number] | 'inherit' | 'current';\n}\n\n/**\n * Props for Stat.Value, the figure. The helper props, the other attributes\n * and the `ref` land on its `<dd>`.\n *\n * @extraProp {React.Ref<HTMLElement>} [ref] - Ref forwarded to the `<dd>`.\n */\nexport interface StatValueProps\n extends\n Omit<React.HTMLAttributes<HTMLElement>, 'color'>,\n Omit<BulmaClassesProps, 'color' | 'backgroundColor'> {\n /** Additional CSS classes to apply. */\n className?: string;\n /**\n * The figure, written as you give it, so write a number with\n * `NumberFormatter`.\n */\n children?: React.ReactNode;\n /** Text color helper. */\n textColor?: (typeof validColors)[number] | 'inherit' | 'current';\n /** Background color helper. */\n bgColor?: (typeof validColors)[number] | 'inherit' | 'current';\n}\n\n/**\n * Props for Stat.Description, a note under the figure. The helper props, the\n * other attributes and the `ref` land on its `<dd>`.\n *\n * @extraProp {React.Ref<HTMLElement>} [ref] - Ref forwarded to the `<dd>`.\n */\nexport interface StatDescriptionProps\n extends\n Omit<React.HTMLAttributes<HTMLElement>, 'color'>,\n Omit<BulmaClassesProps, 'color' | 'backgroundColor'> {\n /** Additional CSS classes to apply. */\n className?: string;\n /** The note, such as \"since last month\". */\n children?: React.ReactNode;\n /** Text color helper. */\n textColor?: (typeof validColors)[number] | 'inherit' | 'current';\n /** Background color helper. */\n bgColor?: (typeof validColors)[number] | 'inherit' | 'current';\n}\n\n/**\n * Props for Stat.Change, a change written with its sign. The helper props,\n * the other attributes and the `ref` land on its `<dd>`.\n *\n * @extraProp {React.Ref<HTMLElement>} [ref] - Ref forwarded to the `<dd>`.\n */\nexport interface StatChangeProps\n extends\n Omit<React.HTMLAttributes<HTMLElement>, 'color' | 'children'>,\n Omit<BulmaClassesProps, 'color' | 'backgroundColor'> {\n /** Additional CSS classes to apply. */\n className?: string;\n /**\n * The change, as Stat's `change`. One that rounds to zero in its format,\n * such as `0.0004` at one decimal place of a percentage, reads as zero, so\n * it gets no arrow and no color of its own.\n */\n value: number;\n /**\n * As Stat's `changeFormat`: yours replace the default, and\n * `signDisplay: 'exceptZero'` is added unless you set a `signDisplay`.\n * @defaultValue { style: 'percent', signDisplay: 'exceptZero', maximumFractionDigits: 1 }\n */\n format?: Intl.NumberFormatOptions;\n /** As Stat's `locale`. Unset, the Stat's. */\n locale?: string;\n /**\n * As Stat's `changeColor`. Unset, it's the sign's: `success` up, `danger`\n * down, and none at zero. Set, it applies at zero too.\n */\n color?: StatChangeColor;\n}\n\n/**\n * What the figure is: a `<dt>`, small and in the weaker text color.\n *\n * @function\n * @param {StatLabelProps} props - Props for Stat.Label.\n * @param {React.Ref<HTMLElement>} ref - Forwarded ref to the `<dt>`.\n * @returns {JSX.Element} The rendered label.\n *\n * @example\n * <Stat.Label>Revenue</Stat.Label>\n */\nexport const StatLabel = forwardRef<HTMLElement, StatLabelProps>(\n ({ className, textColor, bgColor, children, ...props }, ref) => {\n const { bulmaHelperClasses, rest } = useBulmaClasses({\n color: textColor,\n backgroundColor: bgColor,\n ...props,\n });\n const labelClass = usePrefixedClassNames('stat-label');\n return (\n <dt\n ref={ref}\n className={classNames(labelClass, bulmaHelperClasses, className)}\n {...rest}\n >\n {children}\n </dt>\n );\n }\n);\n\nStatLabel.displayName = 'Stat.Label';\n\n/**\n * The figure: a `<dd>`, large, in the Stat's color, with digits of one width so a figure that updates doesn't shift.\n *\n * @function\n * @param {StatValueProps} props - Props for Stat.Value.\n * @param {React.Ref<HTMLElement>} ref - Forwarded ref to the `<dd>`.\n * @returns {JSX.Element} The rendered figure.\n *\n * @example\n * <Stat.Value>\n * <NumberFormatter value={182} format={{ style: 'unit', unit: 'millisecond' }} />\n * </Stat.Value>\n */\nexport const StatValue = forwardRef<HTMLElement, StatValueProps>(\n ({ className, textColor, bgColor, children, ...props }, ref) => {\n const { bulmaHelperClasses, rest } = useBulmaClasses({\n color: textColor,\n backgroundColor: bgColor,\n ...props,\n });\n const valueClass = usePrefixedClassNames('stat-value');\n return (\n <dd\n ref={ref}\n className={classNames(valueClass, bulmaHelperClasses, className)}\n {...rest}\n >\n {children}\n </dd>\n );\n }\n);\n\nStatValue.displayName = 'Stat.Value';\n\n/**\n * A note under the figure: a `<dd>`, small and in the weaker text color.\n *\n * @function\n * @param {StatDescriptionProps} props - Props for Stat.Description.\n * @param {React.Ref<HTMLElement>} ref - Forwarded ref to the `<dd>`.\n * @returns {JSX.Element} The rendered note.\n *\n * @example\n * <Stat.Description>since last month</Stat.Description>\n */\nexport const StatDescription = forwardRef<HTMLElement, StatDescriptionProps>(\n ({ className, textColor, bgColor, children, ...props }, ref) => {\n const { bulmaHelperClasses, rest } = useBulmaClasses({\n color: textColor,\n backgroundColor: bgColor,\n ...props,\n });\n const descriptionClass = usePrefixedClassNames('stat-description');\n return (\n <dd\n ref={ref}\n className={classNames(descriptionClass, bulmaHelperClasses, className)}\n {...rest}\n >\n {children}\n </dd>\n );\n }\n);\n\nStatDescription.displayName = 'Stat.Description';\n\n/**\n * A change written with its sign, such as \"+12%\", after an arrow that repeats the direction: a `<dd>`, green when it's up and red when it's down unless `color` says otherwise.\n *\n * The sign is in the text, written by `Intl` for the locale, so a screen\n * reader hears the direction; the arrow is hidden from assistive tech, and\n * the color repeats the direction for sighted readers rather than carry it.\n *\n * @function\n * @param {StatChangeProps} props - Props for Stat.Change.\n * @param {React.Ref<HTMLElement>} ref - Forwarded ref to the `<dd>`.\n * @returns {JSX.Element} The rendered change.\n *\n * @example\n * <Stat.Change value={-0.08} color=\"success\" />\n */\nexport const StatChange = forwardRef<HTMLElement, StatChangeProps>(\n ({ className, value, format, locale, color, ...props }, ref) => {\n const { bulmaHelperClasses, rest } = useBulmaClasses(props);\n const scope = useContext(StatScopeContext);\n const ownLocale = locale || scope.locale;\n const options = format\n ? { ...format, signDisplay: format.signDisplay || 'exceptZero' }\n : DEFAULT_CHANGE_FORMAT;\n // A change that rounds to zero gets no arrow and no tone, which would\n // contradict a figure of zero. `Intl` writes no sign with 'exceptZero'\n // only when the value rounds to zero, by whatever rounding the options\n // ask for, so the change written that way is zero's text exactly then.\n const formatNumber = useNumberFormat(ownLocale);\n const signed = { ...options, signDisplay: 'exceptZero' as const };\n const direction =\n formatNumber(value, signed) === formatNumber(0, signed)\n ? 0\n : Math.sign(value);\n const tone =\n color ||\n (direction > 0 ? 'success' : direction < 0 ? 'danger' : undefined);\n const changeClass = usePrefixedClassNames('stat-change', {\n [`is-${tone}`]: !!tone,\n });\n return (\n <dd\n ref={ref}\n className={classNames(changeClass, bulmaHelperClasses, className)}\n {...rest}\n >\n {(direction > 0 || direction < 0) && (\n <svg viewBox=\"0 0 16 16\" width=\"1em\" height=\"1em\" aria-hidden=\"true\">\n <path\n d={\n direction > 0\n ? 'M8 13V3M3.5 7.5 8 3l4.5 4.5'\n : 'M8 3v10M3.5 8.5 8 13l4.5-4.5'\n }\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.75\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n )}\n <NumberFormatter value={value} format={options} locale={ownLocale} />\n </dd>\n );\n }\n);\n\nStatChange.displayName = 'Stat.Change';\n\n/**\n * The `Stat` component shows a figure with its name, and often a change and a note, such as a dashboard's revenue or an account's storage used.\n *\n * It's a label and its values in a description list: a `<dt>` for the\n * label, and a `<dd>` each for the figure, the change and the note. On its\n * own it renders its own `<dl>`; inside `Stats` it's a `<div>` in the row's.\n * A number `value` is written by `NumberFormatter`, so it follows a\n * `LocaleProvider`. A change is written with its sign (\"+12%\"), so the\n * direction is in the text, beside a decorative arrow and a color.\n *\n * @function\n * @param {StatProps} props - Props for the Stat component.\n * @param {React.Ref<HTMLElement>} ref - Forwarded ref to the root.\n * @returns {JSX.Element} The rendered stat.\n *\n * @example\n * <Stat\n * label=\"Revenue\"\n * value={8456.4}\n * format={{ style: 'currency', currency: 'USD' }}\n * change={0.12}\n * description=\"since last month\"\n * />\n */\nconst StatRoot = forwardRef<HTMLElement, StatProps>(\n (\n {\n className,\n label,\n value,\n format,\n locale,\n change,\n changeFormat,\n changeColor,\n description,\n size,\n loading,\n textColor,\n bgColor,\n children,\n ...props\n },\n ref\n ) => {\n const scope = useContext(StatScopeContext);\n const { bulmaHelperClasses, rest } = useBulmaClasses({\n color: textColor || scope.textColor,\n backgroundColor: bgColor,\n ...props,\n });\n const statClass = usePrefixedClassNames('stat', {\n [`is-${size}`]: !!size,\n });\n // The parts given as children, by type, so the figure's `<dd>` and the\n // warning below know what's there. A part rendered from inside another\n // component isn't seen.\n const parts = flattenChildren(children);\n const hasPart = (type: unknown) =>\n parts.some(part => isElementOf(part, type));\n const hasValuePart = hasPart(StatValue);\n const labelled = isGiven(label) || hasPart(StatLabel);\n // Without a label, the figure has no name and the values no term.\n useEffect(() => {\n if (!labelled) {\n warnOnce(\n 'Stat:unlabelled',\n '[bestax-bulma] <Stat> has no label. Pass label, or a Stat.Label ' +\n 'child, so the figure has a name for assistive tech and its ' +\n 'values have a term in the list.'\n );\n }\n }, [labelled]);\n // Its parts take its locale and leave the row, so a Stat inside one of\n // them, in a `<dd>`, renders a `<dl>` of its own.\n const inner = useMemo(() => ({ row: false, locale }), [locale]);\n const Root = scope.row ? 'div' : 'dl';\n const figure = loading ? (\n <Skeleton />\n ) : typeof value === 'number' ? (\n <NumberFormatter value={value} format={format} locale={locale} />\n ) : isGiven(value) ? (\n value\n ) : null;\n // A `Stat.Value` child is the figure. Without one, the figure's `<dd>`\n // renders for a figure, or for a label, empty while the figure is\n // missing, so the label keeps its definition.\n const showsFigure = !hasValuePart && (figure !== null || labelled);\n return (\n <Root\n ref={ref as React.Ref<HTMLDivElement & HTMLDListElement>}\n className={classNames(statClass, bulmaHelperClasses, className)}\n {...rest}\n aria-busy={loading || rest['aria-busy']}\n >\n <StatScopeContext.Provider value={inner}>\n {isGiven(label) && <StatLabel>{label}</StatLabel>}\n {children}\n {showsFigure && <StatValue>{figure}</StatValue>}\n {typeof change === 'number' && (\n <StatChange\n value={change}\n format={changeFormat}\n color={changeColor}\n />\n )}\n {isGiven(description) && (\n <StatDescription>{description}</StatDescription>\n )}\n </StatScopeContext.Provider>\n </Root>\n );\n }\n);\n\nexport const Stat = withSubComponents(\n StatRoot,\n {\n Label: StatLabel,\n Value: StatValue,\n Change: StatChange,\n Description: StatDescription,\n },\n 'Stat'\n);\n\nexport default Stat;\n"],"names":["_jsx","_jsxs"],"mappings":"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"}
|