@allxsmith/bestax-bulma 5.61.0 → 5.63.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bestax.css +1 -1
- package/dist/bestax.css.map +1 -1
- package/dist/esm/components/EmptyState.js +2 -6
- package/dist/esm/components/EmptyState.js.map +1 -1
- package/dist/esm/components/Timeline.js +5 -9
- package/dist/esm/components/Timeline.js.map +1 -1
- package/dist/esm/components/TreeView.js +16 -16
- package/dist/esm/components/TreeView.js.map +1 -1
- package/dist/esm/elements/Buttons.js +2 -0
- package/dist/esm/elements/Buttons.js.map +1 -1
- package/dist/esm/elements/CopyButton.js +132 -0
- package/dist/esm/elements/CopyButton.js.map +1 -0
- package/dist/esm/elements/DescriptionList.js +1 -5
- package/dist/esm/elements/DescriptionList.js.map +1 -1
- package/dist/esm/elements/Stat.js +196 -0
- package/dist/esm/elements/Stat.js.map +1 -0
- package/dist/esm/elements/Stats.js +48 -0
- package/dist/esm/elements/Stats.js.map +1 -0
- package/dist/esm/elements/statContext.js +10 -0
- package/dist/esm/elements/statContext.js.map +1 -0
- package/dist/esm/form/Combobox.js +10 -14
- package/dist/esm/form/Combobox.js.map +1 -1
- package/dist/esm/form/Field.js.map +1 -1
- package/dist/esm/form/TreeSelect.js +395 -0
- package/dist/esm/form/TreeSelect.js.map +1 -0
- package/dist/esm/form/_pickerInternals/PickerPopover.js +5 -1
- package/dist/esm/form/_pickerInternals/PickerPopover.js.map +1 -1
- package/dist/esm/form/_selectInternals/DialogSelect.js +208 -0
- package/dist/esm/form/_selectInternals/DialogSelect.js.map +1 -0
- package/dist/esm/form/_selectInternals/ValueTags.js +28 -0
- package/dist/esm/form/_selectInternals/ValueTags.js.map +1 -0
- package/dist/esm/helpers/checkIcon.js +20 -0
- package/dist/esm/helpers/checkIcon.js.map +1 -0
- package/dist/esm/helpers/isGiven.js +13 -0
- package/dist/esm/helpers/isGiven.js.map +1 -0
- package/dist/esm/helpers/statusRegion.js +3 -3
- package/dist/esm/helpers/treeData.js +97 -0
- package/dist/esm/helpers/treeData.js.map +1 -0
- package/dist/esm/helpers/useClipboard.js +176 -0
- package/dist/esm/helpers/useClipboard.js.map +1 -0
- package/dist/esm/helpers/useComboboxEngine.js +2 -2
- package/dist/esm/helpers/useComboboxEngine.js.map +1 -1
- package/dist/esm/helpers/useControlled.js +30 -0
- package/dist/esm/helpers/useControlled.js.map +1 -0
- package/dist/esm/helpers/useTreeView.js +15 -13
- package/dist/esm/helpers/useTreeView.js.map +1 -1
- package/dist/extras.css +1 -1
- package/dist/extras.css.map +1 -1
- package/dist/index.cjs +10605 -9380
- package/dist/index.cjs.map +1 -1
- package/dist/index.esm.js +5 -0
- package/dist/index.esm.js.map +1 -1
- package/dist/types/elements/Buttons.d.ts +1 -0
- package/dist/types/elements/CopyButton.d.ts +105 -0
- package/dist/types/elements/Stat.d.ts +242 -0
- package/dist/types/elements/Stats.d.ts +40 -0
- package/dist/types/elements/statContext.d.ts +18 -0
- package/dist/types/form/Field.d.ts +1 -1
- package/dist/types/form/TreeSelect.d.ts +308 -0
- package/dist/types/form/_pickerInternals/PickerPopover.d.ts +8 -0
- package/dist/types/form/_selectInternals/DialogSelect.d.ts +86 -0
- package/dist/types/form/_selectInternals/ValueTags.d.ts +40 -0
- package/dist/types/helpers/checkIcon.d.ts +10 -0
- package/dist/types/helpers/isGiven.d.ts +7 -0
- package/dist/types/helpers/treeData.d.ts +48 -0
- package/dist/types/helpers/useClipboard.d.ts +65 -0
- package/dist/types/helpers/useControlled.d.ts +14 -0
- package/dist/types/helpers/useTreeView.d.ts +30 -0
- package/dist/types/index.d.ts +6 -0
- package/dist/types-cjs/elements/Buttons.d.ts +1 -0
- package/dist/types-cjs/elements/CopyButton.d.ts +105 -0
- package/dist/types-cjs/elements/Stat.d.ts +242 -0
- package/dist/types-cjs/elements/Stats.d.ts +40 -0
- package/dist/types-cjs/elements/statContext.d.ts +18 -0
- package/dist/types-cjs/form/Field.d.ts +1 -1
- package/dist/types-cjs/form/TreeSelect.d.ts +308 -0
- package/dist/types-cjs/form/_pickerInternals/PickerPopover.d.ts +8 -0
- package/dist/types-cjs/form/_selectInternals/DialogSelect.d.ts +86 -0
- package/dist/types-cjs/form/_selectInternals/ValueTags.d.ts +40 -0
- package/dist/types-cjs/helpers/checkIcon.d.ts +10 -0
- package/dist/types-cjs/helpers/isGiven.d.ts +7 -0
- package/dist/types-cjs/helpers/treeData.d.ts +48 -0
- package/dist/types-cjs/helpers/useClipboard.d.ts +65 -0
- package/dist/types-cjs/helpers/useControlled.d.ts +14 -0
- package/dist/types-cjs/helpers/useTreeView.d.ts +30 -0
- package/dist/types-cjs/index.d.ts +6 -0
- 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/elements/_index.scss +1 -0
- package/src/scss/elements/_stat.scss +153 -0
- package/src/scss/form/_index.scss +1 -0
- package/src/scss/form/_picker-popover.scss +6 -0
- package/src/scss/form/_treeselect.scss +146 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/* @allxsmith/bestax-bulma — AI agents: see AGENTS.md in the package root, or https://bestax.io/llms.txt */
|
|
2
|
+
'use client';
|
|
3
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
4
|
+
import { forwardRef, useState, useRef } from 'react';
|
|
5
|
+
import { Button } from './Button.js';
|
|
6
|
+
import { Icon } from './Icon.js';
|
|
7
|
+
import { IconButton } from './IconButton.js';
|
|
8
|
+
import { buttonType } from '../helpers/buttonType.js';
|
|
9
|
+
import { CheckIcon } from '../helpers/checkIcon.js';
|
|
10
|
+
import { Portal } from '../helpers/portal.js';
|
|
11
|
+
import { StatusRegion } from '../helpers/statusRegion.js';
|
|
12
|
+
import { useClipboard } from '../helpers/useClipboard.js';
|
|
13
|
+
|
|
14
|
+
/** Two sheets, one over the other. */
|
|
15
|
+
const CopyGlyph = () => (jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinejoin: "round", width: "1em", height: "1em", focusable: "false", children: [jsx("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" }), jsx("rect", { x: "5.5", y: "5.5", width: "8", height: "9", rx: "1" })] }));
|
|
16
|
+
const describeAnnouncement$1 = (announcement) => announcement.text;
|
|
17
|
+
const noAnnouncements$1 = [];
|
|
18
|
+
/** What marks an open modal dialog in ARIA. */
|
|
19
|
+
const modalDialog = '[aria-modal="true"]';
|
|
20
|
+
/**
|
|
21
|
+
* The open modal dialog an element is in: one marked `aria-modal="true"`, or
|
|
22
|
+
* a `<dialog>` opened with `showModal()`, which makes the rest of the page
|
|
23
|
+
* inert. An engine without the `:modal` selector throws on the whole list,
|
|
24
|
+
* so it then looks for the ARIA alone.
|
|
25
|
+
*
|
|
26
|
+
* @param element - Where to look up from.
|
|
27
|
+
* @returns The nearest open modal dialog around it, or `null`.
|
|
28
|
+
*/
|
|
29
|
+
function closestModalDialog(element) {
|
|
30
|
+
try {
|
|
31
|
+
return element.closest(`${modalDialog}, dialog:modal`);
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return element.closest(modalDialog);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* The `CopyButton` component renders a button that copies a piece of text to the clipboard, says "Copied" for a moment, and announces the result.
|
|
39
|
+
*
|
|
40
|
+
* It's a `Button` with a copy icon and "Copy", and takes everything `Button`
|
|
41
|
+
* takes. A click writes `value` through the Clipboard API, or through the
|
|
42
|
+
* older copy command where that's missing or refused, and reports a failure
|
|
43
|
+
* rather than showing "Copied". Each outcome is announced through a polite
|
|
44
|
+
* status region portaled out of the button's place, so it stays the last
|
|
45
|
+
* child of a `Control` or `Buttons`: into the open modal dialog the button is
|
|
46
|
+
* in, otherwise where `Portal` puts portaled content (the `ConfigProvider`'s
|
|
47
|
+
* `portalContainer`, or the end of the page). With `showLabel={false}` it's an
|
|
48
|
+
* `IconButton`. For a trigger of your own, use `useClipboard`, which it's
|
|
49
|
+
* built on.
|
|
50
|
+
*
|
|
51
|
+
* @function
|
|
52
|
+
* @param {CopyButtonProps} props - Props for the CopyButton component.
|
|
53
|
+
* @param {React.Ref<HTMLButtonElement>} ref - Forwarded ref to the `<button>`.
|
|
54
|
+
* @returns {JSX.Element} The rendered button.
|
|
55
|
+
* @see {@link https://www.w3.org/WAI/WCAG22/Understanding/status-messages.html | WCAG 4.1.3 Status Messages}
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* <CopyButton value="https://bestax.io" />
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* <CopyButton value={apiKey} showLabel={false} size="small" />
|
|
62
|
+
*/
|
|
63
|
+
const CopyButton = forwardRef(function CopyButton({ value, duration = 2000, showLabel = true, labels, onCopiedChange, onCopyError, onClick, onFocus, ...props }, ref) {
|
|
64
|
+
const copyLabel = labels?.copy || 'Copy';
|
|
65
|
+
const copiedLabel = labels?.copied || 'Copied';
|
|
66
|
+
const copiedStatus = labels?.copiedStatus || 'Copied to the clipboard';
|
|
67
|
+
const failedStatus = labels?.failedStatus || "Couldn't copy to the clipboard";
|
|
68
|
+
// The latest outcome. Each gets a new id, so the region announces a
|
|
69
|
+
// repeat too.
|
|
70
|
+
const [announcements, setAnnouncements] = useState(noAnnouncements$1);
|
|
71
|
+
const countRef = useRef(0);
|
|
72
|
+
const announce = (text) => {
|
|
73
|
+
countRef.current += 1;
|
|
74
|
+
setAnnouncements([{ id: String(countRef.current), text }]);
|
|
75
|
+
};
|
|
76
|
+
// The open modal dialog the button is in, which the region goes inside:
|
|
77
|
+
// a screen reader ignores a live region behind one. It's looked up when
|
|
78
|
+
// the button takes focus or a click, rather than once as it mounts,
|
|
79
|
+
// since Modal and Sidebar keep their content mounted while closed, and
|
|
80
|
+
// only an open one is `aria-modal`. A region that moves starts empty, so
|
|
81
|
+
// it doesn't say the last outcome again. This lookup can go once the
|
|
82
|
+
// overlays hand their content their own root as its portal container
|
|
83
|
+
// (#1471).
|
|
84
|
+
const [dialog, setDialog] = useState(null);
|
|
85
|
+
const placeRegion = (button) => {
|
|
86
|
+
const next = closestModalDialog(button);
|
|
87
|
+
if (next === dialog)
|
|
88
|
+
return;
|
|
89
|
+
setDialog(next);
|
|
90
|
+
setAnnouncements(noAnnouncements$1);
|
|
91
|
+
};
|
|
92
|
+
// The hook fires nothing once this has unmounted, so neither does the
|
|
93
|
+
// announcement.
|
|
94
|
+
const { copy, copied } = useClipboard({
|
|
95
|
+
duration,
|
|
96
|
+
onCopiedChange: next => {
|
|
97
|
+
if (next)
|
|
98
|
+
announce(copiedStatus);
|
|
99
|
+
onCopiedChange?.(next);
|
|
100
|
+
},
|
|
101
|
+
onCopyError: error => {
|
|
102
|
+
announce(failedStatus);
|
|
103
|
+
onCopyError?.(error);
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
// The caller's onClick runs first, so a `preventDefault()` there stops
|
|
107
|
+
// the copy, as it stops ToggleButton's toggle.
|
|
108
|
+
const handleClick = (event) => {
|
|
109
|
+
onClick?.(event);
|
|
110
|
+
if (event.defaultPrevented)
|
|
111
|
+
return;
|
|
112
|
+
placeRegion(event.currentTarget);
|
|
113
|
+
void copy(value);
|
|
114
|
+
};
|
|
115
|
+
const handleFocus = (event) => {
|
|
116
|
+
onFocus?.(event);
|
|
117
|
+
placeRegion(event.currentTarget);
|
|
118
|
+
};
|
|
119
|
+
const glyph = jsx(Icon, { children: copied ? jsx(CheckIcon, {}) : jsx(CopyGlyph, {}) });
|
|
120
|
+
// The props are spread first, so it stays a `<button>` that doesn't
|
|
121
|
+
// submit by accident, whatever a JavaScript caller or a spread object
|
|
122
|
+
// passes along.
|
|
123
|
+
const button = showLabel ? (jsxs(Button, { ...props, ref: ref, as: "button", type: buttonType(props.type), onClick: handleClick, onFocus: handleFocus, children: [glyph, jsx("span", { children: copied ? copiedLabel : copyLabel })] })) : (jsx(IconButton, { ...{
|
|
124
|
+
...props,
|
|
125
|
+
'aria-label': props['aria-label'] || copyLabel,
|
|
126
|
+
}, ref: ref, as: "button", onClick: handleClick, onFocus: handleFocus, children: glyph }));
|
|
127
|
+
return (jsxs(Fragment, { children: [button, jsx(Portal, { container: dialog || undefined, children: jsx(StatusRegion, { items: announcements, describe: describeAnnouncement$1 }) })] }));
|
|
128
|
+
});
|
|
129
|
+
CopyButton.displayName = 'CopyButton';
|
|
130
|
+
|
|
131
|
+
export { CopyButton };
|
|
132
|
+
//# sourceMappingURL=CopyButton.js.map
|
|
@@ -0,0 +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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"}
|
|
@@ -5,15 +5,11 @@ import { forwardRef } 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
|
+
import { isGiven } from '../helpers/isGiven.js';
|
|
8
9
|
|
|
9
10
|
const validDescriptionListOrientations = ['vertical', 'horizontal'];
|
|
10
11
|
const isOrientation = (orientation) => !!orientation &&
|
|
11
12
|
validDescriptionListOrientations.includes(orientation);
|
|
12
|
-
/** Whether a prop was given something to render. */
|
|
13
|
-
const isGiven = (node) => node !== undefined &&
|
|
14
|
-
node !== null &&
|
|
15
|
-
typeof node !== 'boolean' &&
|
|
16
|
-
node !== '';
|
|
17
13
|
/**
|
|
18
14
|
* A label, the name of a fact: a `<dt>`, in the weaker text color.
|
|
19
15
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DescriptionList.js","sources":["../../../src/elements/DescriptionList.tsx"],"sourcesContent":["import React, { forwardRef } from 'react';\nimport { classNames, usePrefixedClassNames } from '../helpers/classNames';\nimport {\n useBulmaClasses,\n BulmaClassesProps,\n validColors,\n} from '../helpers/useBulmaClasses';\nimport { withSubComponents } from '../helpers/withSubComponents';\n\nconst validDescriptionListOrientations = ['vertical', 'horizontal'] as const;\n/** Whether each label sits above its value (`vertical`) or beside it (`horizontal`). */\nexport type DescriptionListOrientation =\n (typeof validDescriptionListOrientations)[number];\n\nconst isOrientation = (\n orientation?: string\n): orientation is DescriptionListOrientation =>\n !!orientation &&\n (validDescriptionListOrientations as readonly string[]).includes(orientation);\n\n/** Whether a prop was given something to render. */\nconst isGiven = (node: React.ReactNode) =>\n node !== undefined &&\n node !== null &&\n typeof node !== 'boolean' &&\n node !== '';\n\n/**\n * Props for the DescriptionList component. The helper props, the other\n * attributes and the `ref` land on the `<dl>`.\n *\n * @extraProp {React.Ref<HTMLDListElement>} [ref] - Ref forwarded to the `<dl>`.\n * @extraProp {string} [aria-labelledby] - The id of the heading above the list, which names it.\n */\nexport interface DescriptionListProps\n extends\n Omit<React.HTMLAttributes<HTMLDListElement>, 'color'>,\n Omit<BulmaClassesProps, 'color' | 'backgroundColor'> {\n /** Additional CSS classes to apply. */\n className?: string;\n /** `DescriptionList.Item`s, one for each fact. */\n children?: React.ReactNode;\n /**\n * Whether each label sits above its value or beside it, at every width no\n * `orientation<Viewport>` prop covers. Those props read the way a\n * `Column`'s `size<Viewport>` props do: `orientationMobile` applies below\n * Bulma's tablet breakpoint, each of the others from its own breakpoint\n * up, and where several apply the widest one wins. With none of them,\n * labels sit above their values below the tablet breakpoint and beside\n * them from it up. Beside, the labels share one column as wide as the\n * longest label, up to `--bulma-description-list-label-max-width`, so the\n * values line up.\n */\n orientation?: DescriptionListOrientation;\n /**\n * The orientation below Bulma's tablet breakpoint (769px). Falls back to\n * `orientation`, then to `vertical`.\n */\n orientationMobile?: DescriptionListOrientation;\n /**\n * The orientation from Bulma's tablet breakpoint (769px) up, until a wider\n * viewport's prop takes over. Falls back to `orientation`, then to\n * `horizontal`.\n */\n orientationTablet?: DescriptionListOrientation;\n /**\n * The orientation from Bulma's desktop breakpoint (1024px) up, until a\n * wider viewport's prop takes over. Falls back to `orientationTablet`, and\n * on down.\n */\n orientationDesktop?: DescriptionListOrientation;\n /**\n * The orientation from Bulma's widescreen breakpoint (1216px) up, until\n * `orientationFullhd` takes over. Falls back to `orientationDesktop`, and\n * on down.\n */\n orientationWidescreen?: DescriptionListOrientation;\n /**\n * The orientation from Bulma's fullhd breakpoint (1408px) up. Falls back to\n * `orientationWidescreen`, and on down.\n */\n orientationFullhd?: DescriptionListOrientation;\n /**\n * Draws a line between items, which helps the eye from a label to its\n * value across a wide list, and shows where each item ends to someone\n * zoomed in.\n */\n hasDividers?: boolean;\n /** Text color helper. It colors the values; the labels keep their own color. */\n textColor?: (typeof validColors)[number] | 'inherit' | 'current';\n /** Background color helper. */\n bgColor?: (typeof validColors)[number] | 'inherit' | 'current';\n}\n\n/**\n * Props for DescriptionList.Item, one fact. The helper props, the other\n * attributes and the `ref` land on its `<div>`.\n *\n * @extraProp {React.Ref<HTMLDivElement>} [ref] - Ref forwarded to the `<div>`.\n */\nexport interface DescriptionListItemProps\n extends\n Omit<React.HTMLAttributes<HTMLDivElement>, 'color'>,\n Omit<BulmaClassesProps, 'color' | 'backgroundColor'> {\n /** Additional CSS classes to apply. */\n className?: string;\n /**\n * The label, such as \"Order\", as a `DescriptionList.Label` before the\n * children.\n */\n label?: React.ReactNode;\n /**\n * The value, such as \"#1234\", as a `DescriptionList.Value` after the\n * children. Text, a link, a `Tag`: anything that renders. `undefined`,\n * `null` and `''` render nothing, so when a value is missing, say so in\n * words, such as \"Not given\", rather than leave a label with nothing after\n * it.\n */\n value?: React.ReactNode;\n /**\n * `DescriptionList.Label`s and `DescriptionList.Value`s, for what `label`\n * and `value` don't cover, such as a label with an info tip or several\n * values. They render between the two, so a `Label` here reads before\n * `value`, and a `Value` here after `label`.\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 DescriptionList.Label. The helper props, the other attributes and\n * the `ref` land on its `<dt>`.\n *\n * @extraProp {React.Ref<HTMLElement>} [ref] - Ref forwarded to the `<dt>`.\n */\nexport interface DescriptionListLabelProps\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 label, 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 DescriptionList.Value. The helper props, the other attributes and\n * the `ref` land on its `<dd>`.\n *\n * @extraProp {React.Ref<HTMLElement>} [ref] - Ref forwarded to the `<dd>`.\n */\nexport interface DescriptionListValueProps\n extends\n Omit<React.HTMLAttributes<HTMLElement>, 'color'>,\n Omit<BulmaClassesProps, 'color' | 'backgroundColor'> {\n /** Additional CSS classes to apply. */\n className?: string;\n /** The value. Anything goes, a list or a paragraph included. */\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 * A label, the name of a fact: a `<dt>`, in the weaker text color.\n *\n * @function\n * @param {DescriptionListLabelProps} props - Props for DescriptionList.Label.\n * @param {React.Ref<HTMLElement>} ref - Forwarded ref to the `<dt>`.\n * @returns {JSX.Element} The rendered label.\n *\n * @example\n * <DescriptionList.Label>Placed on</DescriptionList.Label>\n */\nexport const DescriptionListLabel = forwardRef<\n HTMLElement,\n DescriptionListLabelProps\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('description-list-label');\n return (\n <dt\n ref={ref}\n className={classNames(labelClass, bulmaHelperClasses, className)}\n {...rest}\n >\n {children}\n </dt>\n );\n});\n\nDescriptionListLabel.displayName = 'DescriptionList.Label';\n\n/**\n * A value, what the label says about the thing: a `<dd>`. An item may have several.\n *\n * @function\n * @param {DescriptionListValueProps} props - Props for DescriptionList.Value.\n * @param {React.Ref<HTMLElement>} ref - Forwarded ref to the `<dd>`.\n * @returns {JSX.Element} The rendered value.\n *\n * @example\n * <DescriptionList.Value>14 October 2026</DescriptionList.Value>\n */\nexport const DescriptionListValue = forwardRef<\n HTMLElement,\n DescriptionListValueProps\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('description-list-value');\n return (\n <dd\n ref={ref}\n className={classNames(valueClass, bulmaHelperClasses, className)}\n {...rest}\n >\n {children}\n </dd>\n );\n});\n\nDescriptionListValue.displayName = 'DescriptionList.Value';\n\n/**\n * One fact: its label and its value, in a `<div>`, from the `label` and `value` props or from `Label` and `Value` children.\n *\n * @function\n * @param {DescriptionListItemProps} props - Props for DescriptionList.Item.\n * @param {React.Ref<HTMLDivElement>} ref - Forwarded ref to the `<div>`.\n * @returns {JSX.Element} The rendered item.\n *\n * @example\n * <DescriptionList.Item label=\"Order\" value=\"#1234\" />\n *\n * @example\n * <DescriptionList.Item label=\"Tags\">\n * <DescriptionList.Value>Gift</DescriptionList.Value>\n * <DescriptionList.Value>Priority</DescriptionList.Value>\n * </DescriptionList.Item>\n */\nexport const DescriptionListItem = forwardRef<\n HTMLDivElement,\n DescriptionListItemProps\n>(\n (\n { className, label, value, textColor, bgColor, children, ...props },\n ref\n ) => {\n const { bulmaHelperClasses, rest } = useBulmaClasses({\n color: textColor,\n backgroundColor: bgColor,\n ...props,\n });\n const itemClass = usePrefixedClassNames('description-list-item');\n return (\n <div\n ref={ref}\n className={classNames(itemClass, bulmaHelperClasses, className)}\n {...rest}\n >\n {isGiven(label) && <DescriptionListLabel>{label}</DescriptionListLabel>}\n {children}\n {isGiven(value) && <DescriptionListValue>{value}</DescriptionListValue>}\n </div>\n );\n }\n);\n\nDescriptionListItem.displayName = 'DescriptionList.Item';\n\n/**\n * The `DescriptionList` component lists facts about one thing, each a label and its value, such as an order's details or a profile.\n *\n * It's a `<dl>`, with a `<div>` for each item, a `<dt>` for each label and\n * a `<dd>` for each value, so assistive tech gets HTML's own list of terms\n * and definitions and no roles are added. Labels sit above their values on\n * a phone and beside them from Bulma's tablet breakpoint up, where every\n * label shares one column so the values line up. For data with two\n * dimensions, such as several records with the same fields, use a `Table`.\n *\n * @function\n * @param {DescriptionListProps} props - Props for the DescriptionList component.\n * @param {React.Ref<HTMLDListElement>} ref - Forwarded ref to the `<dl>`.\n * @returns {JSX.Element} The rendered list.\n *\n * @example\n * <DescriptionList aria-labelledby=\"order-heading\">\n * <DescriptionList.Item label=\"Order\" value=\"#1234\" />\n * <DescriptionList.Item label=\"Placed on\" value=\"14 October 2026\" />\n * </DescriptionList>\n */\nconst DescriptionListRoot = forwardRef<HTMLDListElement, DescriptionListProps>(\n (\n {\n className,\n orientation,\n orientationMobile,\n orientationTablet,\n orientationDesktop,\n orientationWidescreen,\n orientationFullhd,\n hasDividers,\n textColor,\n bgColor,\n children,\n ...props\n },\n ref\n ) => {\n const { bulmaHelperClasses, rest } = useBulmaClasses({\n color: textColor,\n backgroundColor: bgColor,\n ...props,\n });\n // A class for each orientation given, which the stylesheet applies from\n // that viewport's breakpoint, so the layout is right on the first paint\n // and on the server. With none, the stylesheet's default is responsive.\n const rootClass = usePrefixedClassNames('description-list', {\n [`is-${orientation}`]: isOrientation(orientation),\n [`is-${orientationMobile}-mobile`]: isOrientation(orientationMobile),\n [`is-${orientationTablet}-tablet`]: isOrientation(orientationTablet),\n [`is-${orientationDesktop}-desktop`]: isOrientation(orientationDesktop),\n [`is-${orientationWidescreen}-widescreen`]: isOrientation(\n orientationWidescreen\n ),\n [`is-${orientationFullhd}-fullhd`]: isOrientation(orientationFullhd),\n 'has-dividers': !!hasDividers,\n });\n return (\n <dl\n ref={ref}\n className={classNames(rootClass, bulmaHelperClasses, className)}\n {...rest}\n >\n {children}\n </dl>\n );\n }\n);\n\nexport const DescriptionList = withSubComponents(\n DescriptionListRoot,\n {\n Item: DescriptionListItem,\n Label: DescriptionListLabel,\n Value: DescriptionListValue,\n },\n 'DescriptionList'\n);\n\nexport default DescriptionList;\n"],"names":["_jsx","_jsxs"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"DescriptionList.js","sources":["../../../src/elements/DescriptionList.tsx"],"sourcesContent":["import React, { forwardRef } 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';\n\nconst validDescriptionListOrientations = ['vertical', 'horizontal'] as const;\n/** Whether each label sits above its value (`vertical`) or beside it (`horizontal`). */\nexport type DescriptionListOrientation =\n (typeof validDescriptionListOrientations)[number];\n\nconst isOrientation = (\n orientation?: string\n): orientation is DescriptionListOrientation =>\n !!orientation &&\n (validDescriptionListOrientations as readonly string[]).includes(orientation);\n\n/**\n * Props for the DescriptionList component. The helper props, the other\n * attributes and the `ref` land on the `<dl>`.\n *\n * @extraProp {React.Ref<HTMLDListElement>} [ref] - Ref forwarded to the `<dl>`.\n * @extraProp {string} [aria-labelledby] - The id of the heading above the list, which names it.\n */\nexport interface DescriptionListProps\n extends\n Omit<React.HTMLAttributes<HTMLDListElement>, 'color'>,\n Omit<BulmaClassesProps, 'color' | 'backgroundColor'> {\n /** Additional CSS classes to apply. */\n className?: string;\n /** `DescriptionList.Item`s, one for each fact. */\n children?: React.ReactNode;\n /**\n * Whether each label sits above its value or beside it, at every width no\n * `orientation<Viewport>` prop covers. Those props read the way a\n * `Column`'s `size<Viewport>` props do: `orientationMobile` applies below\n * Bulma's tablet breakpoint, each of the others from its own breakpoint\n * up, and where several apply the widest one wins. With none of them,\n * labels sit above their values below the tablet breakpoint and beside\n * them from it up. Beside, the labels share one column as wide as the\n * longest label, up to `--bulma-description-list-label-max-width`, so the\n * values line up.\n */\n orientation?: DescriptionListOrientation;\n /**\n * The orientation below Bulma's tablet breakpoint (769px). Falls back to\n * `orientation`, then to `vertical`.\n */\n orientationMobile?: DescriptionListOrientation;\n /**\n * The orientation from Bulma's tablet breakpoint (769px) up, until a wider\n * viewport's prop takes over. Falls back to `orientation`, then to\n * `horizontal`.\n */\n orientationTablet?: DescriptionListOrientation;\n /**\n * The orientation from Bulma's desktop breakpoint (1024px) up, until a\n * wider viewport's prop takes over. Falls back to `orientationTablet`, and\n * on down.\n */\n orientationDesktop?: DescriptionListOrientation;\n /**\n * The orientation from Bulma's widescreen breakpoint (1216px) up, until\n * `orientationFullhd` takes over. Falls back to `orientationDesktop`, and\n * on down.\n */\n orientationWidescreen?: DescriptionListOrientation;\n /**\n * The orientation from Bulma's fullhd breakpoint (1408px) up. Falls back to\n * `orientationWidescreen`, and on down.\n */\n orientationFullhd?: DescriptionListOrientation;\n /**\n * Draws a line between items, which helps the eye from a label to its\n * value across a wide list, and shows where each item ends to someone\n * zoomed in.\n */\n hasDividers?: boolean;\n /** Text color helper. It colors the values; the labels keep their own color. */\n textColor?: (typeof validColors)[number] | 'inherit' | 'current';\n /** Background color helper. */\n bgColor?: (typeof validColors)[number] | 'inherit' | 'current';\n}\n\n/**\n * Props for DescriptionList.Item, one fact. The helper props, the other\n * attributes and the `ref` land on its `<div>`.\n *\n * @extraProp {React.Ref<HTMLDivElement>} [ref] - Ref forwarded to the `<div>`.\n */\nexport interface DescriptionListItemProps\n extends\n Omit<React.HTMLAttributes<HTMLDivElement>, 'color'>,\n Omit<BulmaClassesProps, 'color' | 'backgroundColor'> {\n /** Additional CSS classes to apply. */\n className?: string;\n /**\n * The label, such as \"Order\", as a `DescriptionList.Label` before the\n * children.\n */\n label?: React.ReactNode;\n /**\n * The value, such as \"#1234\", as a `DescriptionList.Value` after the\n * children. Text, a link, a `Tag`: anything that renders. `undefined`,\n * `null` and `''` render nothing, so when a value is missing, say so in\n * words, such as \"Not given\", rather than leave a label with nothing after\n * it.\n */\n value?: React.ReactNode;\n /**\n * `DescriptionList.Label`s and `DescriptionList.Value`s, for what `label`\n * and `value` don't cover, such as a label with an info tip or several\n * values. They render between the two, so a `Label` here reads before\n * `value`, and a `Value` here after `label`.\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 DescriptionList.Label. The helper props, the other attributes and\n * the `ref` land on its `<dt>`.\n *\n * @extraProp {React.Ref<HTMLElement>} [ref] - Ref forwarded to the `<dt>`.\n */\nexport interface DescriptionListLabelProps\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 label, 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 DescriptionList.Value. The helper props, the other attributes and\n * the `ref` land on its `<dd>`.\n *\n * @extraProp {React.Ref<HTMLElement>} [ref] - Ref forwarded to the `<dd>`.\n */\nexport interface DescriptionListValueProps\n extends\n Omit<React.HTMLAttributes<HTMLElement>, 'color'>,\n Omit<BulmaClassesProps, 'color' | 'backgroundColor'> {\n /** Additional CSS classes to apply. */\n className?: string;\n /** The value. Anything goes, a list or a paragraph included. */\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 * A label, the name of a fact: a `<dt>`, in the weaker text color.\n *\n * @function\n * @param {DescriptionListLabelProps} props - Props for DescriptionList.Label.\n * @param {React.Ref<HTMLElement>} ref - Forwarded ref to the `<dt>`.\n * @returns {JSX.Element} The rendered label.\n *\n * @example\n * <DescriptionList.Label>Placed on</DescriptionList.Label>\n */\nexport const DescriptionListLabel = forwardRef<\n HTMLElement,\n DescriptionListLabelProps\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('description-list-label');\n return (\n <dt\n ref={ref}\n className={classNames(labelClass, bulmaHelperClasses, className)}\n {...rest}\n >\n {children}\n </dt>\n );\n});\n\nDescriptionListLabel.displayName = 'DescriptionList.Label';\n\n/**\n * A value, what the label says about the thing: a `<dd>`. An item may have several.\n *\n * @function\n * @param {DescriptionListValueProps} props - Props for DescriptionList.Value.\n * @param {React.Ref<HTMLElement>} ref - Forwarded ref to the `<dd>`.\n * @returns {JSX.Element} The rendered value.\n *\n * @example\n * <DescriptionList.Value>14 October 2026</DescriptionList.Value>\n */\nexport const DescriptionListValue = forwardRef<\n HTMLElement,\n DescriptionListValueProps\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('description-list-value');\n return (\n <dd\n ref={ref}\n className={classNames(valueClass, bulmaHelperClasses, className)}\n {...rest}\n >\n {children}\n </dd>\n );\n});\n\nDescriptionListValue.displayName = 'DescriptionList.Value';\n\n/**\n * One fact: its label and its value, in a `<div>`, from the `label` and `value` props or from `Label` and `Value` children.\n *\n * @function\n * @param {DescriptionListItemProps} props - Props for DescriptionList.Item.\n * @param {React.Ref<HTMLDivElement>} ref - Forwarded ref to the `<div>`.\n * @returns {JSX.Element} The rendered item.\n *\n * @example\n * <DescriptionList.Item label=\"Order\" value=\"#1234\" />\n *\n * @example\n * <DescriptionList.Item label=\"Tags\">\n * <DescriptionList.Value>Gift</DescriptionList.Value>\n * <DescriptionList.Value>Priority</DescriptionList.Value>\n * </DescriptionList.Item>\n */\nexport const DescriptionListItem = forwardRef<\n HTMLDivElement,\n DescriptionListItemProps\n>(\n (\n { className, label, value, textColor, bgColor, children, ...props },\n ref\n ) => {\n const { bulmaHelperClasses, rest } = useBulmaClasses({\n color: textColor,\n backgroundColor: bgColor,\n ...props,\n });\n const itemClass = usePrefixedClassNames('description-list-item');\n return (\n <div\n ref={ref}\n className={classNames(itemClass, bulmaHelperClasses, className)}\n {...rest}\n >\n {isGiven(label) && <DescriptionListLabel>{label}</DescriptionListLabel>}\n {children}\n {isGiven(value) && <DescriptionListValue>{value}</DescriptionListValue>}\n </div>\n );\n }\n);\n\nDescriptionListItem.displayName = 'DescriptionList.Item';\n\n/**\n * The `DescriptionList` component lists facts about one thing, each a label and its value, such as an order's details or a profile.\n *\n * It's a `<dl>`, with a `<div>` for each item, a `<dt>` for each label and\n * a `<dd>` for each value, so assistive tech gets HTML's own list of terms\n * and definitions and no roles are added. Labels sit above their values on\n * a phone and beside them from Bulma's tablet breakpoint up, where every\n * label shares one column so the values line up. For data with two\n * dimensions, such as several records with the same fields, use a `Table`.\n *\n * @function\n * @param {DescriptionListProps} props - Props for the DescriptionList component.\n * @param {React.Ref<HTMLDListElement>} ref - Forwarded ref to the `<dl>`.\n * @returns {JSX.Element} The rendered list.\n *\n * @example\n * <DescriptionList aria-labelledby=\"order-heading\">\n * <DescriptionList.Item label=\"Order\" value=\"#1234\" />\n * <DescriptionList.Item label=\"Placed on\" value=\"14 October 2026\" />\n * </DescriptionList>\n */\nconst DescriptionListRoot = forwardRef<HTMLDListElement, DescriptionListProps>(\n (\n {\n className,\n orientation,\n orientationMobile,\n orientationTablet,\n orientationDesktop,\n orientationWidescreen,\n orientationFullhd,\n hasDividers,\n textColor,\n bgColor,\n children,\n ...props\n },\n ref\n ) => {\n const { bulmaHelperClasses, rest } = useBulmaClasses({\n color: textColor,\n backgroundColor: bgColor,\n ...props,\n });\n // A class for each orientation given, which the stylesheet applies from\n // that viewport's breakpoint, so the layout is right on the first paint\n // and on the server. With none, the stylesheet's default is responsive.\n const rootClass = usePrefixedClassNames('description-list', {\n [`is-${orientation}`]: isOrientation(orientation),\n [`is-${orientationMobile}-mobile`]: isOrientation(orientationMobile),\n [`is-${orientationTablet}-tablet`]: isOrientation(orientationTablet),\n [`is-${orientationDesktop}-desktop`]: isOrientation(orientationDesktop),\n [`is-${orientationWidescreen}-widescreen`]: isOrientation(\n orientationWidescreen\n ),\n [`is-${orientationFullhd}-fullhd`]: isOrientation(orientationFullhd),\n 'has-dividers': !!hasDividers,\n });\n return (\n <dl\n ref={ref}\n className={classNames(rootClass, bulmaHelperClasses, className)}\n {...rest}\n >\n {children}\n </dl>\n );\n }\n);\n\nexport const DescriptionList = withSubComponents(\n DescriptionListRoot,\n {\n Item: DescriptionListItem,\n Label: DescriptionListLabel,\n Value: DescriptionListValue,\n },\n 'DescriptionList'\n);\n\nexport default DescriptionList;\n"],"names":["_jsx","_jsxs"],"mappings":"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"}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/* @allxsmith/bestax-bulma — AI agents: see AGENTS.md in the package root, or https://bestax.io/llms.txt */
|
|
2
|
+
'use client';
|
|
3
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
4
|
+
import React, { forwardRef, useContext, useEffect, useMemo } from 'react';
|
|
5
|
+
import { usePrefixedClassNames, classNames } from '../helpers/classNames.js';
|
|
6
|
+
import { useBulmaClasses } from '../helpers/useBulmaClasses.js';
|
|
7
|
+
import { withSubComponents } from '../helpers/withSubComponents.js';
|
|
8
|
+
import { isGiven } from '../helpers/isGiven.js';
|
|
9
|
+
import { flattenChildren } from '../helpers/flattenChildren.js';
|
|
10
|
+
import { useNumberFormat } from '../helpers/localeFormat.js';
|
|
11
|
+
import { warnOnce } from '../helpers/devWarnings.js';
|
|
12
|
+
import { NumberFormatter } from './NumberFormatter.js';
|
|
13
|
+
import { Skeleton } from './Skeleton.js';
|
|
14
|
+
import { StatScopeContext } from './statContext.js';
|
|
15
|
+
|
|
16
|
+
/** How a change is written when no format is given: a signed percentage. */
|
|
17
|
+
const DEFAULT_CHANGE_FORMAT = {
|
|
18
|
+
style: 'percent',
|
|
19
|
+
signDisplay: 'exceptZero',
|
|
20
|
+
maximumFractionDigits: 1,
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* What the figure is: a `<dt>`, small and in the weaker text color.
|
|
24
|
+
*
|
|
25
|
+
* @function
|
|
26
|
+
* @param {StatLabelProps} props - Props for Stat.Label.
|
|
27
|
+
* @param {React.Ref<HTMLElement>} ref - Forwarded ref to the `<dt>`.
|
|
28
|
+
* @returns {JSX.Element} The rendered label.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* <Stat.Label>Revenue</Stat.Label>
|
|
32
|
+
*/
|
|
33
|
+
const StatLabel = forwardRef(({ className, textColor, bgColor, children, ...props }, ref) => {
|
|
34
|
+
const { bulmaHelperClasses, rest } = useBulmaClasses({
|
|
35
|
+
color: textColor,
|
|
36
|
+
backgroundColor: bgColor,
|
|
37
|
+
...props,
|
|
38
|
+
});
|
|
39
|
+
const labelClass = usePrefixedClassNames('stat-label');
|
|
40
|
+
return (jsx("dt", { ref: ref, className: classNames(labelClass, bulmaHelperClasses, className), ...rest, children: children }));
|
|
41
|
+
});
|
|
42
|
+
StatLabel.displayName = 'Stat.Label';
|
|
43
|
+
/**
|
|
44
|
+
* The figure: a `<dd>`, large, in the Stat's color, with digits of one width so a figure that updates doesn't shift.
|
|
45
|
+
*
|
|
46
|
+
* @function
|
|
47
|
+
* @param {StatValueProps} props - Props for Stat.Value.
|
|
48
|
+
* @param {React.Ref<HTMLElement>} ref - Forwarded ref to the `<dd>`.
|
|
49
|
+
* @returns {JSX.Element} The rendered figure.
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* <Stat.Value>
|
|
53
|
+
* <NumberFormatter value={182} format={{ style: 'unit', unit: 'millisecond' }} />
|
|
54
|
+
* </Stat.Value>
|
|
55
|
+
*/
|
|
56
|
+
const StatValue = forwardRef(({ className, textColor, bgColor, children, ...props }, ref) => {
|
|
57
|
+
const { bulmaHelperClasses, rest } = useBulmaClasses({
|
|
58
|
+
color: textColor,
|
|
59
|
+
backgroundColor: bgColor,
|
|
60
|
+
...props,
|
|
61
|
+
});
|
|
62
|
+
const valueClass = usePrefixedClassNames('stat-value');
|
|
63
|
+
return (jsx("dd", { ref: ref, className: classNames(valueClass, bulmaHelperClasses, className), ...rest, children: children }));
|
|
64
|
+
});
|
|
65
|
+
StatValue.displayName = 'Stat.Value';
|
|
66
|
+
/**
|
|
67
|
+
* A note under the figure: a `<dd>`, small and in the weaker text color.
|
|
68
|
+
*
|
|
69
|
+
* @function
|
|
70
|
+
* @param {StatDescriptionProps} props - Props for Stat.Description.
|
|
71
|
+
* @param {React.Ref<HTMLElement>} ref - Forwarded ref to the `<dd>`.
|
|
72
|
+
* @returns {JSX.Element} The rendered note.
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* <Stat.Description>since last month</Stat.Description>
|
|
76
|
+
*/
|
|
77
|
+
const StatDescription = forwardRef(({ className, textColor, bgColor, children, ...props }, ref) => {
|
|
78
|
+
const { bulmaHelperClasses, rest } = useBulmaClasses({
|
|
79
|
+
color: textColor,
|
|
80
|
+
backgroundColor: bgColor,
|
|
81
|
+
...props,
|
|
82
|
+
});
|
|
83
|
+
const descriptionClass = usePrefixedClassNames('stat-description');
|
|
84
|
+
return (jsx("dd", { ref: ref, className: classNames(descriptionClass, bulmaHelperClasses, className), ...rest, children: children }));
|
|
85
|
+
});
|
|
86
|
+
StatDescription.displayName = 'Stat.Description';
|
|
87
|
+
/**
|
|
88
|
+
* 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.
|
|
89
|
+
*
|
|
90
|
+
* The sign is in the text, written by `Intl` for the locale, so a screen
|
|
91
|
+
* reader hears the direction; the arrow is hidden from assistive tech, and
|
|
92
|
+
* the color repeats the direction for sighted readers rather than carry it.
|
|
93
|
+
*
|
|
94
|
+
* @function
|
|
95
|
+
* @param {StatChangeProps} props - Props for Stat.Change.
|
|
96
|
+
* @param {React.Ref<HTMLElement>} ref - Forwarded ref to the `<dd>`.
|
|
97
|
+
* @returns {JSX.Element} The rendered change.
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* <Stat.Change value={-0.08} color="success" />
|
|
101
|
+
*/
|
|
102
|
+
const StatChange = forwardRef(({ className, value, format, locale, color, ...props }, ref) => {
|
|
103
|
+
const { bulmaHelperClasses, rest } = useBulmaClasses(props);
|
|
104
|
+
const scope = useContext(StatScopeContext);
|
|
105
|
+
const ownLocale = locale || scope.locale;
|
|
106
|
+
const options = format
|
|
107
|
+
? { ...format, signDisplay: format.signDisplay || 'exceptZero' }
|
|
108
|
+
: DEFAULT_CHANGE_FORMAT;
|
|
109
|
+
// A change that rounds to zero gets no arrow and no tone, which would
|
|
110
|
+
// contradict a figure of zero. `Intl` writes no sign with 'exceptZero'
|
|
111
|
+
// only when the value rounds to zero, by whatever rounding the options
|
|
112
|
+
// ask for, so the change written that way is zero's text exactly then.
|
|
113
|
+
const formatNumber = useNumberFormat(ownLocale);
|
|
114
|
+
const signed = { ...options, signDisplay: 'exceptZero' };
|
|
115
|
+
const direction = formatNumber(value, signed) === formatNumber(0, signed)
|
|
116
|
+
? 0
|
|
117
|
+
: Math.sign(value);
|
|
118
|
+
const tone = color ||
|
|
119
|
+
(direction > 0 ? 'success' : direction < 0 ? 'danger' : undefined);
|
|
120
|
+
const changeClass = usePrefixedClassNames('stat-change', {
|
|
121
|
+
[`is-${tone}`]: !!tone,
|
|
122
|
+
});
|
|
123
|
+
return (jsxs("dd", { ref: ref, className: classNames(changeClass, bulmaHelperClasses, className), ...rest, children: [(direction > 0 || direction < 0) && (jsx("svg", { viewBox: "0 0 16 16", width: "1em", height: "1em", "aria-hidden": "true", children: jsx("path", { d: direction > 0
|
|
124
|
+
? 'M8 13V3M3.5 7.5 8 3l4.5 4.5'
|
|
125
|
+
: 'M8 3v10M3.5 8.5 8 13l4.5-4.5', fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", strokeLinejoin: "round" }) })), jsx(NumberFormatter, { value: value, format: options, locale: ownLocale })] }));
|
|
126
|
+
});
|
|
127
|
+
StatChange.displayName = 'Stat.Change';
|
|
128
|
+
/**
|
|
129
|
+
* 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.
|
|
130
|
+
*
|
|
131
|
+
* It's a label and its values in a description list: a `<dt>` for the
|
|
132
|
+
* label, and a `<dd>` each for the figure, the change and the note. On its
|
|
133
|
+
* own it renders its own `<dl>`; inside `Stats` it's a `<div>` in the row's.
|
|
134
|
+
* A number `value` is written by `NumberFormatter`, so it follows a
|
|
135
|
+
* `LocaleProvider`. A change is written with its sign ("+12%"), so the
|
|
136
|
+
* direction is in the text, beside a decorative arrow and a color.
|
|
137
|
+
*
|
|
138
|
+
* @function
|
|
139
|
+
* @param {StatProps} props - Props for the Stat component.
|
|
140
|
+
* @param {React.Ref<HTMLElement>} ref - Forwarded ref to the root.
|
|
141
|
+
* @returns {JSX.Element} The rendered stat.
|
|
142
|
+
*
|
|
143
|
+
* @example
|
|
144
|
+
* <Stat
|
|
145
|
+
* label="Revenue"
|
|
146
|
+
* value={8456.4}
|
|
147
|
+
* format={{ style: 'currency', currency: 'USD' }}
|
|
148
|
+
* change={0.12}
|
|
149
|
+
* description="since last month"
|
|
150
|
+
* />
|
|
151
|
+
*/
|
|
152
|
+
const StatRoot = forwardRef(({ className, label, value, format, locale, change, changeFormat, changeColor, description, size, loading, textColor, bgColor, children, ...props }, ref) => {
|
|
153
|
+
const scope = useContext(StatScopeContext);
|
|
154
|
+
const { bulmaHelperClasses, rest } = useBulmaClasses({
|
|
155
|
+
color: textColor || scope.textColor,
|
|
156
|
+
backgroundColor: bgColor,
|
|
157
|
+
...props,
|
|
158
|
+
});
|
|
159
|
+
const statClass = usePrefixedClassNames('stat', {
|
|
160
|
+
[`is-${size}`]: !!size,
|
|
161
|
+
});
|
|
162
|
+
// The parts given as children, by type, so the figure's `<dd>` and the
|
|
163
|
+
// warning below know what's there. A part rendered from inside another
|
|
164
|
+
// component isn't seen.
|
|
165
|
+
const parts = flattenChildren(children);
|
|
166
|
+
const hasPart = (type) => parts.some(part => React.isValidElement(part) && part.type === type);
|
|
167
|
+
const hasValuePart = hasPart(StatValue);
|
|
168
|
+
const labelled = isGiven(label) || hasPart(StatLabel);
|
|
169
|
+
// Without a label, the figure has no name and the values no term.
|
|
170
|
+
useEffect(() => {
|
|
171
|
+
if (!labelled) {
|
|
172
|
+
warnOnce('Stat:unlabelled', '[bestax-bulma] <Stat> has no label. Pass label, or a Stat.Label ' +
|
|
173
|
+
'child, so the figure has a name for assistive tech and its ' +
|
|
174
|
+
'values have a term in the list.');
|
|
175
|
+
}
|
|
176
|
+
}, [labelled]);
|
|
177
|
+
// Its parts take its locale and leave the row, so a Stat inside one of
|
|
178
|
+
// them, in a `<dd>`, renders a `<dl>` of its own.
|
|
179
|
+
const inner = useMemo(() => ({ row: false, locale }), [locale]);
|
|
180
|
+
const Root = scope.row ? 'div' : 'dl';
|
|
181
|
+
const figure = loading ? (jsx(Skeleton, {})) : typeof value === 'number' ? (jsx(NumberFormatter, { value: value, format: format, locale: locale })) : isGiven(value) ? (value) : null;
|
|
182
|
+
// A `Stat.Value` child is the figure. Without one, the figure's `<dd>`
|
|
183
|
+
// renders for a figure, or for a label, empty while the figure is
|
|
184
|
+
// missing, so the label keeps its definition.
|
|
185
|
+
const showsFigure = !hasValuePart && (figure !== null || labelled);
|
|
186
|
+
return (jsx(Root, { ref: ref, className: classNames(statClass, bulmaHelperClasses, className), ...rest, "aria-busy": loading || rest['aria-busy'], children: jsxs(StatScopeContext.Provider, { value: inner, children: [isGiven(label) && jsx(StatLabel, { children: label }), children, showsFigure && jsx(StatValue, { children: figure }), typeof change === 'number' && (jsx(StatChange, { value: change, format: changeFormat, color: changeColor })), isGiven(description) && (jsx(StatDescription, { children: description }))] }) }));
|
|
187
|
+
});
|
|
188
|
+
const Stat = withSubComponents(StatRoot, {
|
|
189
|
+
Label: StatLabel,
|
|
190
|
+
Value: StatValue,
|
|
191
|
+
Change: StatChange,
|
|
192
|
+
Description: StatDescription,
|
|
193
|
+
}, 'Stat');
|
|
194
|
+
|
|
195
|
+
export { Stat, StatChange, StatDescription, StatLabel, StatValue };
|
|
196
|
+
//# sourceMappingURL=Stat.js.map
|
|
@@ -0,0 +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":"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"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/* @allxsmith/bestax-bulma — AI agents: see AGENTS.md in the package root, or https://bestax.io/llms.txt */
|
|
2
|
+
'use client';
|
|
3
|
+
import { jsx } from 'react/jsx-runtime';
|
|
4
|
+
import { forwardRef, useMemo } from 'react';
|
|
5
|
+
import { usePrefixedClassNames, classNames } from '../helpers/classNames.js';
|
|
6
|
+
import { useBulmaClasses } from '../helpers/useBulmaClasses.js';
|
|
7
|
+
import { withSubComponents } from '../helpers/withSubComponents.js';
|
|
8
|
+
import { Stat } from './Stat.js';
|
|
9
|
+
import { StatScopeContext } from './statContext.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The `Stats` component lays out a row of `Stat`s, such as the key figures at the top of a dashboard, as one description list on Bulma's smart grid.
|
|
13
|
+
*
|
|
14
|
+
* It's a `<dl>` with a `<div>` for each Stat, so a screen reader announces
|
|
15
|
+
* the row once, then each label with its values. The tiles fill the grid's
|
|
16
|
+
* columns, each at least `minCol` wide, and wrap without media queries;
|
|
17
|
+
* the `gap` helper prop sets the space between them. Name the row with
|
|
18
|
+
* `aria-labelledby` pointing at the heading above it. `Stat` is also
|
|
19
|
+
* `Stats.Stat`.
|
|
20
|
+
*
|
|
21
|
+
* @function
|
|
22
|
+
* @param {StatsProps} props - Props for the Stats component.
|
|
23
|
+
* @param {React.Ref<HTMLDListElement>} ref - Forwarded ref to the `<dl>`.
|
|
24
|
+
* @returns {JSX.Element} The rendered row.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* <Stats aria-labelledby="kpis" minCol={8} gap="4">
|
|
28
|
+
* <Stat label="Revenue" value={8456.4} format={{ style: 'currency', currency: 'USD' }} />
|
|
29
|
+
* <Stat label="New customers" value={1234} change={-0.019} />
|
|
30
|
+
* </Stats>
|
|
31
|
+
*/
|
|
32
|
+
const StatsRoot = forwardRef(({ className, minCol, textColor, bgColor, children, ...props }, ref) => {
|
|
33
|
+
const { bulmaHelperClasses, rest } = useBulmaClasses({
|
|
34
|
+
color: textColor,
|
|
35
|
+
backgroundColor: bgColor,
|
|
36
|
+
...props,
|
|
37
|
+
});
|
|
38
|
+
const statsClass = usePrefixedClassNames('stats grid', {
|
|
39
|
+
[`is-col-min-${minCol}`]: minCol !== undefined && minCol !== null,
|
|
40
|
+
});
|
|
41
|
+
const scope = useMemo(() => ({ row: true, textColor }), [textColor]);
|
|
42
|
+
return (jsx("dl", { ref: ref, className: classNames(statsClass, bulmaHelperClasses, className), ...rest, children: jsx(StatScopeContext.Provider, { value: scope, children: children }) }));
|
|
43
|
+
});
|
|
44
|
+
StatsRoot.displayName = 'Stats';
|
|
45
|
+
const Stats = withSubComponents(StatsRoot, { Stat }, 'Stats');
|
|
46
|
+
|
|
47
|
+
export { Stats };
|
|
48
|
+
//# sourceMappingURL=Stats.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Stats.js","sources":["../../../src/elements/Stats.tsx"],"sourcesContent":["import React, { forwardRef, 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 type { BulmaMinColValue } from '../grid/Grid';\nimport { Stat } from './Stat';\nimport { StatScopeContext } from './statContext';\n\n/**\n * Props for the Stats component, a row of Stats on Bulma's smart grid. The\n * helper props, the other attributes and the `ref` land on its `<dl>`.\n *\n * @extraProp {React.Ref<HTMLDListElement>} [ref] - Ref forwarded to the `<dl>`.\n * @extraProp {BulmaGapStep} [gap] - The space between tiles (`is-gap-*`), the helper prop with the values Grid's `gap` takes.\n * @extraProp {string} [aria-labelledby] - The id of the heading above the row, which names it.\n */\nexport interface StatsProps\n extends\n Omit<React.HTMLAttributes<HTMLDListElement>, 'color'>,\n Omit<BulmaClassesProps, 'color' | 'backgroundColor'> {\n /** Additional CSS classes to apply. */\n className?: string;\n /** `Stat`s, one per figure, and nothing else: they're the groups of its `<dl>`. */\n children?: React.ReactNode;\n /**\n * The narrowest a tile gets before the row wraps (`is-col-min-*`), as\n * Grid's `minCol`: 1.5rem a step. Unset, it's Bulma's 9rem.\n */\n minCol?: BulmaMinColValue;\n /**\n * Text color helper: `has-text-*` on the `<dl>`, as on Buttons. Each Stat\n * in the row without a `textColor` of its own takes it too, which colors\n * its figure, since a Stat sets its own color. The labels, the changes and\n * the notes keep theirs.\n */\n textColor?: (typeof validColors)[number] | 'inherit' | 'current';\n /** Background color helper. */\n bgColor?: (typeof validColors)[number] | 'inherit' | 'current';\n}\n\n/**\n * The `Stats` component lays out a row of `Stat`s, such as the key figures at the top of a dashboard, as one description list on Bulma's smart grid.\n *\n * It's a `<dl>` with a `<div>` for each Stat, so a screen reader announces\n * the row once, then each label with its values. The tiles fill the grid's\n * columns, each at least `minCol` wide, and wrap without media queries;\n * the `gap` helper prop sets the space between them. Name the row with\n * `aria-labelledby` pointing at the heading above it. `Stat` is also\n * `Stats.Stat`.\n *\n * @function\n * @param {StatsProps} props - Props for the Stats component.\n * @param {React.Ref<HTMLDListElement>} ref - Forwarded ref to the `<dl>`.\n * @returns {JSX.Element} The rendered row.\n *\n * @example\n * <Stats aria-labelledby=\"kpis\" minCol={8} gap=\"4\">\n * <Stat label=\"Revenue\" value={8456.4} format={{ style: 'currency', currency: 'USD' }} />\n * <Stat label=\"New customers\" value={1234} change={-0.019} />\n * </Stats>\n */\nconst StatsRoot = forwardRef<HTMLDListElement, StatsProps>(\n ({ className, minCol, textColor, bgColor, children, ...props }, ref) => {\n const { bulmaHelperClasses, rest } = useBulmaClasses({\n color: textColor,\n backgroundColor: bgColor,\n ...props,\n });\n const statsClass = usePrefixedClassNames('stats grid', {\n [`is-col-min-${minCol}`]: minCol !== undefined && minCol !== null,\n });\n const scope = useMemo(() => ({ row: true, textColor }), [textColor]);\n return (\n <dl\n ref={ref}\n className={classNames(statsClass, bulmaHelperClasses, className)}\n {...rest}\n >\n <StatScopeContext.Provider value={scope}>\n {children}\n </StatScopeContext.Provider>\n </dl>\n );\n }\n);\n\nStatsRoot.displayName = 'Stats';\n\nexport const Stats = withSubComponents(StatsRoot, { Stat }, 'Stats');\n\nexport default Stats;\n"],"names":["_jsx"],"mappings":";;;;;;;;;;AA4CA;;;;;;;;;;;;;;;;;;;;AAoBG;AACH,MAAM,SAAS,GAAG,UAAU,CAC1B,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,KAAK,EAAE,EAAE,GAAG,KAAI;AACrE,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,EAAE;QACrD,CAAC,CAAA,WAAA,EAAc,MAAM,CAAA,CAAE,GAAG,MAAM,KAAK,SAAS,IAAI,MAAM,KAAK,IAAI;AAClE,KAAA,CAAC;IACF,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC,SAAS,CAAC,CAAC;AACpE,IAAA,QACEA,GAAA,CAAA,IAAA,EAAA,EACE,GAAG,EAAE,GAAG,EACR,SAAS,EAAE,UAAU,CAAC,UAAU,EAAE,kBAAkB,EAAE,SAAS,CAAC,EAAA,GAC5D,IAAI,EAAA,QAAA,EAERA,GAAA,CAAC,gBAAgB,CAAC,QAAQ,EAAA,EAAC,KAAK,EAAE,KAAK,EAAA,QAAA,EACpC,QAAQ,EAAA,CACiB,EAAA,CACzB;AAET,CAAC,CACF;AAED,SAAS,CAAC,WAAW,GAAG,OAAO;AAExB,MAAM,KAAK,GAAG,iBAAiB,CAAC,SAAS,EAAE,EAAE,IAAI,EAAE,EAAE,OAAO;;"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/* @allxsmith/bestax-bulma — AI agents: see AGENTS.md in the package root, or https://bestax.io/llms.txt */
|
|
2
|
+
'use client';
|
|
3
|
+
import { createContext } from 'react';
|
|
4
|
+
|
|
5
|
+
// INTERNAL, deliberately not exported from src/index.ts: what a Stat and its
|
|
6
|
+
// parts read from around them.
|
|
7
|
+
const StatScopeContext = createContext({ row: false });
|
|
8
|
+
|
|
9
|
+
export { StatScopeContext };
|
|
10
|
+
//# sourceMappingURL=statContext.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"statContext.js","sources":["../../../src/elements/statContext.ts"],"sourcesContent":["// INTERNAL, deliberately not exported from src/index.ts: what a Stat and its\n// parts read from around them.\n\nimport { createContext } from 'react';\nimport type { validColors } from '../helpers/useBulmaClasses';\n\nexport interface StatScope {\n /**\n * Whether this is inside a `Stats` row, where a Stat is a `<div>` in the\n * row's `<dl>` rather than a `<dl>` of its own. A Stat sets it back to\n * `false` for its children. Those are meant to be its parts, so a Stat\n * inside a part sits in a `<dd>`, where a `<dl>` of its own is valid. A\n * Stat written straight among another's children isn't handled: it renders\n * a `<dl>` inside the other's list, which HTML doesn't allow, and Stat's\n * `children` TSDoc says not to.\n */\n row: boolean;\n /** The row's `textColor`, which each Stat in it takes unless it has its own. */\n textColor?: (typeof validColors)[number] | 'inherit' | 'current';\n /** The Stat's `locale`, which a `Stat.Change` in it takes unless it has its own. */\n locale?: string;\n}\n\nexport const StatScopeContext = createContext<StatScope>({ row: false });\n"],"names":[],"mappings":";;;;AAAA;AACA;AAsBO,MAAM,gBAAgB,GAAG,aAAa,CAAY,EAAE,GAAG,EAAE,KAAK,EAAE;;"}
|