@allxsmith/bestax-bulma 5.33.1 → 5.34.1
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 +139 -0
- package/dist/esm/components/EmptyState.js.map +1 -0
- package/dist/esm/elements/Buttons.js.map +1 -1
- package/dist/esm/elements/SubTitle.js.map +1 -1
- package/dist/esm/elements/Title.js.map +1 -1
- package/dist/esm/form/Field.js +3 -3
- package/dist/esm/form/Field.js.map +1 -1
- package/dist/esm/form/FormContext.js +2 -1
- package/dist/esm/form/FormContext.js.map +1 -1
- package/dist/esm/form/Taginput.js +36 -6
- package/dist/esm/form/Taginput.js.map +1 -1
- package/dist/esm/form/useAutoLabelId.js.map +1 -1
- package/dist/extras.css +1 -1
- package/dist/extras.css.map +1 -1
- package/dist/index.cjs +1151 -989
- package/dist/index.cjs.map +1 -1
- package/dist/index.esm.js +1 -0
- package/dist/index.esm.js.map +1 -1
- package/dist/types/components/EmptyState.d.ts +149 -0
- package/dist/types/elements/Buttons.d.ts +1 -1
- package/dist/types/elements/SubTitle.d.ts +13 -2
- package/dist/types/elements/Title.d.ts +11 -2
- package/dist/types/form/Field.d.ts +5 -5
- package/dist/types/form/FormContext.d.ts +2 -1
- package/dist/types/form/Taginput.d.ts +8 -3
- package/dist/types/form/useAutoLabelId.d.ts +2 -2
- package/dist/types/index.d.ts +1 -0
- package/dist/types-cjs/components/EmptyState.d.ts +149 -0
- package/dist/types-cjs/elements/Buttons.d.ts +1 -1
- package/dist/types-cjs/elements/SubTitle.d.ts +13 -2
- package/dist/types-cjs/elements/Title.d.ts +11 -2
- package/dist/types-cjs/form/Field.d.ts +5 -5
- package/dist/types-cjs/form/FormContext.d.ts +2 -1
- package/dist/types-cjs/form/Taginput.d.ts +8 -3
- package/dist/types-cjs/form/useAutoLabelId.d.ts +2 -2
- package/dist/types-cjs/index.d.ts +1 -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/components/_emptystate.scss +243 -0
- package/src/scss/components/_index.scss +1 -0
package/dist/index.cjs
CHANGED
|
@@ -2841,237 +2841,921 @@ const Dropdown = withSubComponents(DropdownComponent, {
|
|
|
2841
2841
|
Divider: DropdownDivider,
|
|
2842
2842
|
}, 'Dropdown');
|
|
2843
2843
|
|
|
2844
|
-
|
|
2845
|
-
const MenuListLevelContext = React.createContext(0);
|
|
2844
|
+
const validButtonColors = [...validColors, 'text', 'ghost'];
|
|
2846
2845
|
/**
|
|
2847
|
-
* The `
|
|
2846
|
+
* The `Button` component provides a flexible and highly customizable button for your Bulma React UI.
|
|
2848
2847
|
*
|
|
2849
2848
|
* @function
|
|
2850
|
-
* @param {
|
|
2851
|
-
* @
|
|
2852
|
-
* @
|
|
2849
|
+
* @param {ButtonProps} props - Props for the Button component.
|
|
2850
|
+
* @param {React.Ref} ref - Forwarded ref to the element `as` renders.
|
|
2851
|
+
* @returns {JSX.Element} The rendered button, anchor, or custom element.
|
|
2852
|
+
* @see {@link https://bulma.io/documentation/elements/button/ | Bulma Button documentation}
|
|
2853
2853
|
*/
|
|
2854
|
-
const
|
|
2855
|
-
const {
|
|
2854
|
+
const Button = React.forwardRef(function Button(props, ref) {
|
|
2855
|
+
const { color, size, isLight, isRounded, isLoading, isStatic, isFullwidth, isFullWidth, isOutlined, isInverted, isFocused, isActive, isHovered, isDisabled, className, children, textColor, bgColor, as: Component = 'button', href, onClick, target, rel, ...bulmaProps } = props;
|
|
2856
|
+
const { bulmaHelperClasses, rest } = useBulmaClasses({
|
|
2857
|
+
color: textColor,
|
|
2858
|
+
backgroundColor: bgColor,
|
|
2859
|
+
...bulmaProps,
|
|
2860
|
+
});
|
|
2856
2861
|
// Generate Bulma classes with prefix
|
|
2857
|
-
const bulmaClasses = usePrefixedClassNames('
|
|
2858
|
-
|
|
2859
|
-
|
|
2862
|
+
const bulmaClasses = usePrefixedClassNames('button', {
|
|
2863
|
+
[`is-${color}`]: color &&
|
|
2864
|
+
validButtonColors.includes(color),
|
|
2865
|
+
[`is-${size}`]: size,
|
|
2866
|
+
'is-outlined': isOutlined,
|
|
2867
|
+
'is-light': isLight,
|
|
2868
|
+
'is-loading': isLoading,
|
|
2869
|
+
'is-static': isStatic,
|
|
2870
|
+
'is-disabled': isDisabled,
|
|
2871
|
+
'is-rounded': isRounded,
|
|
2872
|
+
'is-hovered': isHovered,
|
|
2873
|
+
'is-focused': isFocused,
|
|
2874
|
+
'is-active': isActive,
|
|
2875
|
+
'is-inverted': isInverted,
|
|
2876
|
+
'is-fullwidth': isFullwidth ?? isFullWidth,
|
|
2877
|
+
});
|
|
2878
|
+
// Combine prefixed Bulma classes with unprefixed user className and prefixed helper classes
|
|
2879
|
+
const buttonClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
|
|
2880
|
+
if (Component !== 'button') {
|
|
2881
|
+
// Strip the form-control attributes a link-like element cannot carry, so a
|
|
2882
|
+
// JavaScript consumer or a spread object doesn't put them on the DOM. The
|
|
2883
|
+
// types no longer allow them through; this is the runtime backstop.
|
|
2884
|
+
//
|
|
2885
|
+
// Only for INTRINSIC tags. A custom component (a router Link, ...) owns its
|
|
2886
|
+
// prop contract, and `ComponentPropsWithoutRef<T>` promises the caller that
|
|
2887
|
+
// its props reach it. Stripping names by their spelling alone broke that
|
|
2888
|
+
// promise silently: `<Button as={Custom} name="x" />` type-checked while
|
|
2889
|
+
// `Custom` never received the `name` it requires.
|
|
2890
|
+
//
|
|
2891
|
+
// `autoFocus` and `type` are deliberately NOT stripped: both are valid on
|
|
2892
|
+
// targets this list was filtering them from. `autoFocus` is a global
|
|
2893
|
+
// attribute (React decides per element whether to honour it, which is not
|
|
2894
|
+
// ours to pre-empt), and `type` on an anchor is the MIME hint that
|
|
2895
|
+
// `ButtonProps<'a'>` now types it as.
|
|
2896
|
+
//
|
|
2897
|
+
// The types already answer this for a TypeScript caller: `ButtonProps<T>`
|
|
2898
|
+
// derives from `ComponentPropsWithoutRef<T>`, so only what the target
|
|
2899
|
+
// accepts can be passed. What is left is a runtime backstop for JavaScript
|
|
2900
|
+
// consumers and spread objects.
|
|
2901
|
+
//
|
|
2902
|
+
// Keep it to the two cases where a stray attribute does something. The
|
|
2903
|
+
// submit-overrides belong to a submit control and never make sense on the
|
|
2904
|
+
// anchor path. `disabled` is the one that is VISIBLE: Bulma styles
|
|
2905
|
+
// `.button[disabled]` (background, border, shadow, opacity), so letting it
|
|
2906
|
+
// through to a `<span>` greys the element out — main stripped it, and not
|
|
2907
|
+
// stripping it would be a silent visual change on a types-only release.
|
|
2908
|
+
//
|
|
2909
|
+
// Everything else (`name`, `value`, `form`) is inert where it does not
|
|
2910
|
+
// belong, and enumerating which tags own it is what kept going wrong:
|
|
2911
|
+
// first all nine names on every non-button tag (so `<Button as="input"
|
|
2912
|
+
// name="query" value="Search">` submitted nothing), then a form-control
|
|
2913
|
+
// allowlist that still lost `formAction` on an input and `value` on an
|
|
2914
|
+
// option. Those now ride on the types instead.
|
|
2915
|
+
//
|
|
2916
|
+
// A custom component is exempt entirely — it owns its prop contract, and
|
|
2917
|
+
// `ComponentPropsWithoutRef<T>` promises the caller its props arrive.
|
|
2918
|
+
//
|
|
2919
|
+
// The elements that own `disabled`, per the HTML spec. A closed set, unlike
|
|
2920
|
+
// the open-ended question of which tag owns which attribute.
|
|
2921
|
+
const SUBMIT_OVERRIDES = [
|
|
2922
|
+
'formAction',
|
|
2923
|
+
'formEncType',
|
|
2924
|
+
'formMethod',
|
|
2925
|
+
'formNoValidate',
|
|
2926
|
+
'formTarget',
|
|
2927
|
+
];
|
|
2928
|
+
const OWNS_DISABLED = [
|
|
2929
|
+
'button',
|
|
2930
|
+
'fieldset',
|
|
2931
|
+
'input',
|
|
2932
|
+
'optgroup',
|
|
2933
|
+
'option',
|
|
2934
|
+
'select',
|
|
2935
|
+
'textarea',
|
|
2936
|
+
];
|
|
2937
|
+
// Two independent filters. They are not exclusive — an `<a>` owns neither
|
|
2938
|
+
// the submit-overrides nor `disabled`, so it takes both — and deriving them
|
|
2939
|
+
// from one destructure, or from one if/else, is what broke each of them in
|
|
2940
|
+
// turn: first the overrides came off every intrinsic (an `as="input"` lost
|
|
2941
|
+
// `formAction`), then scoping them to the anchor let `disabled` back onto
|
|
2942
|
+
// it.
|
|
2943
|
+
const forwardedRest = { ...rest };
|
|
2944
|
+
if (typeof Component === 'string' && !isCustomElement(Component)) {
|
|
2945
|
+
// Meaningless anywhere but a submit control, and an anchor is not one.
|
|
2946
|
+
if (Component === 'a') {
|
|
2947
|
+
for (const key of SUBMIT_OVERRIDES)
|
|
2948
|
+
delete forwardedRest[key];
|
|
2949
|
+
}
|
|
2950
|
+
// Visible via Bulma's `.button[disabled]`, so withheld from any element
|
|
2951
|
+
// that does not own it.
|
|
2952
|
+
if (!OWNS_DISABLED.includes(Component))
|
|
2953
|
+
delete forwardedRest.disabled;
|
|
2954
|
+
}
|
|
2955
|
+
return (jsxRuntime.jsx(Component, { ref: ref, className: buttonClasses, href: href, target: target, rel: rel, "aria-disabled": isDisabled, tabIndex: isDisabled ? -1 : undefined, onClick: isDisabled
|
|
2956
|
+
? // Guarded, because this handler is installed into whatever `as`
|
|
2957
|
+
// renders and a custom component's `onClick` need not be a DOM
|
|
2958
|
+
// event handler at all. One declaring `onClick: (v: string) =>
|
|
2959
|
+
// void` calls this with a string, and an unguarded
|
|
2960
|
+
// `e.preventDefault()` throws `is not a function`. Blocking the
|
|
2961
|
+
// real event is the point; a foreign payload is simply not ours
|
|
2962
|
+
// to act on.
|
|
2963
|
+
(e) => {
|
|
2964
|
+
if (typeof e?.preventDefault === 'function')
|
|
2965
|
+
e.preventDefault();
|
|
2966
|
+
}
|
|
2967
|
+
: onClick, ...forwardedRest, children: children }));
|
|
2968
|
+
}
|
|
2969
|
+
return (jsxRuntime.jsx("button", { ref: ref, className: buttonClasses, disabled: isDisabled, onClick: onClick, ...rest, children: children }));
|
|
2970
|
+
});
|
|
2971
|
+
Button.displayName = 'Button';
|
|
2972
|
+
|
|
2860
2973
|
/**
|
|
2861
|
-
*
|
|
2974
|
+
* The `LinkButton` component renders a `<button>` that visually looks like text or a link.
|
|
2862
2975
|
*
|
|
2863
2976
|
* @function
|
|
2864
|
-
* @param {
|
|
2865
|
-
* @
|
|
2977
|
+
* @param {LinkButtonProps} props - Props for the LinkButton component.
|
|
2978
|
+
* @param {React.Ref} ref - Forwarded ref to the element `as` renders.
|
|
2979
|
+
* @returns {JSX.Element} The rendered link-styled button element.
|
|
2980
|
+
*
|
|
2981
|
+
* @example
|
|
2982
|
+
* // Text variant (default)
|
|
2983
|
+
* <LinkButton onClick={handleClick}>Click me</LinkButton>
|
|
2984
|
+
*
|
|
2985
|
+
* @example
|
|
2986
|
+
* // Underline variant with color
|
|
2987
|
+
* <LinkButton variant="underline" color="primary">Learn more</LinkButton>
|
|
2866
2988
|
*/
|
|
2867
|
-
const
|
|
2868
|
-
const
|
|
2869
|
-
|
|
2870
|
-
|
|
2989
|
+
const LinkButton = React.forwardRef(function LinkButton({ variant = 'text', color, className, ...props }, ref) {
|
|
2990
|
+
const buttonColor = variant === 'underline' ? 'text' : variant;
|
|
2991
|
+
const prefixedClasses = usePrefixedClassNames('link-button', color && `link-button-${color}`, variant === 'underline' && 'link-button-underline');
|
|
2992
|
+
return (jsxRuntime.jsx(Button
|
|
2993
|
+
// LinkButton is polymorphic through to Button, which resolves the element
|
|
2994
|
+
// from `as`. TS cannot infer that through the spread below.
|
|
2995
|
+
, {
|
|
2996
|
+
// LinkButton is polymorphic through to Button, which resolves the element
|
|
2997
|
+
// from `as`. TS cannot infer that through the spread below.
|
|
2998
|
+
ref: ref, color: buttonColor, className: classNames(prefixedClasses, className), ...props }));
|
|
2999
|
+
});
|
|
3000
|
+
LinkButton.displayName = 'LinkButton';
|
|
3001
|
+
|
|
2871
3002
|
/**
|
|
2872
|
-
*
|
|
3003
|
+
* The `ToggleButton` component renders a button that stays pressed until it's pressed again, and says whether it's on through `aria-pressed`.
|
|
3004
|
+
*
|
|
3005
|
+
* It's always a `<button type="button">`, so it never submits a form and
|
|
3006
|
+
* carries no form value: a setting saved with a form is a `Checkbox` or a
|
|
3007
|
+
* `Switch`. A set of toggles that each turn on and off on their own goes in
|
|
3008
|
+
* `Buttons hasAddons` with `role="group"` and an `aria-label`. A set where only
|
|
3009
|
+
* one can be on is a radio group rather than a row of these: use
|
|
3010
|
+
* `SegmentedControl`, or `Radios` for a choice that's sent with a form.
|
|
2873
3011
|
*
|
|
2874
3012
|
* @function
|
|
2875
|
-
* @param {
|
|
2876
|
-
* @
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
*
|
|
2889
|
-
* no-op, the way both siblings' sets are.
|
|
3013
|
+
* @param {ToggleButtonProps} props - Props for the ToggleButton component.
|
|
3014
|
+
* @param {React.Ref<HTMLButtonElement>} ref - Forwarded ref to the `<button>`.
|
|
3015
|
+
* @returns {JSX.Element} The rendered toggle button.
|
|
3016
|
+
* @see {@link https://www.w3.org/WAI/ARIA/apg/patterns/button/ | APG button pattern}
|
|
3017
|
+
*
|
|
3018
|
+
* @example
|
|
3019
|
+
* <ToggleButton onPressedChange={setMuted}>Mute</ToggleButton>
|
|
3020
|
+
*
|
|
3021
|
+
* @example
|
|
3022
|
+
* <Buttons hasAddons role="group" aria-label="Text style">
|
|
3023
|
+
* <ToggleButton aria-label="Bold" pressed={bold} onPressedChange={setBold}>
|
|
3024
|
+
* <Icon name="bold" />
|
|
3025
|
+
* </ToggleButton>
|
|
3026
|
+
* </Buttons>
|
|
2890
3027
|
*/
|
|
2891
|
-
const
|
|
3028
|
+
const ToggleButton = React.forwardRef(function ToggleButton({ pressed: controlledPressed, defaultPressed = false, onPressedChange, color = 'link', isLoading, 'aria-busy': ariaBusy, 'aria-disabled': ariaDisabled, className, onClick, ...props }, ref) {
|
|
3029
|
+
const isControlled = controlledPressed !== undefined;
|
|
3030
|
+
const [internalPressed, setInternalPressed] = React.useState(defaultPressed);
|
|
3031
|
+
const pressed = controlledPressed ?? internalPressed;
|
|
3032
|
+
// Prefixed like Button's own classes, which a hand-written
|
|
3033
|
+
// `className="is-selected"` could not be under ConfigProvider classPrefix.
|
|
3034
|
+
// Bulma lifts `is-selected` above its neighbours in `.buttons.has-addons`.
|
|
3035
|
+
const selectedClass = usePrefixedClassNames({ 'is-selected': pressed });
|
|
3036
|
+
// The caller's onClick runs first, so a `preventDefault()` there vetoes
|
|
3037
|
+
// the toggle, as it does in Radix, MUI and Zag. Enter and Space reach here
|
|
3038
|
+
// through the button's own click, and a disabled button gets no click.
|
|
3039
|
+
//
|
|
3040
|
+
// A loading one gets the click but drops it. Bulma's spinner only stops a
|
|
3041
|
+
// pointer, so without this a key flipped a toggle the mouse couldn't.
|
|
3042
|
+
// `disabled` would stop both, but it takes focus away mid-save.
|
|
3043
|
+
const handleClick = (event) => {
|
|
3044
|
+
if (isLoading)
|
|
3045
|
+
return;
|
|
3046
|
+
onClick?.(event);
|
|
3047
|
+
if (event.defaultPrevented)
|
|
3048
|
+
return;
|
|
3049
|
+
const next = !pressed;
|
|
3050
|
+
onPressedChange?.(next);
|
|
3051
|
+
if (!isControlled)
|
|
3052
|
+
setInternalPressed(next);
|
|
3053
|
+
};
|
|
3054
|
+
// The props are spread first, so what a toggle depends on wins over
|
|
3055
|
+
// anything a JavaScript caller or a spread object passes along: it stays
|
|
3056
|
+
// a button, never submits, reports the state it's in, and says it's busy
|
|
3057
|
+
// and won't respond while it's loading. A caller's own `aria-busy` or
|
|
3058
|
+
// `aria-disabled` stays when it isn't loading.
|
|
3059
|
+
return (jsxRuntime.jsx(Button, { ...props, ref: ref, as: "button", type: "button", isLoading: isLoading, "aria-busy": isLoading || ariaBusy, "aria-disabled": isLoading || ariaDisabled, "aria-pressed": pressed, color: pressed ? color : undefined, className: classNames(selectedClass, className), onClick: handleClick }));
|
|
3060
|
+
});
|
|
3061
|
+
ToggleButton.displayName = 'ToggleButton';
|
|
3062
|
+
|
|
3063
|
+
const validButtonsSizes = ['small', 'medium', 'large'];
|
|
2892
3064
|
/**
|
|
2893
|
-
*
|
|
3065
|
+
* The `Buttons` component lets you group multiple `Button` elements together with Bulma's spacing, alignment, and add-on features.
|
|
2894
3066
|
*
|
|
2895
3067
|
* @function
|
|
2896
|
-
* @param {
|
|
2897
|
-
* @
|
|
2898
|
-
* @
|
|
3068
|
+
* @param {ButtonsProps} props - Props for the Buttons component.
|
|
3069
|
+
* @returns {JSX.Element} The rendered group of buttons.
|
|
3070
|
+
* @see {@link https://bulma.io/documentation/elements/button/#group | Bulma Button Group documentation}
|
|
2899
3071
|
*/
|
|
2900
|
-
const
|
|
2901
|
-
const
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
// intrinsic-only strip. Before #641 `href` was an own prop re-applied only
|
|
2907
|
-
// for `as="a"`; deriving it from `as` must not quietly widen that.
|
|
2908
|
-
const isLinkLike = Component === 'a' ||
|
|
2909
|
-
typeof Component !== 'string' ||
|
|
2910
|
-
isCustomElement(Component);
|
|
2911
|
-
// `href` ALONE. `Dropdown.Item` strips every anchor-only attribute except
|
|
2912
|
-
// `type`; `Level.Item` strips all of them, plus `rel`.
|
|
2913
|
-
//
|
|
2914
|
-
// Not because nothing else can arrive: `as` here is an open `React.ElementType`,
|
|
2915
|
-
// and a non-fresh spread skips excess-property checking, so a typed caller
|
|
2916
|
-
// CAN deliver `target` or `download` and they are forwarded. The set stays at
|
|
2917
|
-
// `href` because widening it would be wrong for an open `as` — `referrerPolicy`
|
|
2918
|
-
// on `as="img"`, `target` on `as="form"` and `media` on `as="source"` are all
|
|
2919
|
-
// legal, and a flat strip would delete them. Getting that right needs a
|
|
2920
|
-
// per-attribute-per-element rule, which `bestax-migrate` already models in
|
|
2921
|
-
// `LINK_ATTR_ELEMENTS`.
|
|
2922
|
-
const linkProps = isLinkLike
|
|
2923
|
-
? forwarded
|
|
2924
|
-
: omitAttrs(forwarded, STRIP_FROM_NON_LINK);
|
|
2925
|
-
const itemClass = classNames({ [usePrefixedClassNames('is-active')]: active }, bulmaHelperClasses);
|
|
2926
|
-
// Split children into label and nested MenuList(s)
|
|
2927
|
-
const labelChildren = [];
|
|
2928
|
-
const nestedMenuLists = [];
|
|
2929
|
-
React.Children.forEach(children, child => {
|
|
2930
|
-
if (React.isValidElement(child) && child.type === MenuList) {
|
|
2931
|
-
nestedMenuLists.push(child);
|
|
2932
|
-
}
|
|
2933
|
-
else {
|
|
2934
|
-
labelChildren.push(child);
|
|
2935
|
-
}
|
|
3072
|
+
const ButtonsComponent = ({ className, textColor, color, bgColor, isCentered, isRight, hasAddons, size, children, ...props }) => {
|
|
3073
|
+
const buttonsClasses = usePrefixedClassNames('buttons', {
|
|
3074
|
+
'is-centered': isCentered,
|
|
3075
|
+
'is-right': isRight,
|
|
3076
|
+
'has-addons': hasAddons,
|
|
3077
|
+
[`are-${size}`]: size && validButtonsSizes.includes(size),
|
|
2936
3078
|
});
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
});
|
|
2941
|
-
MenuItem.displayName = 'MenuItem';
|
|
2942
|
-
// Attach static subcomponents
|
|
2943
|
-
const Menu = withSubComponents(MenuComponent, {
|
|
2944
|
-
Label: MenuLabel,
|
|
2945
|
-
List: MenuList,
|
|
2946
|
-
Item: MenuItem,
|
|
2947
|
-
}, 'Menu');
|
|
2948
|
-
|
|
2949
|
-
const validMessageSizes = ['small', 'medium', 'large'];
|
|
2950
|
-
/**
|
|
2951
|
-
* The `Message` component provides Bulma's flexible notice/message box for your Bulma React UI.
|
|
2952
|
-
*
|
|
2953
|
-
* @function
|
|
2954
|
-
* @param {MessageProps} props - Props for the Message component.
|
|
2955
|
-
* @returns {JSX.Element} The rendered message.
|
|
2956
|
-
* @see {@link https://bulma.io/documentation/components/message/ | Bulma Message documentation}
|
|
2957
|
-
*/
|
|
2958
|
-
const MessageComponent = ({ className, title, textColor, color, bgColor, size, onClose, children, ...props }) => {
|
|
2959
|
-
const { classPrefix } = useConfig();
|
|
3079
|
+
/**
|
|
3080
|
+
* Generates Bulma helper classes and separates out remaining props.
|
|
3081
|
+
*/
|
|
2960
3082
|
const { bulmaHelperClasses, rest } = useBulmaClasses({
|
|
2961
|
-
color: textColor,
|
|
3083
|
+
color: textColor ?? color,
|
|
2962
3084
|
backgroundColor: bgColor,
|
|
2963
3085
|
...props,
|
|
2964
3086
|
});
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
[`is-${color}`]: color,
|
|
2968
|
-
[`is-${size}`]: size && validMessageSizes.includes(size),
|
|
2969
|
-
});
|
|
2970
|
-
const deleteClass = usePrefixedClassNames('delete');
|
|
2971
|
-
const messageClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
|
|
2972
|
-
return (jsxRuntime.jsxs("article", { className: messageClasses, ...rest, "data-testid": "message", children: [(title || onClose) && (jsxRuntime.jsxs("div", { className: prefixedClassNames(classPrefix, 'message-header'), children: [title && jsxRuntime.jsx("span", { children: title }), onClose && (jsxRuntime.jsx("button", { className: deleteClass, "aria-label": "delete", onClick: onClose, type: "button", "data-testid": "message-close" }))] })), children && (jsxRuntime.jsx("div", { className: prefixedClassNames(classPrefix, 'message-body'), "data-testid": "message-body", children: children }))] }));
|
|
3087
|
+
const combinedClasses = classNames(buttonsClasses, className, bulmaHelperClasses);
|
|
3088
|
+
return (jsxRuntime.jsx("div", { className: combinedClasses, ...rest, children: children }));
|
|
2973
3089
|
};
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
* @returns {JSX.Element} The rendered message header.
|
|
2980
|
-
*/
|
|
2981
|
-
const MessageHeader = ({ className, children, ...props }) => (jsxRuntime.jsx("div", { className: classNames(usePrefixedClassNames('message-header'), className), ...props, children: children }));
|
|
2982
|
-
/**
|
|
2983
|
-
* Message body compound component. Renders a `.message-body` element.
|
|
2984
|
-
*
|
|
2985
|
-
* @function
|
|
2986
|
-
* @param {MessageBodyProps} props - Props for the MessageBody component.
|
|
2987
|
-
* @returns {JSX.Element} The rendered message body.
|
|
2988
|
-
*/
|
|
2989
|
-
const MessageBody = ({ className, children, ...props }) => (jsxRuntime.jsx("div", { className: classNames(usePrefixedClassNames('message-body'), className), ...props, children: children }));
|
|
2990
|
-
// Attach compound components
|
|
2991
|
-
const Message = withSubComponents(MessageComponent, {
|
|
2992
|
-
Header: MessageHeader,
|
|
2993
|
-
Body: MessageBody,
|
|
2994
|
-
}, 'Message');
|
|
3090
|
+
const Buttons = withSubComponents(ButtonsComponent, {
|
|
3091
|
+
Button,
|
|
3092
|
+
LinkButton,
|
|
3093
|
+
ToggleButton,
|
|
3094
|
+
}, 'Buttons');
|
|
2995
3095
|
|
|
2996
|
-
// Ref-counted across every caller (e.g. a Dialog rendering its own Modal)
|
|
2997
|
-
// so overlapping/nested locks don't fight over which one restores `overflow`.
|
|
2998
|
-
// Every overlay that locks body scroll — Modal, Dialog, Sidebar, Loading —
|
|
2999
|
-
// goes through here; a component setting `document.body.style.overflow`
|
|
3000
|
-
// itself would clear a lock another overlay still needs.
|
|
3001
|
-
let lockCount = 0;
|
|
3002
|
-
let originalOverflow = '';
|
|
3003
3096
|
/**
|
|
3004
|
-
*
|
|
3005
|
-
*
|
|
3006
|
-
*
|
|
3097
|
+
* Strips a redundant leading library prefix from an icon name (e.g. `fa-check` -> `check`
|
|
3098
|
+
* for the `fa` library, `mdi-account` -> `account` for `mdi`), so `name` behaves the same
|
|
3099
|
+
* whether or not the caller includes the prefix. Font Awesome and Material Design Icons docs
|
|
3100
|
+
* conventionally refer to icons with their class prefix (`fa-check`), which otherwise gets
|
|
3101
|
+
* doubled by `getIconClasses` (producing `fa-fa-check`, matching no glyph).
|
|
3007
3102
|
*
|
|
3008
|
-
* @
|
|
3009
|
-
* @param
|
|
3103
|
+
* @param {string} name - The icon name, possibly including a redundant library prefix.
|
|
3104
|
+
* @param {IconLibrary} library - The icon library the name will be rendered with.
|
|
3105
|
+
* @returns {string} The icon name with any redundant leading library prefix removed.
|
|
3010
3106
|
*/
|
|
3011
|
-
function
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
if (lockCount === 0) {
|
|
3023
|
-
document.body.style.overflow = originalOverflow;
|
|
3024
|
-
}
|
|
3025
|
-
};
|
|
3026
|
-
}, [active]);
|
|
3107
|
+
function stripRedundantLibraryPrefix(name, library) {
|
|
3108
|
+
if (!name) {
|
|
3109
|
+
return name;
|
|
3110
|
+
}
|
|
3111
|
+
if (library === 'fa' && name.startsWith('fa-')) {
|
|
3112
|
+
return name.substring(3);
|
|
3113
|
+
}
|
|
3114
|
+
if (library === 'mdi' && name.startsWith('mdi-')) {
|
|
3115
|
+
return name.substring(4);
|
|
3116
|
+
}
|
|
3117
|
+
return name;
|
|
3027
3118
|
}
|
|
3028
|
-
|
|
3029
|
-
// "Are we past hydration?" as a store that never changes: React reads the
|
|
3030
|
-
// server snapshot both while server-rendering and while hydrating, and the
|
|
3031
|
-
// client snapshot from the first post-hydration render onward.
|
|
3032
|
-
const subscribeToNothing = () => () => { };
|
|
3033
|
-
const getClientSnapshot = () => true;
|
|
3034
|
-
const getServerSnapshot = () => false;
|
|
3035
3119
|
/**
|
|
3036
|
-
*
|
|
3037
|
-
*
|
|
3038
|
-
* Returns `false` during server rendering and during the hydrating client
|
|
3039
|
-
* render, then `true` from the commit that follows. Use it to defer
|
|
3040
|
-
* DOM-only behaviour (such as portalling) past hydration so the first client
|
|
3041
|
-
* render matches the server markup instead of tripping hydration recovery.
|
|
3042
|
-
*
|
|
3043
|
-
* Only a component that hydrates ever sees `false` on the client. One that
|
|
3044
|
-
* mounts later (after a route change, or in an app with no server rendering)
|
|
3045
|
-
* gets `true` on its first render, so it pays for no extra render. It
|
|
3046
|
-
* subscribes to nothing, so no later render flips it back.
|
|
3120
|
+
* Gets the correct classes for the icon element based on the library and features.
|
|
3047
3121
|
*
|
|
3048
|
-
* @
|
|
3049
|
-
* @
|
|
3122
|
+
* @param {IconLibrary} library - The icon library.
|
|
3123
|
+
* @param {string} name - The icon name.
|
|
3124
|
+
* @param {string} [variant] - Icon style variant (e.g., 'solid', 'outlined', 'rounded').
|
|
3125
|
+
* @param {string | string[]} [features] - Additional library-specific modifiers.
|
|
3126
|
+
* @returns {string} The combined class string for the icon.
|
|
3050
3127
|
*/
|
|
3051
|
-
function
|
|
3052
|
-
|
|
3128
|
+
function getIconClasses(library, name, variant, features) {
|
|
3129
|
+
let baseClass;
|
|
3130
|
+
let iconClass;
|
|
3131
|
+
const featureList = Array.isArray(features)
|
|
3132
|
+
? features
|
|
3133
|
+
: features
|
|
3134
|
+
? [features]
|
|
3135
|
+
: [];
|
|
3136
|
+
switch (library) {
|
|
3137
|
+
case 'fa': {
|
|
3138
|
+
// Font Awesome: use variant as style ('solid' -> 'fas', 'regular' -> 'far', etc.)
|
|
3139
|
+
const styleMap = {
|
|
3140
|
+
solid: 'fas',
|
|
3141
|
+
regular: 'far',
|
|
3142
|
+
brands: 'fab',
|
|
3143
|
+
light: 'fal',
|
|
3144
|
+
duotone: 'fad',
|
|
3145
|
+
thin: 'fat',
|
|
3146
|
+
};
|
|
3147
|
+
const faStyle = variant ? styleMap[variant] || variant : 'fas';
|
|
3148
|
+
baseClass = faStyle;
|
|
3149
|
+
iconClass = `fa-${name}`;
|
|
3150
|
+
return [baseClass, iconClass, ...featureList].join(' ');
|
|
3151
|
+
}
|
|
3152
|
+
case 'mdi':
|
|
3153
|
+
// Material Design Icons: no variants, just features
|
|
3154
|
+
baseClass = 'mdi';
|
|
3155
|
+
iconClass = `mdi-${name}`;
|
|
3156
|
+
return [baseClass, iconClass, ...featureList].join(' ');
|
|
3157
|
+
case 'material-icons': {
|
|
3158
|
+
// Google Material Icons: map variants to full class names
|
|
3159
|
+
const styleVariants = {
|
|
3160
|
+
filled: 'material-icons',
|
|
3161
|
+
outlined: 'material-icons-outlined',
|
|
3162
|
+
round: 'material-icons-round',
|
|
3163
|
+
sharp: 'material-icons-sharp',
|
|
3164
|
+
};
|
|
3165
|
+
baseClass = variant
|
|
3166
|
+
? styleVariants[variant] || `material-icons-${variant}`
|
|
3167
|
+
: 'material-icons';
|
|
3168
|
+
return [baseClass, ...featureList].join(' ');
|
|
3169
|
+
}
|
|
3170
|
+
case 'material-symbols': {
|
|
3171
|
+
// Google Material Symbols: map variants to full class names
|
|
3172
|
+
const styleVariants = {
|
|
3173
|
+
outlined: 'material-symbols-outlined',
|
|
3174
|
+
rounded: 'material-symbols-rounded',
|
|
3175
|
+
sharp: 'material-symbols-sharp',
|
|
3176
|
+
};
|
|
3177
|
+
baseClass = variant
|
|
3178
|
+
? styleVariants[variant] || `material-symbols-${variant}`
|
|
3179
|
+
: 'material-symbols-outlined';
|
|
3180
|
+
return [baseClass, ...featureList].join(' ');
|
|
3181
|
+
}
|
|
3182
|
+
default:
|
|
3183
|
+
// fallback: just icon name and features
|
|
3184
|
+
return [name, ...featureList].join(' ');
|
|
3185
|
+
}
|
|
3053
3186
|
}
|
|
3054
|
-
|
|
3055
3187
|
/**
|
|
3056
|
-
*
|
|
3057
|
-
* `string` is treated as a `document.querySelector` selector (falling back to
|
|
3058
|
-
* `document.body` when it matches nothing), and any falsy value — `undefined`
|
|
3059
|
-
* or the empty string — resolves to `document.body`.
|
|
3060
|
-
*
|
|
3061
|
-
* The empty string is deliberately treated as "no target" rather than passed
|
|
3062
|
-
* through: `document.querySelector('')` throws a `SyntaxError`, and callers
|
|
3063
|
-
* building a selector from state can easily hand us `''`.
|
|
3188
|
+
* The `Icon` component is a Bulma-styled wrapper for displaying icons from various libraries (Font Awesome, Material Design Icons, Ionicons, Google Material Icons, Material Symbols, etc.).
|
|
3064
3189
|
*
|
|
3065
|
-
* @function
|
|
3066
|
-
* @param
|
|
3067
|
-
* @returns The
|
|
3190
|
+
* @function
|
|
3191
|
+
* @param {IconProps} props - Props for the Icon component.
|
|
3192
|
+
* @returns {JSX.Element} The rendered icon element.
|
|
3193
|
+
* @see {@link https://bulma.io/documentation/elements/icon/ | Bulma Icon documentation}
|
|
3068
3194
|
*/
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3195
|
+
const Icon = ({ className, textColor, bgColor, name, library, variant, features, libraryFeatures, // Deprecated but maintained for backward compatibility
|
|
3196
|
+
size, ariaLabel, style, icon, // Capture and exclude the deprecated 'icon' prop from DOM
|
|
3197
|
+
color: _color, // Exclude 'color' prop if passed directly
|
|
3198
|
+
containerClassName, children, ...restProps }) => {
|
|
3199
|
+
// Get the default icon library from context, fallback to 'fa' if not set
|
|
3200
|
+
const defaultLibrary = useIconLibrary();
|
|
3201
|
+
/**
|
|
3202
|
+
* Generates Bulma helper classes and separates out remaining props.
|
|
3203
|
+
* Note: variant, features, and libraryFeatures are excluded from props spread
|
|
3204
|
+
*/
|
|
3205
|
+
const { bulmaHelperClasses, rest } = useBulmaClasses({
|
|
3206
|
+
color: textColor,
|
|
3207
|
+
backgroundColor: bgColor,
|
|
3208
|
+
...restProps,
|
|
3209
|
+
});
|
|
3210
|
+
// Hoisted unconditionally to respect rules-of-hooks; the branches below
|
|
3211
|
+
// pick which result to consume.
|
|
3212
|
+
const defaultIconClasses = usePrefixedClassNames('icon', {
|
|
3213
|
+
[`is-${size}`]: size,
|
|
3214
|
+
});
|
|
3215
|
+
const sizeModifierClass = usePrefixedClassNames(size ? `is-${size}` : undefined);
|
|
3216
|
+
const bulmaClasses = containerClassName
|
|
3217
|
+
? containerClassName
|
|
3218
|
+
: defaultIconClasses;
|
|
3219
|
+
const iconContainerClasses = classNames(bulmaClasses, containerClassName && size ? sizeModifierClass : undefined, bulmaHelperClasses, className);
|
|
3220
|
+
// A named icon is an image, whose name is its label alone, so a ligature inside
|
|
3221
|
+
// never leaks into it. An unnamed one is decorative and hidden outright. Spread
|
|
3222
|
+
// before `rest`, so a caller's own `role` or `aria-hidden` still wins.
|
|
3223
|
+
const named = !!ariaLabel || !!restProps['aria-label'] || !!restProps['aria-labelledby'];
|
|
3224
|
+
// A caller's own `role` or `tabIndex` on an unnamed icon means it isn't decoration (an
|
|
3225
|
+
// icon made a button, say), so it isn't hidden either: `aria-hidden` on something
|
|
3226
|
+
// focusable is a failure of its own.
|
|
3227
|
+
const a11yProps = named
|
|
3228
|
+
? { role: 'img', 'aria-label': ariaLabel || undefined }
|
|
3229
|
+
: restProps.role !== undefined || restProps.tabIndex !== undefined
|
|
3230
|
+
? {}
|
|
3231
|
+
: { 'aria-hidden': true };
|
|
3232
|
+
if (children !== undefined) {
|
|
3233
|
+
// `IconChildrenProps`: render the caller's node (an inline SVG, a `react-icons`
|
|
3234
|
+
// component, …) in place of a class-based glyph. Library/variant/features don't apply.
|
|
3235
|
+
return (jsxRuntime.jsx("span", { className: iconContainerClasses, ...a11yProps, style: style, ...rest, children: children }));
|
|
3236
|
+
}
|
|
3237
|
+
// `name` is guaranteed once `children` is absent UNLESS the caller is on the deprecated
|
|
3238
|
+
// `icon` path, which `IconDeprecatedProps` declares with `name?: undefined`.
|
|
3239
|
+
// (`IconChildrenProps['children']` excludes `undefined`, so `children={undefined}` can't
|
|
3240
|
+
// slip past into this branch.) The cast covers that third member, and the `!name && icon`
|
|
3241
|
+
// fallback below is what fills it in.
|
|
3242
|
+
let finalName = name;
|
|
3243
|
+
if (!name && icon) {
|
|
3244
|
+
// If icon prop is provided instead of name, try to parse it
|
|
3245
|
+
// e.g., "mdi mdi-rocket-launch" -> "rocket-launch"
|
|
3246
|
+
if (typeof icon === 'string') {
|
|
3247
|
+
const parts = icon.split(' ');
|
|
3248
|
+
const lastPart = parts[parts.length - 1];
|
|
3249
|
+
if (lastPart.startsWith('mdi-')) {
|
|
3250
|
+
finalName = lastPart.substring(4); // Remove "mdi-" prefix
|
|
3251
|
+
}
|
|
3252
|
+
else if (lastPart.startsWith('fa-')) {
|
|
3253
|
+
finalName = lastPart.substring(3); // Remove "fa-" prefix
|
|
3254
|
+
}
|
|
3255
|
+
else {
|
|
3256
|
+
finalName = lastPart;
|
|
3257
|
+
}
|
|
3258
|
+
}
|
|
3259
|
+
}
|
|
3260
|
+
const finalLibrary = library || defaultLibrary || 'fa';
|
|
3261
|
+
// Normalize a redundant leading library prefix (e.g. "fa-check" -> "check" when the
|
|
3262
|
+
// library is 'fa'), so `name` behaves identically with or without the prefix.
|
|
3263
|
+
finalName = stripRedundantLibraryPrefix(finalName, finalLibrary);
|
|
3264
|
+
if (!finalName) {
|
|
3265
|
+
// No glyph to name. Reachable through the public type only as `icon=""`, and otherwise
|
|
3266
|
+
// by a plain-JS caller (or one that casts). Building a class off an absent name produced
|
|
3267
|
+
// a bogus `fa-undefined` glyph; render the bare container instead, matching the
|
|
3268
|
+
// `children` branch.
|
|
3269
|
+
return (jsxRuntime.jsx("span", { className: iconContainerClasses, ...a11yProps, style: style, ...rest }));
|
|
3270
|
+
}
|
|
3271
|
+
// Backward compatibility: if libraryFeatures is provided, parse it for variant and features
|
|
3272
|
+
let finalVariant = variant;
|
|
3273
|
+
let finalFeatures = features;
|
|
3274
|
+
if (libraryFeatures && !variant && !features) {
|
|
3275
|
+
const legacyFeatures = Array.isArray(libraryFeatures)
|
|
3276
|
+
? libraryFeatures
|
|
3277
|
+
: [libraryFeatures];
|
|
3278
|
+
// For Font Awesome, extract style from features
|
|
3279
|
+
if (finalLibrary === 'fa') {
|
|
3280
|
+
const faStyle = legacyFeatures.find(f => [
|
|
3281
|
+
'fas',
|
|
3282
|
+
'far',
|
|
3283
|
+
'fab',
|
|
3284
|
+
'fal',
|
|
3285
|
+
'fad',
|
|
3286
|
+
'fat',
|
|
3287
|
+
'solid',
|
|
3288
|
+
'regular',
|
|
3289
|
+
'brands',
|
|
3290
|
+
'light',
|
|
3291
|
+
'duotone',
|
|
3292
|
+
'thin',
|
|
3293
|
+
].includes(f));
|
|
3294
|
+
if (faStyle) {
|
|
3295
|
+
finalVariant = faStyle;
|
|
3296
|
+
finalFeatures = legacyFeatures.filter(f => f !== faStyle);
|
|
3297
|
+
}
|
|
3298
|
+
else {
|
|
3299
|
+
finalFeatures = legacyFeatures;
|
|
3300
|
+
}
|
|
3301
|
+
}
|
|
3302
|
+
// For Material Icons/Symbols, extract style variant
|
|
3303
|
+
else if (finalLibrary === 'material-icons' ||
|
|
3304
|
+
finalLibrary === 'material-symbols') {
|
|
3305
|
+
const styleVariants = finalLibrary === 'material-icons'
|
|
3306
|
+
? ['filled', 'outlined', 'round', 'sharp']
|
|
3307
|
+
: ['outlined', 'rounded', 'sharp'];
|
|
3308
|
+
const styleVariant = legacyFeatures.find(f => styleVariants.includes(f));
|
|
3309
|
+
if (styleVariant) {
|
|
3310
|
+
finalVariant = styleVariant;
|
|
3311
|
+
finalFeatures = legacyFeatures.filter(f => f !== styleVariant);
|
|
3312
|
+
}
|
|
3313
|
+
else {
|
|
3314
|
+
finalFeatures = legacyFeatures;
|
|
3315
|
+
}
|
|
3316
|
+
}
|
|
3317
|
+
// For others, all features go to finalFeatures
|
|
3318
|
+
else {
|
|
3319
|
+
finalFeatures = legacyFeatures;
|
|
3320
|
+
}
|
|
3321
|
+
}
|
|
3322
|
+
// Handle web components vs CSS-based icons
|
|
3323
|
+
if (finalLibrary === 'ion') {
|
|
3324
|
+
// For Ionicons, handle variant in the name
|
|
3325
|
+
let ionName = finalName;
|
|
3326
|
+
if (finalVariant === 'outline') {
|
|
3327
|
+
ionName = `${finalName}-outline`;
|
|
3328
|
+
}
|
|
3329
|
+
else if (finalVariant === 'sharp') {
|
|
3330
|
+
ionName = `${finalName}-sharp`;
|
|
3331
|
+
}
|
|
3332
|
+
return (jsxRuntime.jsx("span", { className: iconContainerClasses, ...a11yProps, style: style, ...rest, children: jsxRuntime.jsx("ion-icon", { name: ionName }) }));
|
|
3333
|
+
}
|
|
3334
|
+
// Legacy CSS-based icons
|
|
3335
|
+
const iClasses = getIconClasses(finalLibrary, finalName, finalVariant, finalFeatures);
|
|
3336
|
+
// Material Icons and Material Symbols use text content, not CSS classes for the icon name
|
|
3337
|
+
if (finalLibrary === 'material-icons' ||
|
|
3338
|
+
finalLibrary === 'material-symbols') {
|
|
3339
|
+
return (jsxRuntime.jsx("span", { className: iconContainerClasses, ...a11yProps, style: style, ...rest, children: jsxRuntime.jsx("i", { className: iClasses, children: finalName }) }));
|
|
3340
|
+
}
|
|
3341
|
+
return (jsxRuntime.jsx("span", { className: iconContainerClasses, ...a11yProps, style: style, ...rest, children: jsxRuntime.jsx("i", { className: iClasses }) }));
|
|
3342
|
+
};
|
|
3343
|
+
|
|
3344
|
+
/**
|
|
3345
|
+
* The `Paragraph` component renders a styled `<p>` element with Bulma helper class integration.
|
|
3346
|
+
*
|
|
3347
|
+
* @function
|
|
3348
|
+
* @param {ParagraphProps} props - Props for the Paragraph component.
|
|
3349
|
+
* @returns {JSX.Element} The rendered paragraph element.
|
|
3350
|
+
*/
|
|
3351
|
+
const Paragraph = ({ className, textColor, bgColor, children, ...props }) => {
|
|
3352
|
+
/**
|
|
3353
|
+
* Generates Bulma helper classes and separates out remaining props.
|
|
3354
|
+
*/
|
|
3355
|
+
const { bulmaHelperClasses, rest } = useBulmaClasses({
|
|
3356
|
+
color: textColor,
|
|
3357
|
+
backgroundColor: bgColor,
|
|
3358
|
+
...props,
|
|
3359
|
+
});
|
|
3360
|
+
const bulmaClasses = usePrefixedClassNames();
|
|
3361
|
+
const paragraphClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
|
|
3362
|
+
return (jsxRuntime.jsx("p", { className: paragraphClasses || undefined, ...rest, children: children }));
|
|
3363
|
+
};
|
|
3364
|
+
|
|
3365
|
+
const validTitleSizes = ['1', '2', '3', '4', '5', '6'];
|
|
3366
|
+
const validTitleElements = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p'];
|
|
3367
|
+
/**
|
|
3368
|
+
* The `Title` component renders a Bulma-styled title (heading), supporting sizes `1`-`6`, spacing, and rendering as any heading or paragraph element (`h1`-`h6`, `p`).
|
|
3369
|
+
*
|
|
3370
|
+
* @function
|
|
3371
|
+
* @param {TitleProps} props - Props for the Title component.
|
|
3372
|
+
* @returns {JSX.Element} The rendered title element.
|
|
3373
|
+
* @see {@link https://bulma.io/documentation/elements/title/ | Bulma Title documentation}
|
|
3374
|
+
*/
|
|
3375
|
+
const Title = ({ className, size, isSpaced, as = 'h1', hasSkeleton, textColor, bgColor, children, ...props }) => {
|
|
3376
|
+
/**
|
|
3377
|
+
* Generates Bulma helper classes and separates out remaining props.
|
|
3378
|
+
*/
|
|
3379
|
+
const { bulmaHelperClasses, rest } = useBulmaClasses({
|
|
3380
|
+
color: textColor,
|
|
3381
|
+
backgroundColor: bgColor,
|
|
3382
|
+
...props,
|
|
3383
|
+
});
|
|
3384
|
+
// Validate 'as' prop at runtime
|
|
3385
|
+
const element = validTitleElements.includes(as) ? as : 'h1';
|
|
3386
|
+
// Validate 'size' prop at runtime (numeric sizes normalize to strings)
|
|
3387
|
+
const normalized = size == null ? undefined : String(size);
|
|
3388
|
+
const validSize = normalized && validTitleSizes.includes(normalized)
|
|
3389
|
+
? normalized
|
|
3390
|
+
: undefined;
|
|
3391
|
+
const bulmaClasses = usePrefixedClassNames('title', {
|
|
3392
|
+
[`is-${validSize}`]: validSize,
|
|
3393
|
+
'is-spaced': isSpaced,
|
|
3394
|
+
'has-skeleton': hasSkeleton,
|
|
3395
|
+
});
|
|
3396
|
+
const titleClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
|
|
3397
|
+
// Determine the tag based on 'element' and 'validSize'
|
|
3398
|
+
const Tag = element === 'p' ? 'p' : validSize ? `h${validSize}` : element;
|
|
3399
|
+
return (jsxRuntime.jsx(Tag, { className: titleClasses, ...rest, children: children }));
|
|
3400
|
+
};
|
|
3401
|
+
|
|
3402
|
+
const validEmptyStateColors = [
|
|
3403
|
+
'primary',
|
|
3404
|
+
'link',
|
|
3405
|
+
'info',
|
|
3406
|
+
'success',
|
|
3407
|
+
'warning',
|
|
3408
|
+
'danger',
|
|
3409
|
+
];
|
|
3410
|
+
const validEmptyStateHeadings = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
|
|
3411
|
+
const validEmptyStateSizes = ['small', 'medium', 'large'];
|
|
3412
|
+
/**
|
|
3413
|
+
* The circle the picture sits in, hidden from assistive tech whatever is in it.
|
|
3414
|
+
*
|
|
3415
|
+
* @function
|
|
3416
|
+
* @param {EmptyStateIndicatorProps} props - Props for EmptyState.Indicator.
|
|
3417
|
+
* @returns {JSX.Element} The rendered indicator.
|
|
3418
|
+
*/
|
|
3419
|
+
const EmptyStateIndicator = ({ className, textColor, bgColor, children, ...props }) => {
|
|
3420
|
+
const { bulmaHelperClasses, rest } = useBulmaClasses({
|
|
3421
|
+
color: textColor,
|
|
3422
|
+
backgroundColor: bgColor,
|
|
3423
|
+
...props,
|
|
3424
|
+
});
|
|
3425
|
+
const indicatorClass = usePrefixedClassNames('empty-state-indicator');
|
|
3426
|
+
// Hidden after the rest of the props, so nothing passed in can expose the
|
|
3427
|
+
// picture: the title says what it means.
|
|
3428
|
+
return (jsxRuntime.jsx("div", { className: classNames(indicatorClass, bulmaHelperClasses, className), ...rest, "aria-hidden": "true", children: children }));
|
|
3429
|
+
};
|
|
3430
|
+
EmptyStateIndicator.displayName = 'EmptyState.Indicator';
|
|
3431
|
+
/**
|
|
3432
|
+
* The heading, a Bulma `Title` at the level `as` sets, `h2` by default, and at the EmptyState's size.
|
|
3433
|
+
*
|
|
3434
|
+
* @function
|
|
3435
|
+
* @param {EmptyStateTitleProps} props - Props for EmptyState.Title.
|
|
3436
|
+
* @returns {JSX.Element} The rendered heading.
|
|
3437
|
+
*/
|
|
3438
|
+
const EmptyStateTitle = ({ as = 'h2', className, ...props }) => {
|
|
3439
|
+
// `size` isn't in the type, but on `Title` it picks the tag, so it's kept
|
|
3440
|
+
// off even when it's passed anyway. The level is `as` alone.
|
|
3441
|
+
const { size: _size, ...rest } = props;
|
|
3442
|
+
const titleClass = usePrefixedClassNames('empty-state-title');
|
|
3443
|
+
const level = validEmptyStateHeadings.includes(as) ? as : 'h2';
|
|
3444
|
+
return (jsxRuntime.jsx(Title, { as: level, className: classNames(titleClass, className), ...rest }));
|
|
3445
|
+
};
|
|
3446
|
+
EmptyStateTitle.displayName = 'EmptyState.Title';
|
|
3447
|
+
/**
|
|
3448
|
+
* A sentence or two under the title, in the color of the text around it.
|
|
3449
|
+
*
|
|
3450
|
+
* @function
|
|
3451
|
+
* @param {EmptyStateDescriptionProps} props - Props for EmptyState.Description.
|
|
3452
|
+
* @returns {JSX.Element} The rendered description.
|
|
3453
|
+
*/
|
|
3454
|
+
const EmptyStateDescription = ({ className, ...props }) => {
|
|
3455
|
+
const descriptionClass = usePrefixedClassNames('empty-state-description');
|
|
3456
|
+
return (jsxRuntime.jsx(Paragraph, { className: classNames(descriptionClass, className), ...props }));
|
|
3457
|
+
};
|
|
3458
|
+
EmptyStateDescription.displayName = 'EmptyState.Description';
|
|
3459
|
+
/**
|
|
3460
|
+
* A row of buttons, centered under the text or at the start of the line beside the indicator.
|
|
3461
|
+
*
|
|
3462
|
+
* @function
|
|
3463
|
+
* @param {EmptyStateActionsProps} props - Props for EmptyState.Actions.
|
|
3464
|
+
* @returns {JSX.Element} The rendered row of buttons.
|
|
3465
|
+
*/
|
|
3466
|
+
const EmptyStateActions = ({ className, ...props }) => {
|
|
3467
|
+
const actionsClass = usePrefixedClassNames('empty-state-actions');
|
|
3468
|
+
return jsxRuntime.jsx(Buttons, { className: classNames(actionsClass, className), ...props });
|
|
3469
|
+
};
|
|
3470
|
+
EmptyStateActions.displayName = 'EmptyState.Actions';
|
|
3471
|
+
/** Whether a shorthand prop was given something to render. */
|
|
3472
|
+
const isGiven = (node) => node !== undefined &&
|
|
3473
|
+
node !== null &&
|
|
3474
|
+
typeof node !== 'boolean' &&
|
|
3475
|
+
node !== '';
|
|
3476
|
+
/**
|
|
3477
|
+
* The `EmptyState` component renders the block an app shows where content would be: an empty list, a search with no results, or a first-run screen.
|
|
3478
|
+
*
|
|
3479
|
+
* It's a column of a picture in a circle, a heading, a line of text and the
|
|
3480
|
+
* actions, with one layout whether it's written with the `icon`, `title` and
|
|
3481
|
+
* `description` props or with the parts. The children always render as
|
|
3482
|
+
* written, after the parts the props render.
|
|
3483
|
+
*
|
|
3484
|
+
* It has no role and no live region. It arrives already filled and goes away
|
|
3485
|
+
* when results come back, so it can't announce a search that matched
|
|
3486
|
+
* nothing: keep a `role="status"` region beside the search field for that,
|
|
3487
|
+
* in the page the whole time. While the data loads, show a `Skeleton` or a
|
|
3488
|
+
* `Loader` instead, so "No projects yet" doesn't flash first, and show a
|
|
3489
|
+
* load that failed in a `Message` or a `Notification`.
|
|
3490
|
+
*
|
|
3491
|
+
* @function
|
|
3492
|
+
* @param {EmptyStateProps} props - Props for the EmptyState component.
|
|
3493
|
+
* @param {React.Ref<HTMLDivElement>} ref - Forwarded ref to the root `<div>`.
|
|
3494
|
+
* @returns {JSX.Element} The rendered empty state.
|
|
3495
|
+
*
|
|
3496
|
+
* @example
|
|
3497
|
+
* <EmptyState
|
|
3498
|
+
* icon="folder-open"
|
|
3499
|
+
* title="No projects yet"
|
|
3500
|
+
* description="Projects you create or join show up here."
|
|
3501
|
+
* >
|
|
3502
|
+
* <Button color="primary">New project</Button>
|
|
3503
|
+
* </EmptyState>
|
|
3504
|
+
*/
|
|
3505
|
+
const EmptyStateRoot = React.forwardRef(({ className, icon, title, titleAs = 'h2', description, children, color, variant = 'light', size = 'normal', align = 'center', textColor, bgColor, ...props }, ref) => {
|
|
3506
|
+
const { bulmaHelperClasses, rest } = useBulmaClasses({
|
|
3507
|
+
color: textColor,
|
|
3508
|
+
backgroundColor: bgColor,
|
|
3509
|
+
...props,
|
|
3510
|
+
});
|
|
3511
|
+
const rootClass = usePrefixedClassNames('empty-state', {
|
|
3512
|
+
[`is-${size}`]: validEmptyStateSizes.includes(size),
|
|
3513
|
+
'is-left': align === 'left',
|
|
3514
|
+
[`is-${color}`]: !!color && validEmptyStateColors.includes(color),
|
|
3515
|
+
'is-filled': variant === 'filled',
|
|
3516
|
+
});
|
|
3517
|
+
// The props' parts first, each only when it's given, then the children
|
|
3518
|
+
// exactly as they were written: nothing here looks at them.
|
|
3519
|
+
return (jsxRuntime.jsxs("div", { ref: ref, className: classNames(rootClass, bulmaHelperClasses, className), ...rest, children: [isGiven(icon) && (jsxRuntime.jsx(EmptyStateIndicator, { children: typeof icon === 'string' ? jsxRuntime.jsx(Icon, { name: icon }) : icon })), isGiven(title) && (jsxRuntime.jsx(EmptyStateTitle, { as: titleAs, children: title })), isGiven(description) && (jsxRuntime.jsx(EmptyStateDescription, { children: description })), children] }));
|
|
3520
|
+
});
|
|
3521
|
+
const EmptyState = withSubComponents(EmptyStateRoot, {
|
|
3522
|
+
Indicator: EmptyStateIndicator,
|
|
3523
|
+
Title: EmptyStateTitle,
|
|
3524
|
+
Description: EmptyStateDescription,
|
|
3525
|
+
Actions: EmptyStateActions,
|
|
3526
|
+
}, 'EmptyState');
|
|
3527
|
+
|
|
3528
|
+
// Context to track MenuList nesting level
|
|
3529
|
+
const MenuListLevelContext = React.createContext(0);
|
|
3530
|
+
/**
|
|
3531
|
+
* The `Menu` component provides Bulma's vertical navigation menu: a simple, accessible sidebar or section menu for your Bulma React UI.
|
|
3532
|
+
*
|
|
3533
|
+
* @function
|
|
3534
|
+
* @param {MenuProps} props - Props for the Menu component.
|
|
3535
|
+
* @returns {JSX.Element} The rendered menu.
|
|
3536
|
+
* @see {@link https://bulma.io/documentation/components/menu/ | Bulma Menu documentation}
|
|
3537
|
+
*/
|
|
3538
|
+
const MenuComponent = ({ className, children, ...props }) => {
|
|
3539
|
+
const { bulmaHelperClasses, rest } = useBulmaClasses(props);
|
|
3540
|
+
// Generate Bulma classes with prefix
|
|
3541
|
+
const bulmaClasses = usePrefixedClassNames('menu');
|
|
3542
|
+
return (jsxRuntime.jsx("aside", { className: classNames(bulmaClasses, bulmaHelperClasses, className), ...rest, children: children }));
|
|
3543
|
+
};
|
|
3544
|
+
/**
|
|
3545
|
+
* Bulma Menu label component.
|
|
3546
|
+
*
|
|
3547
|
+
* @function
|
|
3548
|
+
* @param {MenuLabelProps} props - Props for the MenuLabel component.
|
|
3549
|
+
* @returns {JSX.Element} The rendered menu label.
|
|
3550
|
+
*/
|
|
3551
|
+
const MenuLabel = ({ className, children, ...props }) => {
|
|
3552
|
+
const { bulmaHelperClasses, rest } = useBulmaClasses(props);
|
|
3553
|
+
return (jsxRuntime.jsx("p", { className: classNames(usePrefixedClassNames('menu-label'), className, bulmaHelperClasses), ...rest, children: children }));
|
|
3554
|
+
};
|
|
3555
|
+
/**
|
|
3556
|
+
* MenuList applies `menu-list` class only at the top level (not for nested lists).
|
|
3557
|
+
*
|
|
3558
|
+
* @function
|
|
3559
|
+
* @param {MenuListProps} props - Props for the MenuList component.
|
|
3560
|
+
* @returns {JSX.Element} The rendered menu list.
|
|
3561
|
+
*/
|
|
3562
|
+
const MenuList = ({ className, children, ...props }) => {
|
|
3563
|
+
const level = React.useContext(MenuListLevelContext);
|
|
3564
|
+
const { bulmaHelperClasses, rest } = useBulmaClasses(props);
|
|
3565
|
+
const ulClass = classNames(className, bulmaHelperClasses, {
|
|
3566
|
+
[usePrefixedClassNames('menu-list')]: level === 0,
|
|
3567
|
+
});
|
|
3568
|
+
// Increment level for nested MenuLists
|
|
3569
|
+
return (jsxRuntime.jsx(MenuListLevelContext.Provider, { value: level + 1, children: jsxRuntime.jsx("ul", { className: ulClass || undefined, ...rest, children: children }) }));
|
|
3570
|
+
};
|
|
3571
|
+
/**
|
|
3572
|
+
* `href` alone — annotated so a typo is a compile error rather than a silent
|
|
3573
|
+
* no-op, the way both siblings' sets are.
|
|
3574
|
+
*/
|
|
3575
|
+
const STRIP_FROM_NON_LINK = { href: true };
|
|
3576
|
+
/**
|
|
3577
|
+
* MenuItem supports `as` prop for custom link components, e.g., react-router-dom Link.
|
|
3578
|
+
*
|
|
3579
|
+
* @function
|
|
3580
|
+
* @param {MenuItemProps} props - Props for the MenuItem component.
|
|
3581
|
+
* @param {React.Ref} ref - Forwarded ref to the inner element `as` renders.
|
|
3582
|
+
* @returns {JSX.Element} The rendered menu item.
|
|
3583
|
+
*/
|
|
3584
|
+
const MenuItem = React.forwardRef(function MenuItem(itemProps, ref) {
|
|
3585
|
+
const { className, children, active, as: Component = 'a', 'data-testid': testId, style, id, title, role, tabIndex, ...rest } = itemProps;
|
|
3586
|
+
const { bulmaHelperClasses, rest: forwarded } = useBulmaClasses(rest);
|
|
3587
|
+
// `href` reaches an anchor or a custom component (which owns its own prop
|
|
3588
|
+
// contract), but not another intrinsic tag — `<span href>` is invalid HTML.
|
|
3589
|
+
// The same rule Avatar applies through `isLinkLike` and Button through its
|
|
3590
|
+
// intrinsic-only strip. Before #641 `href` was an own prop re-applied only
|
|
3591
|
+
// for `as="a"`; deriving it from `as` must not quietly widen that.
|
|
3592
|
+
const isLinkLike = Component === 'a' ||
|
|
3593
|
+
typeof Component !== 'string' ||
|
|
3594
|
+
isCustomElement(Component);
|
|
3595
|
+
// `href` ALONE. `Dropdown.Item` strips every anchor-only attribute except
|
|
3596
|
+
// `type`; `Level.Item` strips all of them, plus `rel`.
|
|
3597
|
+
//
|
|
3598
|
+
// Not because nothing else can arrive: `as` here is an open `React.ElementType`,
|
|
3599
|
+
// and a non-fresh spread skips excess-property checking, so a typed caller
|
|
3600
|
+
// CAN deliver `target` or `download` and they are forwarded. The set stays at
|
|
3601
|
+
// `href` because widening it would be wrong for an open `as` — `referrerPolicy`
|
|
3602
|
+
// on `as="img"`, `target` on `as="form"` and `media` on `as="source"` are all
|
|
3603
|
+
// legal, and a flat strip would delete them. Getting that right needs a
|
|
3604
|
+
// per-attribute-per-element rule, which `bestax-migrate` already models in
|
|
3605
|
+
// `LINK_ATTR_ELEMENTS`.
|
|
3606
|
+
const linkProps = isLinkLike
|
|
3607
|
+
? forwarded
|
|
3608
|
+
: omitAttrs(forwarded, STRIP_FROM_NON_LINK);
|
|
3609
|
+
const itemClass = classNames({ [usePrefixedClassNames('is-active')]: active }, bulmaHelperClasses);
|
|
3610
|
+
// Split children into label and nested MenuList(s)
|
|
3611
|
+
const labelChildren = [];
|
|
3612
|
+
const nestedMenuLists = [];
|
|
3613
|
+
React.Children.forEach(children, child => {
|
|
3614
|
+
if (React.isValidElement(child) && child.type === MenuList) {
|
|
3615
|
+
nestedMenuLists.push(child);
|
|
3616
|
+
}
|
|
3617
|
+
else {
|
|
3618
|
+
labelChildren.push(child);
|
|
3619
|
+
}
|
|
3620
|
+
});
|
|
3621
|
+
return (jsxRuntime.jsxs("li", { className: className, "data-testid": testId, style: style, id: id, title: title, role: role, tabIndex: tabIndex, children: [jsxRuntime.jsx(Component, { ref: ref, className: itemClass || undefined, ...linkProps, ...(Component === 'button'
|
|
3622
|
+
? { type: buttonType(linkProps.type) }
|
|
3623
|
+
: {}), children: labelChildren }), nestedMenuLists] }));
|
|
3624
|
+
});
|
|
3625
|
+
MenuItem.displayName = 'MenuItem';
|
|
3626
|
+
// Attach static subcomponents
|
|
3627
|
+
const Menu = withSubComponents(MenuComponent, {
|
|
3628
|
+
Label: MenuLabel,
|
|
3629
|
+
List: MenuList,
|
|
3630
|
+
Item: MenuItem,
|
|
3631
|
+
}, 'Menu');
|
|
3632
|
+
|
|
3633
|
+
const validMessageSizes = ['small', 'medium', 'large'];
|
|
3634
|
+
/**
|
|
3635
|
+
* The `Message` component provides Bulma's flexible notice/message box for your Bulma React UI.
|
|
3636
|
+
*
|
|
3637
|
+
* @function
|
|
3638
|
+
* @param {MessageProps} props - Props for the Message component.
|
|
3639
|
+
* @returns {JSX.Element} The rendered message.
|
|
3640
|
+
* @see {@link https://bulma.io/documentation/components/message/ | Bulma Message documentation}
|
|
3641
|
+
*/
|
|
3642
|
+
const MessageComponent = ({ className, title, textColor, color, bgColor, size, onClose, children, ...props }) => {
|
|
3643
|
+
const { classPrefix } = useConfig();
|
|
3644
|
+
const { bulmaHelperClasses, rest } = useBulmaClasses({
|
|
3645
|
+
color: textColor,
|
|
3646
|
+
backgroundColor: bgColor,
|
|
3647
|
+
...props,
|
|
3648
|
+
});
|
|
3649
|
+
// Generate Bulma classes with prefix
|
|
3650
|
+
const bulmaClasses = usePrefixedClassNames('message', {
|
|
3651
|
+
[`is-${color}`]: color,
|
|
3652
|
+
[`is-${size}`]: size && validMessageSizes.includes(size),
|
|
3653
|
+
});
|
|
3654
|
+
const deleteClass = usePrefixedClassNames('delete');
|
|
3655
|
+
const messageClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
|
|
3656
|
+
return (jsxRuntime.jsxs("article", { className: messageClasses, ...rest, "data-testid": "message", children: [(title || onClose) && (jsxRuntime.jsxs("div", { className: prefixedClassNames(classPrefix, 'message-header'), children: [title && jsxRuntime.jsx("span", { children: title }), onClose && (jsxRuntime.jsx("button", { className: deleteClass, "aria-label": "delete", onClick: onClose, type: "button", "data-testid": "message-close" }))] })), children && (jsxRuntime.jsx("div", { className: prefixedClassNames(classPrefix, 'message-body'), "data-testid": "message-body", children: children }))] }));
|
|
3657
|
+
};
|
|
3658
|
+
/**
|
|
3659
|
+
* Message header compound component. Renders a `.message-header` element.
|
|
3660
|
+
*
|
|
3661
|
+
* @function
|
|
3662
|
+
* @param {MessageHeaderProps} props - Props for the MessageHeader component.
|
|
3663
|
+
* @returns {JSX.Element} The rendered message header.
|
|
3664
|
+
*/
|
|
3665
|
+
const MessageHeader = ({ className, children, ...props }) => (jsxRuntime.jsx("div", { className: classNames(usePrefixedClassNames('message-header'), className), ...props, children: children }));
|
|
3666
|
+
/**
|
|
3667
|
+
* Message body compound component. Renders a `.message-body` element.
|
|
3668
|
+
*
|
|
3669
|
+
* @function
|
|
3670
|
+
* @param {MessageBodyProps} props - Props for the MessageBody component.
|
|
3671
|
+
* @returns {JSX.Element} The rendered message body.
|
|
3672
|
+
*/
|
|
3673
|
+
const MessageBody = ({ className, children, ...props }) => (jsxRuntime.jsx("div", { className: classNames(usePrefixedClassNames('message-body'), className), ...props, children: children }));
|
|
3674
|
+
// Attach compound components
|
|
3675
|
+
const Message = withSubComponents(MessageComponent, {
|
|
3676
|
+
Header: MessageHeader,
|
|
3677
|
+
Body: MessageBody,
|
|
3678
|
+
}, 'Message');
|
|
3679
|
+
|
|
3680
|
+
// Ref-counted across every caller (e.g. a Dialog rendering its own Modal)
|
|
3681
|
+
// so overlapping/nested locks don't fight over which one restores `overflow`.
|
|
3682
|
+
// Every overlay that locks body scroll — Modal, Dialog, Sidebar, Loading —
|
|
3683
|
+
// goes through here; a component setting `document.body.style.overflow`
|
|
3684
|
+
// itself would clear a lock another overlay still needs.
|
|
3685
|
+
let lockCount = 0;
|
|
3686
|
+
let originalOverflow = '';
|
|
3687
|
+
/**
|
|
3688
|
+
* Locks (and ref-counted-ly unlocks) `document.body` scrolling while `active`
|
|
3689
|
+
* is true. Safe to call from multiple components at once — the body scroll
|
|
3690
|
+
* is only restored once every active caller has released its lock.
|
|
3691
|
+
*
|
|
3692
|
+
* @function useScrollLock
|
|
3693
|
+
* @param active - Whether this caller wants the body scroll locked.
|
|
3694
|
+
*/
|
|
3695
|
+
function useScrollLock(active) {
|
|
3696
|
+
React.useEffect(() => {
|
|
3697
|
+
if (!active)
|
|
3698
|
+
return undefined;
|
|
3699
|
+
lockCount++;
|
|
3700
|
+
if (lockCount === 1) {
|
|
3701
|
+
originalOverflow = document.body.style.overflow;
|
|
3702
|
+
document.body.style.overflow = 'hidden';
|
|
3703
|
+
}
|
|
3704
|
+
return () => {
|
|
3705
|
+
lockCount--;
|
|
3706
|
+
if (lockCount === 0) {
|
|
3707
|
+
document.body.style.overflow = originalOverflow;
|
|
3708
|
+
}
|
|
3709
|
+
};
|
|
3710
|
+
}, [active]);
|
|
3711
|
+
}
|
|
3712
|
+
|
|
3713
|
+
// "Are we past hydration?" as a store that never changes: React reads the
|
|
3714
|
+
// server snapshot both while server-rendering and while hydrating, and the
|
|
3715
|
+
// client snapshot from the first post-hydration render onward.
|
|
3716
|
+
const subscribeToNothing = () => () => { };
|
|
3717
|
+
const getClientSnapshot = () => true;
|
|
3718
|
+
const getServerSnapshot = () => false;
|
|
3719
|
+
/**
|
|
3720
|
+
* Reports whether the component has passed hydration.
|
|
3721
|
+
*
|
|
3722
|
+
* Returns `false` during server rendering and during the hydrating client
|
|
3723
|
+
* render, then `true` from the commit that follows. Use it to defer
|
|
3724
|
+
* DOM-only behaviour (such as portalling) past hydration so the first client
|
|
3725
|
+
* render matches the server markup instead of tripping hydration recovery.
|
|
3726
|
+
*
|
|
3727
|
+
* Only a component that hydrates ever sees `false` on the client. One that
|
|
3728
|
+
* mounts later (after a route change, or in an app with no server rendering)
|
|
3729
|
+
* gets `true` on its first render, so it pays for no extra render. It
|
|
3730
|
+
* subscribes to nothing, so no later render flips it back.
|
|
3731
|
+
*
|
|
3732
|
+
* @function useIsHydrated
|
|
3733
|
+
* @returns `true` once the hydrating render has completed, otherwise `false`.
|
|
3734
|
+
*/
|
|
3735
|
+
function useIsHydrated() {
|
|
3736
|
+
return React.useSyncExternalStore(subscribeToNothing, getClientSnapshot, getServerSnapshot);
|
|
3737
|
+
}
|
|
3738
|
+
|
|
3739
|
+
/**
|
|
3740
|
+
* Resolves a portal target: an `HTMLElement` is used directly, a non-empty
|
|
3741
|
+
* `string` is treated as a `document.querySelector` selector (falling back to
|
|
3742
|
+
* `document.body` when it matches nothing), and any falsy value — `undefined`
|
|
3743
|
+
* or the empty string — resolves to `document.body`.
|
|
3744
|
+
*
|
|
3745
|
+
* The empty string is deliberately treated as "no target" rather than passed
|
|
3746
|
+
* through: `document.querySelector('')` throws a `SyntaxError`, and callers
|
|
3747
|
+
* building a selector from state can easily hand us `''`.
|
|
3748
|
+
*
|
|
3749
|
+
* @function resolvePortalContainer
|
|
3750
|
+
* @param container - The requested portal target, if any.
|
|
3751
|
+
* @returns The resolved DOM node to portal into.
|
|
3752
|
+
*/
|
|
3753
|
+
function resolvePortalContainer(container) {
|
|
3754
|
+
if (!container) {
|
|
3755
|
+
return document.body;
|
|
3756
|
+
}
|
|
3757
|
+
if (typeof container === 'string') {
|
|
3758
|
+
return (document.querySelector(container) ?? document.body);
|
|
3075
3759
|
}
|
|
3076
3760
|
return container;
|
|
3077
3761
|
}
|
|
@@ -3819,428 +4503,180 @@ function whole(count, fallback) {
|
|
|
3819
4503
|
}
|
|
3820
4504
|
function range(from, to) {
|
|
3821
4505
|
return Array.from({ length: Math.max(to - from + 1, 0) }, (_, i) => from + i);
|
|
3822
|
-
}
|
|
3823
|
-
/**
|
|
3824
|
-
* The pages to show for `current` of `total`: the first and last
|
|
3825
|
-
* `boundaries`, `siblings` on each side of the current one, and a gap for
|
|
3826
|
-
* each run left out. A gap of a single page shows that page instead, and the
|
|
3827
|
-
* window around the current page slides inward near either end, so the row
|
|
3828
|
-
* keeps its length as the page moves.
|
|
3829
|
-
*/
|
|
3830
|
-
function pageItems(total, current, siblings, boundaries) {
|
|
3831
|
-
const windowSize = siblings * 2 + 1;
|
|
3832
|
-
// Every page fits in the room the boundary pages, the window and two gaps take.
|
|
3833
|
-
if (total <= boundaries * 2 + windowSize + 2)
|
|
3834
|
-
return range(1, total);
|
|
3835
|
-
const lowest = boundaries + 2;
|
|
3836
|
-
const highest = total - boundaries - 1;
|
|
3837
|
-
const start = Math.min(Math.max(current - siblings, lowest), highest - windowSize + 1);
|
|
3838
|
-
const end = start + windowSize - 1;
|
|
3839
|
-
const gap = (from, to, name) => to === from ? [from] : [name];
|
|
3840
|
-
return [
|
|
3841
|
-
...range(1, boundaries),
|
|
3842
|
-
...gap(boundaries + 1, start - 1, 'start-gap'),
|
|
3843
|
-
...range(start, end),
|
|
3844
|
-
...gap(end + 1, total - boundaries, 'end-gap'),
|
|
3845
|
-
...range(total - boundaries + 1, total),
|
|
3846
|
-
];
|
|
3847
|
-
}
|
|
3848
|
-
/**
|
|
3849
|
-
* "Previous" navigation button.
|
|
3850
|
-
*
|
|
3851
|
-
* @function
|
|
3852
|
-
* @param {PaginationPreviousNextProps} props - Props for the PaginationPrevious component.
|
|
3853
|
-
* @returns {JSX.Element} The rendered previous button.
|
|
3854
|
-
*/
|
|
3855
|
-
const PaginationPrevious = ({ className, disabled, children, ...props }) => (jsxRuntime.jsx("a", { className: classNames(usePrefixedClassNames('pagination-previous', {
|
|
3856
|
-
'is-disabled': disabled,
|
|
3857
|
-
}), className), "aria-disabled": disabled, tabIndex: disabled ? -1 : 0, ...props, onClick: disabled
|
|
3858
|
-
? e => {
|
|
3859
|
-
e.preventDefault();
|
|
3860
|
-
e.stopPropagation();
|
|
3861
|
-
}
|
|
3862
|
-
: props.onClick, children: children }));
|
|
3863
|
-
/**
|
|
3864
|
-
* "Next" navigation button.
|
|
3865
|
-
*
|
|
3866
|
-
* @function
|
|
3867
|
-
* @param {PaginationPreviousNextProps} props - Props for the PaginationNext component.
|
|
3868
|
-
* @returns {JSX.Element} The rendered next button.
|
|
3869
|
-
*/
|
|
3870
|
-
const PaginationNext = ({ className, disabled, children, ...props }) => (jsxRuntime.jsx("a", { className: classNames(usePrefixedClassNames('pagination-next', { 'is-disabled': disabled }), className), "aria-disabled": disabled, tabIndex: disabled ? -1 : 0, ...props, onClick: disabled
|
|
3871
|
-
? e => {
|
|
3872
|
-
e.preventDefault();
|
|
3873
|
-
e.stopPropagation();
|
|
3874
|
-
}
|
|
3875
|
-
: props.onClick, children: children }));
|
|
3876
|
-
/**
|
|
3877
|
-
* The `Pagination` component provides a flexible, composable Bulma pagination navigation for your Bulma React UI.
|
|
3878
|
-
*
|
|
3879
|
-
* @function
|
|
3880
|
-
* @param {PaginationProps} props - Props for the Pagination component.
|
|
3881
|
-
* @returns {JSX.Element} The rendered pagination.
|
|
3882
|
-
* @see {@link https://bulma.io/documentation/components/pagination/ | Bulma Pagination documentation}
|
|
3883
|
-
*/
|
|
3884
|
-
const PaginationComponent = ({ color, textColor, bgColor, size, align, rounded, total, current, onPageChange, siblingCount = 1, boundaryCount = 1, previousLabel = 'Previous', nextLabel = 'Next', disabled, getPageHref, getPageLabel, className, children, ...props }) => {
|
|
3885
|
-
const [kept, setKept] = React.useState(1);
|
|
3886
|
-
warnDeprecatedColorProp('Pagination', color, 'Use the textColor / bgColor helper props instead.');
|
|
3887
|
-
const { bulmaHelperClasses, rest } = useBulmaClasses({
|
|
3888
|
-
color: textColor,
|
|
3889
|
-
backgroundColor: bgColor,
|
|
3890
|
-
...props,
|
|
3891
|
-
});
|
|
3892
|
-
// Generate Bulma classes with prefix
|
|
3893
|
-
const bulmaClasses = usePrefixedClassNames('pagination', {
|
|
3894
|
-
[`is-${color}`]: color,
|
|
3895
|
-
[`is-${size}`]: size,
|
|
3896
|
-
[`is-${align}`]: align,
|
|
3897
|
-
'is-rounded': rounded,
|
|
3898
|
-
});
|
|
3899
|
-
const paginationClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
|
|
3900
|
-
// A child that renders nothing (`{show && …}` when false) is no children.
|
|
3901
|
-
const fromTotal = total !== undefined && React.Children.toArray(children).length === 0;
|
|
3902
|
-
let content = children;
|
|
3903
|
-
if (fromTotal) {
|
|
3904
|
-
const pages = Number.isFinite(total) ? Math.max(Math.floor(total), 0) : 0;
|
|
3905
|
-
// A current page that's no number reads as the first.
|
|
3906
|
-
const page = Math.min(Math.max(whole(current ?? kept, 1), 1), Math.max(pages, 1));
|
|
3907
|
-
const choose = (next) => {
|
|
3908
|
-
if (next === page)
|
|
3909
|
-
return;
|
|
3910
|
-
if (current === undefined)
|
|
3911
|
-
setKept(next);
|
|
3912
|
-
onPageChange?.(next);
|
|
3913
|
-
};
|
|
3914
|
-
/**
|
|
3915
|
-
* What a link to `target` is given, beside what it renders. With no
|
|
3916
|
-
* `getPageHref` it's a button; a link that's `off` goes nowhere.
|
|
3917
|
-
*/
|
|
3918
|
-
const linkTo = (target, off) => ({
|
|
3919
|
-
href: off || !getPageHref ? undefined : getPageHref(target),
|
|
3920
|
-
role: getPageHref ? undefined : 'button',
|
|
3921
|
-
// A link that's off is disabled too, and the parts drop its clicks.
|
|
3922
|
-
onClick: () => choose(target),
|
|
3923
|
-
onKeyDown: getPageHref
|
|
3924
|
-
? undefined
|
|
3925
|
-
: (e) => {
|
|
3926
|
-
if (off || (e.key !== 'Enter' && e.key !== ' '))
|
|
3927
|
-
return;
|
|
3928
|
-
e.preventDefault();
|
|
3929
|
-
choose(target);
|
|
3930
|
-
},
|
|
3931
|
-
});
|
|
3932
|
-
const atStart = disabled || page <= 1;
|
|
3933
|
-
const atEnd = disabled || page >= pages;
|
|
3934
|
-
content = (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(PaginationPrevious, { disabled: atStart, ...linkTo(page - 1, atStart), children: previousLabel }), jsxRuntime.jsx(PaginationNext, { disabled: atEnd, ...linkTo(page + 1, atEnd), children: nextLabel }), jsxRuntime.jsx(PaginationList, { children: pageItems(pages, page, whole(siblingCount, 1), whole(boundaryCount, 1)).map(item => typeof item === 'number' ? (jsxRuntime.jsx(PaginationLink, { "aria-label": getPageLabel ? getPageLabel(item) : `Page ${item}`, active: item === page, disabled: disabled, ...linkTo(item, Boolean(disabled)), children: item }, item)) : (jsxRuntime.jsx(PaginationEllipsis, {}, item))) })] }));
|
|
3935
|
-
}
|
|
3936
|
-
return (jsxRuntime.jsx("nav", { className: paginationClasses, role: "navigation", "aria-label": "pagination", ...rest, children: content }));
|
|
3937
|
-
};
|
|
3938
|
-
/**
|
|
3939
|
-
* Container for page links and ellipsis.
|
|
3940
|
-
*
|
|
3941
|
-
* @function
|
|
3942
|
-
* @param {PaginationListProps} props - Props for the PaginationList component.
|
|
3943
|
-
* @returns {JSX.Element} The rendered pagination list.
|
|
3944
|
-
*/
|
|
3945
|
-
const PaginationList = ({ className, textColor, bgColor, children, ...props }) => {
|
|
3946
|
-
const { bulmaHelperClasses, rest } = useBulmaClasses({
|
|
3947
|
-
color: textColor,
|
|
3948
|
-
backgroundColor: bgColor,
|
|
3949
|
-
...props,
|
|
3950
|
-
});
|
|
3951
|
-
return (jsxRuntime.jsx("ul", { className: classNames(usePrefixedClassNames('pagination-list'), bulmaHelperClasses, className), ...rest, children: children }));
|
|
3952
|
-
};
|
|
3953
|
-
/**
|
|
3954
|
-
* Page number or navigation link.
|
|
3955
|
-
*
|
|
3956
|
-
* @function
|
|
3957
|
-
* @param {PaginationLinkProps} props - Props for the PaginationLink component.
|
|
3958
|
-
* @returns {JSX.Element} The rendered pagination link.
|
|
3959
|
-
*/
|
|
3960
|
-
const PaginationLink = ({ className, textColor, bgColor, active, disabled, onClick, children, ...props }) => {
|
|
3961
|
-
const { bulmaHelperClasses, rest } = useBulmaClasses({
|
|
3962
|
-
color: textColor,
|
|
3963
|
-
backgroundColor: bgColor,
|
|
3964
|
-
...props,
|
|
3965
|
-
});
|
|
3966
|
-
const handleClick = (e) => {
|
|
3967
|
-
if (disabled) {
|
|
3968
|
-
e.preventDefault();
|
|
3969
|
-
e.stopPropagation();
|
|
3970
|
-
return;
|
|
3971
|
-
}
|
|
3972
|
-
if (onClick) {
|
|
3973
|
-
onClick(e);
|
|
3974
|
-
}
|
|
3975
|
-
};
|
|
3976
|
-
return (jsxRuntime.jsx("li", { children: jsxRuntime.jsx("a", { className: classNames(usePrefixedClassNames('pagination-link', {
|
|
3977
|
-
'is-current': active,
|
|
3978
|
-
'is-disabled': disabled,
|
|
3979
|
-
}), bulmaHelperClasses, className), "aria-current": active ? 'page' : undefined, "aria-disabled": disabled, tabIndex: disabled ? -1 : 0, onClick: handleClick, ...rest, children: children }) }));
|
|
3980
|
-
};
|
|
3981
|
-
/**
|
|
3982
|
-
* Ellipsis separator.
|
|
3983
|
-
*
|
|
3984
|
-
* @function
|
|
3985
|
-
* @param {React.LiHTMLAttributes<HTMLLIElement>} props - Standard li props.
|
|
3986
|
-
* @returns {JSX.Element} The rendered ellipsis.
|
|
3987
|
-
*/
|
|
3988
|
-
const PaginationEllipsis = props => (jsxRuntime.jsx("li", { children: jsxRuntime.jsx("span", { className: usePrefixedClassNames('pagination-ellipsis'), ...props, children: "\u2026" }) }));
|
|
3989
|
-
const Pagination = withSubComponents(PaginationComponent, {
|
|
3990
|
-
Link: PaginationLink,
|
|
3991
|
-
List: PaginationList,
|
|
3992
|
-
Ellipsis: PaginationEllipsis,
|
|
3993
|
-
Previous: PaginationPrevious,
|
|
3994
|
-
Next: PaginationNext,
|
|
3995
|
-
}, 'Pagination');
|
|
3996
|
-
|
|
3997
|
-
/**
|
|
3998
|
-
* Strips a redundant leading library prefix from an icon name (e.g. `fa-check` -> `check`
|
|
3999
|
-
* for the `fa` library, `mdi-account` -> `account` for `mdi`), so `name` behaves the same
|
|
4000
|
-
* whether or not the caller includes the prefix. Font Awesome and Material Design Icons docs
|
|
4001
|
-
* conventionally refer to icons with their class prefix (`fa-check`), which otherwise gets
|
|
4002
|
-
* doubled by `getIconClasses` (producing `fa-fa-check`, matching no glyph).
|
|
4003
|
-
*
|
|
4004
|
-
* @param {string} name - The icon name, possibly including a redundant library prefix.
|
|
4005
|
-
* @param {IconLibrary} library - The icon library the name will be rendered with.
|
|
4006
|
-
* @returns {string} The icon name with any redundant leading library prefix removed.
|
|
4007
|
-
*/
|
|
4008
|
-
function stripRedundantLibraryPrefix(name, library) {
|
|
4009
|
-
if (!name) {
|
|
4010
|
-
return name;
|
|
4011
|
-
}
|
|
4012
|
-
if (library === 'fa' && name.startsWith('fa-')) {
|
|
4013
|
-
return name.substring(3);
|
|
4014
|
-
}
|
|
4015
|
-
if (library === 'mdi' && name.startsWith('mdi-')) {
|
|
4016
|
-
return name.substring(4);
|
|
4017
|
-
}
|
|
4018
|
-
return name;
|
|
4019
|
-
}
|
|
4020
|
-
/**
|
|
4021
|
-
* Gets the correct classes for the icon element based on the library and features.
|
|
4022
|
-
*
|
|
4023
|
-
* @param {IconLibrary} library - The icon library.
|
|
4024
|
-
* @param {string} name - The icon name.
|
|
4025
|
-
* @param {string} [variant] - Icon style variant (e.g., 'solid', 'outlined', 'rounded').
|
|
4026
|
-
* @param {string | string[]} [features] - Additional library-specific modifiers.
|
|
4027
|
-
* @returns {string} The combined class string for the icon.
|
|
4028
|
-
*/
|
|
4029
|
-
function getIconClasses(library, name, variant, features) {
|
|
4030
|
-
let baseClass;
|
|
4031
|
-
let iconClass;
|
|
4032
|
-
const featureList = Array.isArray(features)
|
|
4033
|
-
? features
|
|
4034
|
-
: features
|
|
4035
|
-
? [features]
|
|
4036
|
-
: [];
|
|
4037
|
-
switch (library) {
|
|
4038
|
-
case 'fa': {
|
|
4039
|
-
// Font Awesome: use variant as style ('solid' -> 'fas', 'regular' -> 'far', etc.)
|
|
4040
|
-
const styleMap = {
|
|
4041
|
-
solid: 'fas',
|
|
4042
|
-
regular: 'far',
|
|
4043
|
-
brands: 'fab',
|
|
4044
|
-
light: 'fal',
|
|
4045
|
-
duotone: 'fad',
|
|
4046
|
-
thin: 'fat',
|
|
4047
|
-
};
|
|
4048
|
-
const faStyle = variant ? styleMap[variant] || variant : 'fas';
|
|
4049
|
-
baseClass = faStyle;
|
|
4050
|
-
iconClass = `fa-${name}`;
|
|
4051
|
-
return [baseClass, iconClass, ...featureList].join(' ');
|
|
4052
|
-
}
|
|
4053
|
-
case 'mdi':
|
|
4054
|
-
// Material Design Icons: no variants, just features
|
|
4055
|
-
baseClass = 'mdi';
|
|
4056
|
-
iconClass = `mdi-${name}`;
|
|
4057
|
-
return [baseClass, iconClass, ...featureList].join(' ');
|
|
4058
|
-
case 'material-icons': {
|
|
4059
|
-
// Google Material Icons: map variants to full class names
|
|
4060
|
-
const styleVariants = {
|
|
4061
|
-
filled: 'material-icons',
|
|
4062
|
-
outlined: 'material-icons-outlined',
|
|
4063
|
-
round: 'material-icons-round',
|
|
4064
|
-
sharp: 'material-icons-sharp',
|
|
4065
|
-
};
|
|
4066
|
-
baseClass = variant
|
|
4067
|
-
? styleVariants[variant] || `material-icons-${variant}`
|
|
4068
|
-
: 'material-icons';
|
|
4069
|
-
return [baseClass, ...featureList].join(' ');
|
|
4506
|
+
}
|
|
4507
|
+
/**
|
|
4508
|
+
* The pages to show for `current` of `total`: the first and last
|
|
4509
|
+
* `boundaries`, `siblings` on each side of the current one, and a gap for
|
|
4510
|
+
* each run left out. A gap of a single page shows that page instead, and the
|
|
4511
|
+
* window around the current page slides inward near either end, so the row
|
|
4512
|
+
* keeps its length as the page moves.
|
|
4513
|
+
*/
|
|
4514
|
+
function pageItems(total, current, siblings, boundaries) {
|
|
4515
|
+
const windowSize = siblings * 2 + 1;
|
|
4516
|
+
// Every page fits in the room the boundary pages, the window and two gaps take.
|
|
4517
|
+
if (total <= boundaries * 2 + windowSize + 2)
|
|
4518
|
+
return range(1, total);
|
|
4519
|
+
const lowest = boundaries + 2;
|
|
4520
|
+
const highest = total - boundaries - 1;
|
|
4521
|
+
const start = Math.min(Math.max(current - siblings, lowest), highest - windowSize + 1);
|
|
4522
|
+
const end = start + windowSize - 1;
|
|
4523
|
+
const gap = (from, to, name) => to === from ? [from] : [name];
|
|
4524
|
+
return [
|
|
4525
|
+
...range(1, boundaries),
|
|
4526
|
+
...gap(boundaries + 1, start - 1, 'start-gap'),
|
|
4527
|
+
...range(start, end),
|
|
4528
|
+
...gap(end + 1, total - boundaries, 'end-gap'),
|
|
4529
|
+
...range(total - boundaries + 1, total),
|
|
4530
|
+
];
|
|
4531
|
+
}
|
|
4532
|
+
/**
|
|
4533
|
+
* "Previous" navigation button.
|
|
4534
|
+
*
|
|
4535
|
+
* @function
|
|
4536
|
+
* @param {PaginationPreviousNextProps} props - Props for the PaginationPrevious component.
|
|
4537
|
+
* @returns {JSX.Element} The rendered previous button.
|
|
4538
|
+
*/
|
|
4539
|
+
const PaginationPrevious = ({ className, disabled, children, ...props }) => (jsxRuntime.jsx("a", { className: classNames(usePrefixedClassNames('pagination-previous', {
|
|
4540
|
+
'is-disabled': disabled,
|
|
4541
|
+
}), className), "aria-disabled": disabled, tabIndex: disabled ? -1 : 0, ...props, onClick: disabled
|
|
4542
|
+
? e => {
|
|
4543
|
+
e.preventDefault();
|
|
4544
|
+
e.stopPropagation();
|
|
4070
4545
|
}
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
|
|
4081
|
-
|
|
4546
|
+
: props.onClick, children: children }));
|
|
4547
|
+
/**
|
|
4548
|
+
* "Next" navigation button.
|
|
4549
|
+
*
|
|
4550
|
+
* @function
|
|
4551
|
+
* @param {PaginationPreviousNextProps} props - Props for the PaginationNext component.
|
|
4552
|
+
* @returns {JSX.Element} The rendered next button.
|
|
4553
|
+
*/
|
|
4554
|
+
const PaginationNext = ({ className, disabled, children, ...props }) => (jsxRuntime.jsx("a", { className: classNames(usePrefixedClassNames('pagination-next', { 'is-disabled': disabled }), className), "aria-disabled": disabled, tabIndex: disabled ? -1 : 0, ...props, onClick: disabled
|
|
4555
|
+
? e => {
|
|
4556
|
+
e.preventDefault();
|
|
4557
|
+
e.stopPropagation();
|
|
4082
4558
|
}
|
|
4083
|
-
|
|
4084
|
-
// fallback: just icon name and features
|
|
4085
|
-
return [name, ...featureList].join(' ');
|
|
4086
|
-
}
|
|
4087
|
-
}
|
|
4559
|
+
: props.onClick, children: children }));
|
|
4088
4560
|
/**
|
|
4089
|
-
* The `
|
|
4561
|
+
* The `Pagination` component provides a flexible, composable Bulma pagination navigation for your Bulma React UI.
|
|
4090
4562
|
*
|
|
4091
4563
|
* @function
|
|
4092
|
-
* @param {
|
|
4093
|
-
* @returns {JSX.Element} The rendered
|
|
4094
|
-
* @see {@link https://bulma.io/documentation/
|
|
4564
|
+
* @param {PaginationProps} props - Props for the Pagination component.
|
|
4565
|
+
* @returns {JSX.Element} The rendered pagination.
|
|
4566
|
+
* @see {@link https://bulma.io/documentation/components/pagination/ | Bulma Pagination documentation}
|
|
4095
4567
|
*/
|
|
4096
|
-
const
|
|
4097
|
-
|
|
4098
|
-
color
|
|
4099
|
-
containerClassName, children, ...restProps }) => {
|
|
4100
|
-
// Get the default icon library from context, fallback to 'fa' if not set
|
|
4101
|
-
const defaultLibrary = useIconLibrary();
|
|
4102
|
-
/**
|
|
4103
|
-
* Generates Bulma helper classes and separates out remaining props.
|
|
4104
|
-
* Note: variant, features, and libraryFeatures are excluded from props spread
|
|
4105
|
-
*/
|
|
4568
|
+
const PaginationComponent = ({ color, textColor, bgColor, size, align, rounded, total, current, onPageChange, siblingCount = 1, boundaryCount = 1, previousLabel = 'Previous', nextLabel = 'Next', disabled, getPageHref, getPageLabel, className, children, ...props }) => {
|
|
4569
|
+
const [kept, setKept] = React.useState(1);
|
|
4570
|
+
warnDeprecatedColorProp('Pagination', color, 'Use the textColor / bgColor helper props instead.');
|
|
4106
4571
|
const { bulmaHelperClasses, rest } = useBulmaClasses({
|
|
4107
4572
|
color: textColor,
|
|
4108
4573
|
backgroundColor: bgColor,
|
|
4109
|
-
...
|
|
4574
|
+
...props,
|
|
4110
4575
|
});
|
|
4111
|
-
//
|
|
4112
|
-
|
|
4113
|
-
|
|
4576
|
+
// Generate Bulma classes with prefix
|
|
4577
|
+
const bulmaClasses = usePrefixedClassNames('pagination', {
|
|
4578
|
+
[`is-${color}`]: color,
|
|
4114
4579
|
[`is-${size}`]: size,
|
|
4580
|
+
[`is-${align}`]: align,
|
|
4581
|
+
'is-rounded': rounded,
|
|
4115
4582
|
});
|
|
4116
|
-
const
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
|
|
4136
|
-
|
|
4137
|
-
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
}
|
|
4153
|
-
else if (lastPart.startsWith('fa-')) {
|
|
4154
|
-
finalName = lastPart.substring(3); // Remove "fa-" prefix
|
|
4155
|
-
}
|
|
4156
|
-
else {
|
|
4157
|
-
finalName = lastPart;
|
|
4158
|
-
}
|
|
4159
|
-
}
|
|
4160
|
-
}
|
|
4161
|
-
const finalLibrary = library || defaultLibrary || 'fa';
|
|
4162
|
-
// Normalize a redundant leading library prefix (e.g. "fa-check" -> "check" when the
|
|
4163
|
-
// library is 'fa'), so `name` behaves identically with or without the prefix.
|
|
4164
|
-
finalName = stripRedundantLibraryPrefix(finalName, finalLibrary);
|
|
4165
|
-
if (!finalName) {
|
|
4166
|
-
// No glyph to name. Reachable through the public type only as `icon=""`, and otherwise
|
|
4167
|
-
// by a plain-JS caller (or one that casts). Building a class off an absent name produced
|
|
4168
|
-
// a bogus `fa-undefined` glyph; render the bare container instead, matching the
|
|
4169
|
-
// `children` branch.
|
|
4170
|
-
return (jsxRuntime.jsx("span", { className: iconContainerClasses, ...a11yProps, style: style, ...rest }));
|
|
4171
|
-
}
|
|
4172
|
-
// Backward compatibility: if libraryFeatures is provided, parse it for variant and features
|
|
4173
|
-
let finalVariant = variant;
|
|
4174
|
-
let finalFeatures = features;
|
|
4175
|
-
if (libraryFeatures && !variant && !features) {
|
|
4176
|
-
const legacyFeatures = Array.isArray(libraryFeatures)
|
|
4177
|
-
? libraryFeatures
|
|
4178
|
-
: [libraryFeatures];
|
|
4179
|
-
// For Font Awesome, extract style from features
|
|
4180
|
-
if (finalLibrary === 'fa') {
|
|
4181
|
-
const faStyle = legacyFeatures.find(f => [
|
|
4182
|
-
'fas',
|
|
4183
|
-
'far',
|
|
4184
|
-
'fab',
|
|
4185
|
-
'fal',
|
|
4186
|
-
'fad',
|
|
4187
|
-
'fat',
|
|
4188
|
-
'solid',
|
|
4189
|
-
'regular',
|
|
4190
|
-
'brands',
|
|
4191
|
-
'light',
|
|
4192
|
-
'duotone',
|
|
4193
|
-
'thin',
|
|
4194
|
-
].includes(f));
|
|
4195
|
-
if (faStyle) {
|
|
4196
|
-
finalVariant = faStyle;
|
|
4197
|
-
finalFeatures = legacyFeatures.filter(f => f !== faStyle);
|
|
4198
|
-
}
|
|
4199
|
-
else {
|
|
4200
|
-
finalFeatures = legacyFeatures;
|
|
4201
|
-
}
|
|
4202
|
-
}
|
|
4203
|
-
// For Material Icons/Symbols, extract style variant
|
|
4204
|
-
else if (finalLibrary === 'material-icons' ||
|
|
4205
|
-
finalLibrary === 'material-symbols') {
|
|
4206
|
-
const styleVariants = finalLibrary === 'material-icons'
|
|
4207
|
-
? ['filled', 'outlined', 'round', 'sharp']
|
|
4208
|
-
: ['outlined', 'rounded', 'sharp'];
|
|
4209
|
-
const styleVariant = legacyFeatures.find(f => styleVariants.includes(f));
|
|
4210
|
-
if (styleVariant) {
|
|
4211
|
-
finalVariant = styleVariant;
|
|
4212
|
-
finalFeatures = legacyFeatures.filter(f => f !== styleVariant);
|
|
4213
|
-
}
|
|
4214
|
-
else {
|
|
4215
|
-
finalFeatures = legacyFeatures;
|
|
4216
|
-
}
|
|
4217
|
-
}
|
|
4218
|
-
// For others, all features go to finalFeatures
|
|
4219
|
-
else {
|
|
4220
|
-
finalFeatures = legacyFeatures;
|
|
4221
|
-
}
|
|
4583
|
+
const paginationClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
|
|
4584
|
+
// A child that renders nothing (`{show && …}` when false) is no children.
|
|
4585
|
+
const fromTotal = total !== undefined && React.Children.toArray(children).length === 0;
|
|
4586
|
+
let content = children;
|
|
4587
|
+
if (fromTotal) {
|
|
4588
|
+
const pages = Number.isFinite(total) ? Math.max(Math.floor(total), 0) : 0;
|
|
4589
|
+
// A current page that's no number reads as the first.
|
|
4590
|
+
const page = Math.min(Math.max(whole(current ?? kept, 1), 1), Math.max(pages, 1));
|
|
4591
|
+
const choose = (next) => {
|
|
4592
|
+
if (next === page)
|
|
4593
|
+
return;
|
|
4594
|
+
if (current === undefined)
|
|
4595
|
+
setKept(next);
|
|
4596
|
+
onPageChange?.(next);
|
|
4597
|
+
};
|
|
4598
|
+
/**
|
|
4599
|
+
* What a link to `target` is given, beside what it renders. With no
|
|
4600
|
+
* `getPageHref` it's a button; a link that's `off` goes nowhere.
|
|
4601
|
+
*/
|
|
4602
|
+
const linkTo = (target, off) => ({
|
|
4603
|
+
href: off || !getPageHref ? undefined : getPageHref(target),
|
|
4604
|
+
role: getPageHref ? undefined : 'button',
|
|
4605
|
+
// A link that's off is disabled too, and the parts drop its clicks.
|
|
4606
|
+
onClick: () => choose(target),
|
|
4607
|
+
onKeyDown: getPageHref
|
|
4608
|
+
? undefined
|
|
4609
|
+
: (e) => {
|
|
4610
|
+
if (off || (e.key !== 'Enter' && e.key !== ' '))
|
|
4611
|
+
return;
|
|
4612
|
+
e.preventDefault();
|
|
4613
|
+
choose(target);
|
|
4614
|
+
},
|
|
4615
|
+
});
|
|
4616
|
+
const atStart = disabled || page <= 1;
|
|
4617
|
+
const atEnd = disabled || page >= pages;
|
|
4618
|
+
content = (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(PaginationPrevious, { disabled: atStart, ...linkTo(page - 1, atStart), children: previousLabel }), jsxRuntime.jsx(PaginationNext, { disabled: atEnd, ...linkTo(page + 1, atEnd), children: nextLabel }), jsxRuntime.jsx(PaginationList, { children: pageItems(pages, page, whole(siblingCount, 1), whole(boundaryCount, 1)).map(item => typeof item === 'number' ? (jsxRuntime.jsx(PaginationLink, { "aria-label": getPageLabel ? getPageLabel(item) : `Page ${item}`, active: item === page, disabled: disabled, ...linkTo(item, Boolean(disabled)), children: item }, item)) : (jsxRuntime.jsx(PaginationEllipsis, {}, item))) })] }));
|
|
4222
4619
|
}
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
|
|
4227
|
-
|
|
4228
|
-
|
|
4620
|
+
return (jsxRuntime.jsx("nav", { className: paginationClasses, role: "navigation", "aria-label": "pagination", ...rest, children: content }));
|
|
4621
|
+
};
|
|
4622
|
+
/**
|
|
4623
|
+
* Container for page links and ellipsis.
|
|
4624
|
+
*
|
|
4625
|
+
* @function
|
|
4626
|
+
* @param {PaginationListProps} props - Props for the PaginationList component.
|
|
4627
|
+
* @returns {JSX.Element} The rendered pagination list.
|
|
4628
|
+
*/
|
|
4629
|
+
const PaginationList = ({ className, textColor, bgColor, children, ...props }) => {
|
|
4630
|
+
const { bulmaHelperClasses, rest } = useBulmaClasses({
|
|
4631
|
+
color: textColor,
|
|
4632
|
+
backgroundColor: bgColor,
|
|
4633
|
+
...props,
|
|
4634
|
+
});
|
|
4635
|
+
return (jsxRuntime.jsx("ul", { className: classNames(usePrefixedClassNames('pagination-list'), bulmaHelperClasses, className), ...rest, children: children }));
|
|
4636
|
+
};
|
|
4637
|
+
/**
|
|
4638
|
+
* Page number or navigation link.
|
|
4639
|
+
*
|
|
4640
|
+
* @function
|
|
4641
|
+
* @param {PaginationLinkProps} props - Props for the PaginationLink component.
|
|
4642
|
+
* @returns {JSX.Element} The rendered pagination link.
|
|
4643
|
+
*/
|
|
4644
|
+
const PaginationLink = ({ className, textColor, bgColor, active, disabled, onClick, children, ...props }) => {
|
|
4645
|
+
const { bulmaHelperClasses, rest } = useBulmaClasses({
|
|
4646
|
+
color: textColor,
|
|
4647
|
+
backgroundColor: bgColor,
|
|
4648
|
+
...props,
|
|
4649
|
+
});
|
|
4650
|
+
const handleClick = (e) => {
|
|
4651
|
+
if (disabled) {
|
|
4652
|
+
e.preventDefault();
|
|
4653
|
+
e.stopPropagation();
|
|
4654
|
+
return;
|
|
4229
4655
|
}
|
|
4230
|
-
|
|
4231
|
-
|
|
4656
|
+
if (onClick) {
|
|
4657
|
+
onClick(e);
|
|
4232
4658
|
}
|
|
4233
|
-
|
|
4234
|
-
|
|
4235
|
-
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
if (finalLibrary === 'material-icons' ||
|
|
4239
|
-
finalLibrary === 'material-symbols') {
|
|
4240
|
-
return (jsxRuntime.jsx("span", { className: iconContainerClasses, ...a11yProps, style: style, ...rest, children: jsxRuntime.jsx("i", { className: iClasses, children: finalName }) }));
|
|
4241
|
-
}
|
|
4242
|
-
return (jsxRuntime.jsx("span", { className: iconContainerClasses, ...a11yProps, style: style, ...rest, children: jsxRuntime.jsx("i", { className: iClasses }) }));
|
|
4659
|
+
};
|
|
4660
|
+
return (jsxRuntime.jsx("li", { children: jsxRuntime.jsx("a", { className: classNames(usePrefixedClassNames('pagination-link', {
|
|
4661
|
+
'is-current': active,
|
|
4662
|
+
'is-disabled': disabled,
|
|
4663
|
+
}), bulmaHelperClasses, className), "aria-current": active ? 'page' : undefined, "aria-disabled": disabled, tabIndex: disabled ? -1 : 0, onClick: handleClick, ...rest, children: children }) }));
|
|
4243
4664
|
};
|
|
4665
|
+
/**
|
|
4666
|
+
* Ellipsis separator.
|
|
4667
|
+
*
|
|
4668
|
+
* @function
|
|
4669
|
+
* @param {React.LiHTMLAttributes<HTMLLIElement>} props - Standard li props.
|
|
4670
|
+
* @returns {JSX.Element} The rendered ellipsis.
|
|
4671
|
+
*/
|
|
4672
|
+
const PaginationEllipsis = props => (jsxRuntime.jsx("li", { children: jsxRuntime.jsx("span", { className: usePrefixedClassNames('pagination-ellipsis'), ...props, children: "\u2026" }) }));
|
|
4673
|
+
const Pagination = withSubComponents(PaginationComponent, {
|
|
4674
|
+
Link: PaginationLink,
|
|
4675
|
+
List: PaginationList,
|
|
4676
|
+
Ellipsis: PaginationEllipsis,
|
|
4677
|
+
Previous: PaginationPrevious,
|
|
4678
|
+
Next: PaginationNext,
|
|
4679
|
+
}, 'Pagination');
|
|
4244
4680
|
|
|
4245
4681
|
/**
|
|
4246
4682
|
* The `Panel` component implements Bulma's versatile panel block for React.
|
|
@@ -4587,204 +5023,75 @@ const Tab = ({ index, disabled, icon, iconLibrary, iconVariant, iconSize = 'smal
|
|
|
4587
5023
|
if (target) {
|
|
4588
5024
|
e.preventDefault();
|
|
4589
5025
|
target.focus();
|
|
4590
|
-
}
|
|
4591
|
-
};
|
|
4592
|
-
const handleFocus = (e) => {
|
|
4593
|
-
onFocus?.(e);
|
|
4594
|
-
ctx?.setFocusedTab(index);
|
|
4595
|
-
};
|
|
4596
|
-
const handleBlur = (e) => {
|
|
4597
|
-
onBlur?.(e);
|
|
4598
|
-
if (!tabListOf(e.currentTarget).contains(e.relatedTarget)) {
|
|
4599
|
-
ctx?.setFocusedTab(null);
|
|
4600
|
-
}
|
|
4601
|
-
};
|
|
4602
|
-
return (jsxRuntime.jsx("li", { className: classNames(activeClass, className) || undefined, role: "tab", id: tabId, "aria-selected": isActive, "aria-controls": ctx?.panelId(index), "aria-disabled": disabled || undefined, tabIndex: tabIndex, onKeyDown: handleKeyDown, onFocus: handleFocus, onBlur: handleBlur, ...props, children: jsxRuntime.jsxs("a", { ref: anchorRef, onClick: handleClick, "aria-disabled": disabled || undefined, children: [icon && (jsxRuntime.jsx(Icon, { name: icon, library: iconLibrary, variant: iconVariant, size: iconSize, features: iconFeatures })), children && jsxRuntime.jsx("span", { children: children })] }) }));
|
|
4603
|
-
};
|
|
4604
|
-
/**
|
|
4605
|
-
* Each tab; accepts `active`, `onClick`, etc.
|
|
4606
|
-
*
|
|
4607
|
-
* @function
|
|
4608
|
-
* @param {TabItemProps} props - Props for the TabItem component.
|
|
4609
|
-
* @returns {JSX.Element} The rendered tab item.
|
|
4610
|
-
* @deprecated Use `Tabs.Tab` with an `index` prop instead.
|
|
4611
|
-
*/
|
|
4612
|
-
const TabItem = ({ active, className, children, onClick, ...props }) => (jsxRuntime.jsx("li", { className: classNames({ [usePrefixedClassNames('is-active')]: active }, className) ||
|
|
4613
|
-
undefined, onClick: onClick, ...props, children: children }));
|
|
4614
|
-
/**
|
|
4615
|
-
* Container for tab content panels. No custom props beyond `children` and standard `<div>` HTML attributes. Applies the `.tabs-content` class.
|
|
4616
|
-
*
|
|
4617
|
-
* @function
|
|
4618
|
-
* @param {TabsContentProps} props - Props for the TabsContent component.
|
|
4619
|
-
* @returns {JSX.Element} The rendered tabs content wrapper.
|
|
4620
|
-
*/
|
|
4621
|
-
const TabsContentComponent = ({ className, children, ...props }) => {
|
|
4622
|
-
const contentClass = usePrefixedClassNames('tabs-content');
|
|
4623
|
-
return (jsxRuntime.jsx("div", { className: classNames(contentClass, className), ...props, children: children }));
|
|
4624
|
-
};
|
|
4625
|
-
/**
|
|
4626
|
-
* Individual content panel. Shows/hides based on active tab from context.
|
|
4627
|
-
*
|
|
4628
|
-
* @function
|
|
4629
|
-
* @param {TabContentItemProps} props - Props for the TabContentItem component.
|
|
4630
|
-
* @returns {JSX.Element} The rendered tab content panel.
|
|
4631
|
-
*/
|
|
4632
|
-
const TabContentItem = ({ index, className, children, id, ...props }) => {
|
|
4633
|
-
const ctx = useTabsContext();
|
|
4634
|
-
const isActive = ctx ? ctx.activeTab === index : false;
|
|
4635
|
-
const itemClass = usePrefixedClassNames('tabs-content-item', {
|
|
4636
|
-
'is-active': isActive,
|
|
4637
|
-
});
|
|
4638
|
-
const panelId = id ?? (ctx ? `${ctx.baseId}-panel-${index}` : undefined);
|
|
4639
|
-
const registerPanel = ctx?.registerPanel;
|
|
4640
|
-
React.useEffect(() => {
|
|
4641
|
-
if (!registerPanel || panelId === undefined)
|
|
4642
|
-
return undefined;
|
|
4643
|
-
return registerPanel({ index, id: panelId });
|
|
4644
|
-
}, [registerPanel, index, panelId]);
|
|
4645
|
-
return (jsxRuntime.jsx("div", { className: classNames(itemClass, className), role: "tabpanel", id: panelId, "aria-labelledby": ctx?.tabId(index), "aria-hidden": !isActive, ...props, children: children }));
|
|
4646
|
-
};
|
|
4647
|
-
// ---------------------------------------------------------------------------
|
|
4648
|
-
// Static property attachment
|
|
4649
|
-
// ---------------------------------------------------------------------------
|
|
4650
|
-
const TabsContent = withSubComponents(TabsContentComponent, {
|
|
4651
|
-
Item: TabContentItem,
|
|
4652
|
-
});
|
|
4653
|
-
const Tabs = withSubComponents(TabsComponent, {
|
|
4654
|
-
List: TabList,
|
|
4655
|
-
Tab,
|
|
4656
|
-
Item: TabItem,
|
|
4657
|
-
Content: TabsContent,
|
|
4658
|
-
}, 'Tabs');
|
|
4659
|
-
|
|
4660
|
-
const validButtonColors = [...validColors, 'text', 'ghost'];
|
|
4661
|
-
/**
|
|
4662
|
-
* The `Button` component provides a flexible and highly customizable button for your Bulma React UI.
|
|
4663
|
-
*
|
|
4664
|
-
* @function
|
|
4665
|
-
* @param {ButtonProps} props - Props for the Button component.
|
|
4666
|
-
* @param {React.Ref} ref - Forwarded ref to the element `as` renders.
|
|
4667
|
-
* @returns {JSX.Element} The rendered button, anchor, or custom element.
|
|
4668
|
-
* @see {@link https://bulma.io/documentation/elements/button/ | Bulma Button documentation}
|
|
4669
|
-
*/
|
|
4670
|
-
const Button = React.forwardRef(function Button(props, ref) {
|
|
4671
|
-
const { color, size, isLight, isRounded, isLoading, isStatic, isFullwidth, isFullWidth, isOutlined, isInverted, isFocused, isActive, isHovered, isDisabled, className, children, textColor, bgColor, as: Component = 'button', href, onClick, target, rel, ...bulmaProps } = props;
|
|
4672
|
-
const { bulmaHelperClasses, rest } = useBulmaClasses({
|
|
4673
|
-
color: textColor,
|
|
4674
|
-
backgroundColor: bgColor,
|
|
4675
|
-
...bulmaProps,
|
|
4676
|
-
});
|
|
4677
|
-
// Generate Bulma classes with prefix
|
|
4678
|
-
const bulmaClasses = usePrefixedClassNames('button', {
|
|
4679
|
-
[`is-${color}`]: color &&
|
|
4680
|
-
validButtonColors.includes(color),
|
|
4681
|
-
[`is-${size}`]: size,
|
|
4682
|
-
'is-outlined': isOutlined,
|
|
4683
|
-
'is-light': isLight,
|
|
4684
|
-
'is-loading': isLoading,
|
|
4685
|
-
'is-static': isStatic,
|
|
4686
|
-
'is-disabled': isDisabled,
|
|
4687
|
-
'is-rounded': isRounded,
|
|
4688
|
-
'is-hovered': isHovered,
|
|
4689
|
-
'is-focused': isFocused,
|
|
4690
|
-
'is-active': isActive,
|
|
4691
|
-
'is-inverted': isInverted,
|
|
4692
|
-
'is-fullwidth': isFullwidth ?? isFullWidth,
|
|
4693
|
-
});
|
|
4694
|
-
// Combine prefixed Bulma classes with unprefixed user className and prefixed helper classes
|
|
4695
|
-
const buttonClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
|
|
4696
|
-
if (Component !== 'button') {
|
|
4697
|
-
// Strip the form-control attributes a link-like element cannot carry, so a
|
|
4698
|
-
// JavaScript consumer or a spread object doesn't put them on the DOM. The
|
|
4699
|
-
// types no longer allow them through; this is the runtime backstop.
|
|
4700
|
-
//
|
|
4701
|
-
// Only for INTRINSIC tags. A custom component (a router Link, ...) owns its
|
|
4702
|
-
// prop contract, and `ComponentPropsWithoutRef<T>` promises the caller that
|
|
4703
|
-
// its props reach it. Stripping names by their spelling alone broke that
|
|
4704
|
-
// promise silently: `<Button as={Custom} name="x" />` type-checked while
|
|
4705
|
-
// `Custom` never received the `name` it requires.
|
|
4706
|
-
//
|
|
4707
|
-
// `autoFocus` and `type` are deliberately NOT stripped: both are valid on
|
|
4708
|
-
// targets this list was filtering them from. `autoFocus` is a global
|
|
4709
|
-
// attribute (React decides per element whether to honour it, which is not
|
|
4710
|
-
// ours to pre-empt), and `type` on an anchor is the MIME hint that
|
|
4711
|
-
// `ButtonProps<'a'>` now types it as.
|
|
4712
|
-
//
|
|
4713
|
-
// The types already answer this for a TypeScript caller: `ButtonProps<T>`
|
|
4714
|
-
// derives from `ComponentPropsWithoutRef<T>`, so only what the target
|
|
4715
|
-
// accepts can be passed. What is left is a runtime backstop for JavaScript
|
|
4716
|
-
// consumers and spread objects.
|
|
4717
|
-
//
|
|
4718
|
-
// Keep it to the two cases where a stray attribute does something. The
|
|
4719
|
-
// submit-overrides belong to a submit control and never make sense on the
|
|
4720
|
-
// anchor path. `disabled` is the one that is VISIBLE: Bulma styles
|
|
4721
|
-
// `.button[disabled]` (background, border, shadow, opacity), so letting it
|
|
4722
|
-
// through to a `<span>` greys the element out — main stripped it, and not
|
|
4723
|
-
// stripping it would be a silent visual change on a types-only release.
|
|
4724
|
-
//
|
|
4725
|
-
// Everything else (`name`, `value`, `form`) is inert where it does not
|
|
4726
|
-
// belong, and enumerating which tags own it is what kept going wrong:
|
|
4727
|
-
// first all nine names on every non-button tag (so `<Button as="input"
|
|
4728
|
-
// name="query" value="Search">` submitted nothing), then a form-control
|
|
4729
|
-
// allowlist that still lost `formAction` on an input and `value` on an
|
|
4730
|
-
// option. Those now ride on the types instead.
|
|
4731
|
-
//
|
|
4732
|
-
// A custom component is exempt entirely — it owns its prop contract, and
|
|
4733
|
-
// `ComponentPropsWithoutRef<T>` promises the caller its props arrive.
|
|
4734
|
-
//
|
|
4735
|
-
// The elements that own `disabled`, per the HTML spec. A closed set, unlike
|
|
4736
|
-
// the open-ended question of which tag owns which attribute.
|
|
4737
|
-
const SUBMIT_OVERRIDES = [
|
|
4738
|
-
'formAction',
|
|
4739
|
-
'formEncType',
|
|
4740
|
-
'formMethod',
|
|
4741
|
-
'formNoValidate',
|
|
4742
|
-
'formTarget',
|
|
4743
|
-
];
|
|
4744
|
-
const OWNS_DISABLED = [
|
|
4745
|
-
'button',
|
|
4746
|
-
'fieldset',
|
|
4747
|
-
'input',
|
|
4748
|
-
'optgroup',
|
|
4749
|
-
'option',
|
|
4750
|
-
'select',
|
|
4751
|
-
'textarea',
|
|
4752
|
-
];
|
|
4753
|
-
// Two independent filters. They are not exclusive — an `<a>` owns neither
|
|
4754
|
-
// the submit-overrides nor `disabled`, so it takes both — and deriving them
|
|
4755
|
-
// from one destructure, or from one if/else, is what broke each of them in
|
|
4756
|
-
// turn: first the overrides came off every intrinsic (an `as="input"` lost
|
|
4757
|
-
// `formAction`), then scoping them to the anchor let `disabled` back onto
|
|
4758
|
-
// it.
|
|
4759
|
-
const forwardedRest = { ...rest };
|
|
4760
|
-
if (typeof Component === 'string' && !isCustomElement(Component)) {
|
|
4761
|
-
// Meaningless anywhere but a submit control, and an anchor is not one.
|
|
4762
|
-
if (Component === 'a') {
|
|
4763
|
-
for (const key of SUBMIT_OVERRIDES)
|
|
4764
|
-
delete forwardedRest[key];
|
|
4765
|
-
}
|
|
4766
|
-
// Visible via Bulma's `.button[disabled]`, so withheld from any element
|
|
4767
|
-
// that does not own it.
|
|
4768
|
-
if (!OWNS_DISABLED.includes(Component))
|
|
4769
|
-
delete forwardedRest.disabled;
|
|
4770
|
-
}
|
|
4771
|
-
return (jsxRuntime.jsx(Component, { ref: ref, className: buttonClasses, href: href, target: target, rel: rel, "aria-disabled": isDisabled, tabIndex: isDisabled ? -1 : undefined, onClick: isDisabled
|
|
4772
|
-
? // Guarded, because this handler is installed into whatever `as`
|
|
4773
|
-
// renders and a custom component's `onClick` need not be a DOM
|
|
4774
|
-
// event handler at all. One declaring `onClick: (v: string) =>
|
|
4775
|
-
// void` calls this with a string, and an unguarded
|
|
4776
|
-
// `e.preventDefault()` throws `is not a function`. Blocking the
|
|
4777
|
-
// real event is the point; a foreign payload is simply not ours
|
|
4778
|
-
// to act on.
|
|
4779
|
-
(e) => {
|
|
4780
|
-
if (typeof e?.preventDefault === 'function')
|
|
4781
|
-
e.preventDefault();
|
|
4782
|
-
}
|
|
4783
|
-
: onClick, ...forwardedRest, children: children }));
|
|
4784
|
-
}
|
|
4785
|
-
return (jsxRuntime.jsx("button", { ref: ref, className: buttonClasses, disabled: isDisabled, onClick: onClick, ...rest, children: children }));
|
|
5026
|
+
}
|
|
5027
|
+
};
|
|
5028
|
+
const handleFocus = (e) => {
|
|
5029
|
+
onFocus?.(e);
|
|
5030
|
+
ctx?.setFocusedTab(index);
|
|
5031
|
+
};
|
|
5032
|
+
const handleBlur = (e) => {
|
|
5033
|
+
onBlur?.(e);
|
|
5034
|
+
if (!tabListOf(e.currentTarget).contains(e.relatedTarget)) {
|
|
5035
|
+
ctx?.setFocusedTab(null);
|
|
5036
|
+
}
|
|
5037
|
+
};
|
|
5038
|
+
return (jsxRuntime.jsx("li", { className: classNames(activeClass, className) || undefined, role: "tab", id: tabId, "aria-selected": isActive, "aria-controls": ctx?.panelId(index), "aria-disabled": disabled || undefined, tabIndex: tabIndex, onKeyDown: handleKeyDown, onFocus: handleFocus, onBlur: handleBlur, ...props, children: jsxRuntime.jsxs("a", { ref: anchorRef, onClick: handleClick, "aria-disabled": disabled || undefined, children: [icon && (jsxRuntime.jsx(Icon, { name: icon, library: iconLibrary, variant: iconVariant, size: iconSize, features: iconFeatures })), children && jsxRuntime.jsx("span", { children: children })] }) }));
|
|
5039
|
+
};
|
|
5040
|
+
/**
|
|
5041
|
+
* Each tab; accepts `active`, `onClick`, etc.
|
|
5042
|
+
*
|
|
5043
|
+
* @function
|
|
5044
|
+
* @param {TabItemProps} props - Props for the TabItem component.
|
|
5045
|
+
* @returns {JSX.Element} The rendered tab item.
|
|
5046
|
+
* @deprecated Use `Tabs.Tab` with an `index` prop instead.
|
|
5047
|
+
*/
|
|
5048
|
+
const TabItem = ({ active, className, children, onClick, ...props }) => (jsxRuntime.jsx("li", { className: classNames({ [usePrefixedClassNames('is-active')]: active }, className) ||
|
|
5049
|
+
undefined, onClick: onClick, ...props, children: children }));
|
|
5050
|
+
/**
|
|
5051
|
+
* Container for tab content panels. No custom props beyond `children` and standard `<div>` HTML attributes. Applies the `.tabs-content` class.
|
|
5052
|
+
*
|
|
5053
|
+
* @function
|
|
5054
|
+
* @param {TabsContentProps} props - Props for the TabsContent component.
|
|
5055
|
+
* @returns {JSX.Element} The rendered tabs content wrapper.
|
|
5056
|
+
*/
|
|
5057
|
+
const TabsContentComponent = ({ className, children, ...props }) => {
|
|
5058
|
+
const contentClass = usePrefixedClassNames('tabs-content');
|
|
5059
|
+
return (jsxRuntime.jsx("div", { className: classNames(contentClass, className), ...props, children: children }));
|
|
5060
|
+
};
|
|
5061
|
+
/**
|
|
5062
|
+
* Individual content panel. Shows/hides based on active tab from context.
|
|
5063
|
+
*
|
|
5064
|
+
* @function
|
|
5065
|
+
* @param {TabContentItemProps} props - Props for the TabContentItem component.
|
|
5066
|
+
* @returns {JSX.Element} The rendered tab content panel.
|
|
5067
|
+
*/
|
|
5068
|
+
const TabContentItem = ({ index, className, children, id, ...props }) => {
|
|
5069
|
+
const ctx = useTabsContext();
|
|
5070
|
+
const isActive = ctx ? ctx.activeTab === index : false;
|
|
5071
|
+
const itemClass = usePrefixedClassNames('tabs-content-item', {
|
|
5072
|
+
'is-active': isActive,
|
|
5073
|
+
});
|
|
5074
|
+
const panelId = id ?? (ctx ? `${ctx.baseId}-panel-${index}` : undefined);
|
|
5075
|
+
const registerPanel = ctx?.registerPanel;
|
|
5076
|
+
React.useEffect(() => {
|
|
5077
|
+
if (!registerPanel || panelId === undefined)
|
|
5078
|
+
return undefined;
|
|
5079
|
+
return registerPanel({ index, id: panelId });
|
|
5080
|
+
}, [registerPanel, index, panelId]);
|
|
5081
|
+
return (jsxRuntime.jsx("div", { className: classNames(itemClass, className), role: "tabpanel", id: panelId, "aria-labelledby": ctx?.tabId(index), "aria-hidden": !isActive, ...props, children: children }));
|
|
5082
|
+
};
|
|
5083
|
+
// ---------------------------------------------------------------------------
|
|
5084
|
+
// Static property attachment
|
|
5085
|
+
// ---------------------------------------------------------------------------
|
|
5086
|
+
const TabsContent = withSubComponents(TabsContentComponent, {
|
|
5087
|
+
Item: TabContentItem,
|
|
4786
5088
|
});
|
|
4787
|
-
|
|
5089
|
+
const Tabs = withSubComponents(TabsComponent, {
|
|
5090
|
+
List: TabList,
|
|
5091
|
+
Tab,
|
|
5092
|
+
Item: TabItem,
|
|
5093
|
+
Content: TabsContent,
|
|
5094
|
+
}, 'Tabs');
|
|
4788
5095
|
|
|
4789
5096
|
// INTERNAL: deliberately not exported from src/index.ts.
|
|
4790
5097
|
/**
|
|
@@ -8753,129 +9060,6 @@ const Box = ({ className, textColor, color, bgColor, hasShadow = true, children,
|
|
|
8753
9060
|
return (jsxRuntime.jsx("div", { className: boxClasses, style: mergeBulmaStyles(bulmaHelperStyles, style), ...rest, children: children }));
|
|
8754
9061
|
};
|
|
8755
9062
|
|
|
8756
|
-
/**
|
|
8757
|
-
* The `LinkButton` component renders a `<button>` that visually looks like text or a link.
|
|
8758
|
-
*
|
|
8759
|
-
* @function
|
|
8760
|
-
* @param {LinkButtonProps} props - Props for the LinkButton component.
|
|
8761
|
-
* @param {React.Ref} ref - Forwarded ref to the element `as` renders.
|
|
8762
|
-
* @returns {JSX.Element} The rendered link-styled button element.
|
|
8763
|
-
*
|
|
8764
|
-
* @example
|
|
8765
|
-
* // Text variant (default)
|
|
8766
|
-
* <LinkButton onClick={handleClick}>Click me</LinkButton>
|
|
8767
|
-
*
|
|
8768
|
-
* @example
|
|
8769
|
-
* // Underline variant with color
|
|
8770
|
-
* <LinkButton variant="underline" color="primary">Learn more</LinkButton>
|
|
8771
|
-
*/
|
|
8772
|
-
const LinkButton = React.forwardRef(function LinkButton({ variant = 'text', color, className, ...props }, ref) {
|
|
8773
|
-
const buttonColor = variant === 'underline' ? 'text' : variant;
|
|
8774
|
-
const prefixedClasses = usePrefixedClassNames('link-button', color && `link-button-${color}`, variant === 'underline' && 'link-button-underline');
|
|
8775
|
-
return (jsxRuntime.jsx(Button
|
|
8776
|
-
// LinkButton is polymorphic through to Button, which resolves the element
|
|
8777
|
-
// from `as`. TS cannot infer that through the spread below.
|
|
8778
|
-
, {
|
|
8779
|
-
// LinkButton is polymorphic through to Button, which resolves the element
|
|
8780
|
-
// from `as`. TS cannot infer that through the spread below.
|
|
8781
|
-
ref: ref, color: buttonColor, className: classNames(prefixedClasses, className), ...props }));
|
|
8782
|
-
});
|
|
8783
|
-
LinkButton.displayName = 'LinkButton';
|
|
8784
|
-
|
|
8785
|
-
/**
|
|
8786
|
-
* The `ToggleButton` component renders a button that stays pressed until it's pressed again, and says whether it's on through `aria-pressed`.
|
|
8787
|
-
*
|
|
8788
|
-
* It's always a `<button type="button">`, so it never submits a form and
|
|
8789
|
-
* carries no form value: a setting saved with a form is a `Checkbox` or a
|
|
8790
|
-
* `Switch`. A set of toggles that each turn on and off on their own goes in
|
|
8791
|
-
* `Buttons hasAddons` with `role="group"` and an `aria-label`. A set where only
|
|
8792
|
-
* one can be on is a radio group rather than a row of these: use
|
|
8793
|
-
* `SegmentedControl`, or `Radios` for a choice that's sent with a form.
|
|
8794
|
-
*
|
|
8795
|
-
* @function
|
|
8796
|
-
* @param {ToggleButtonProps} props - Props for the ToggleButton component.
|
|
8797
|
-
* @param {React.Ref<HTMLButtonElement>} ref - Forwarded ref to the `<button>`.
|
|
8798
|
-
* @returns {JSX.Element} The rendered toggle button.
|
|
8799
|
-
* @see {@link https://www.w3.org/WAI/ARIA/apg/patterns/button/ | APG button pattern}
|
|
8800
|
-
*
|
|
8801
|
-
* @example
|
|
8802
|
-
* <ToggleButton onPressedChange={setMuted}>Mute</ToggleButton>
|
|
8803
|
-
*
|
|
8804
|
-
* @example
|
|
8805
|
-
* <Buttons hasAddons role="group" aria-label="Text style">
|
|
8806
|
-
* <ToggleButton aria-label="Bold" pressed={bold} onPressedChange={setBold}>
|
|
8807
|
-
* <Icon name="bold" />
|
|
8808
|
-
* </ToggleButton>
|
|
8809
|
-
* </Buttons>
|
|
8810
|
-
*/
|
|
8811
|
-
const ToggleButton = React.forwardRef(function ToggleButton({ pressed: controlledPressed, defaultPressed = false, onPressedChange, color = 'link', isLoading, 'aria-busy': ariaBusy, 'aria-disabled': ariaDisabled, className, onClick, ...props }, ref) {
|
|
8812
|
-
const isControlled = controlledPressed !== undefined;
|
|
8813
|
-
const [internalPressed, setInternalPressed] = React.useState(defaultPressed);
|
|
8814
|
-
const pressed = controlledPressed ?? internalPressed;
|
|
8815
|
-
// Prefixed like Button's own classes, which a hand-written
|
|
8816
|
-
// `className="is-selected"` could not be under ConfigProvider classPrefix.
|
|
8817
|
-
// Bulma lifts `is-selected` above its neighbours in `.buttons.has-addons`.
|
|
8818
|
-
const selectedClass = usePrefixedClassNames({ 'is-selected': pressed });
|
|
8819
|
-
// The caller's onClick runs first, so a `preventDefault()` there vetoes
|
|
8820
|
-
// the toggle, as it does in Radix, MUI and Zag. Enter and Space reach here
|
|
8821
|
-
// through the button's own click, and a disabled button gets no click.
|
|
8822
|
-
//
|
|
8823
|
-
// A loading one gets the click but drops it. Bulma's spinner only stops a
|
|
8824
|
-
// pointer, so without this a key flipped a toggle the mouse couldn't.
|
|
8825
|
-
// `disabled` would stop both, but it takes focus away mid-save.
|
|
8826
|
-
const handleClick = (event) => {
|
|
8827
|
-
if (isLoading)
|
|
8828
|
-
return;
|
|
8829
|
-
onClick?.(event);
|
|
8830
|
-
if (event.defaultPrevented)
|
|
8831
|
-
return;
|
|
8832
|
-
const next = !pressed;
|
|
8833
|
-
onPressedChange?.(next);
|
|
8834
|
-
if (!isControlled)
|
|
8835
|
-
setInternalPressed(next);
|
|
8836
|
-
};
|
|
8837
|
-
// The props are spread first, so what a toggle depends on wins over
|
|
8838
|
-
// anything a JavaScript caller or a spread object passes along: it stays
|
|
8839
|
-
// a button, never submits, reports the state it's in, and says it's busy
|
|
8840
|
-
// and won't respond while it's loading. A caller's own `aria-busy` or
|
|
8841
|
-
// `aria-disabled` stays when it isn't loading.
|
|
8842
|
-
return (jsxRuntime.jsx(Button, { ...props, ref: ref, as: "button", type: "button", isLoading: isLoading, "aria-busy": isLoading || ariaBusy, "aria-disabled": isLoading || ariaDisabled, "aria-pressed": pressed, color: pressed ? color : undefined, className: classNames(selectedClass, className), onClick: handleClick }));
|
|
8843
|
-
});
|
|
8844
|
-
ToggleButton.displayName = 'ToggleButton';
|
|
8845
|
-
|
|
8846
|
-
const validButtonsSizes = ['small', 'medium', 'large'];
|
|
8847
|
-
/**
|
|
8848
|
-
* The `Buttons` component lets you group multiple `Button` elements together with Bulma's spacing, alignment, and add-on features.
|
|
8849
|
-
*
|
|
8850
|
-
* @function
|
|
8851
|
-
* @param {ButtonsProps} props - Props for the Buttons component.
|
|
8852
|
-
* @returns {JSX.Element} The rendered group of buttons.
|
|
8853
|
-
* @see {@link https://bulma.io/documentation/elements/button/#group | Bulma Button Group documentation}
|
|
8854
|
-
*/
|
|
8855
|
-
const ButtonsComponent = ({ className, textColor, color, bgColor, isCentered, isRight, hasAddons, size, children, ...props }) => {
|
|
8856
|
-
const buttonsClasses = usePrefixedClassNames('buttons', {
|
|
8857
|
-
'is-centered': isCentered,
|
|
8858
|
-
'is-right': isRight,
|
|
8859
|
-
'has-addons': hasAddons,
|
|
8860
|
-
[`are-${size}`]: size && validButtonsSizes.includes(size),
|
|
8861
|
-
});
|
|
8862
|
-
/**
|
|
8863
|
-
* Generates Bulma helper classes and separates out remaining props.
|
|
8864
|
-
*/
|
|
8865
|
-
const { bulmaHelperClasses, rest } = useBulmaClasses({
|
|
8866
|
-
color: textColor ?? color,
|
|
8867
|
-
backgroundColor: bgColor,
|
|
8868
|
-
...props,
|
|
8869
|
-
});
|
|
8870
|
-
const combinedClasses = classNames(buttonsClasses, className, bulmaHelperClasses);
|
|
8871
|
-
return (jsxRuntime.jsx("div", { className: combinedClasses, ...rest, children: children }));
|
|
8872
|
-
};
|
|
8873
|
-
const Buttons = withSubComponents(ButtonsComponent, {
|
|
8874
|
-
Button,
|
|
8875
|
-
LinkButton,
|
|
8876
|
-
ToggleButton,
|
|
8877
|
-
}, 'Buttons');
|
|
8878
|
-
|
|
8879
9063
|
/**
|
|
8880
9064
|
* The `Code` component renders a styled `<code>` element with Bulma helper class integration.
|
|
8881
9065
|
*
|
|
@@ -9904,27 +10088,6 @@ const OrderedListComponent = ({ className, textColor, bgColor, children, ...prop
|
|
|
9904
10088
|
};
|
|
9905
10089
|
const OrderedList = withSubComponents(OrderedListComponent, { Item: ListItem }, 'OrderedList');
|
|
9906
10090
|
|
|
9907
|
-
/**
|
|
9908
|
-
* The `Paragraph` component renders a styled `<p>` element with Bulma helper class integration.
|
|
9909
|
-
*
|
|
9910
|
-
* @function
|
|
9911
|
-
* @param {ParagraphProps} props - Props for the Paragraph component.
|
|
9912
|
-
* @returns {JSX.Element} The rendered paragraph element.
|
|
9913
|
-
*/
|
|
9914
|
-
const Paragraph = ({ className, textColor, bgColor, children, ...props }) => {
|
|
9915
|
-
/**
|
|
9916
|
-
* Generates Bulma helper classes and separates out remaining props.
|
|
9917
|
-
*/
|
|
9918
|
-
const { bulmaHelperClasses, rest } = useBulmaClasses({
|
|
9919
|
-
color: textColor,
|
|
9920
|
-
backgroundColor: bgColor,
|
|
9921
|
-
...props,
|
|
9922
|
-
});
|
|
9923
|
-
const bulmaClasses = usePrefixedClassNames();
|
|
9924
|
-
const paragraphClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
|
|
9925
|
-
return (jsxRuntime.jsx("p", { className: paragraphClasses || undefined, ...rest, children: children }));
|
|
9926
|
-
};
|
|
9927
|
-
|
|
9928
10091
|
/**
|
|
9929
10092
|
* The `Pre` component renders a styled `<pre>` element with Bulma helper class integration.
|
|
9930
10093
|
*
|
|
@@ -10337,43 +10500,6 @@ isMultiline, size, children, ...props }) => {
|
|
|
10337
10500
|
};
|
|
10338
10501
|
const Tags = withSubComponents(TagsComponent, { Tag }, 'Tags');
|
|
10339
10502
|
|
|
10340
|
-
const validTitleSizes = ['1', '2', '3', '4', '5', '6'];
|
|
10341
|
-
const validTitleElements = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p'];
|
|
10342
|
-
/**
|
|
10343
|
-
* The `Title` component renders a Bulma-styled title (heading), supporting sizes `1`-`6`, spacing, and rendering as any heading or paragraph element (`h1`-`h6`, `p`).
|
|
10344
|
-
*
|
|
10345
|
-
* @function
|
|
10346
|
-
* @param {TitleProps} props - Props for the Title component.
|
|
10347
|
-
* @returns {JSX.Element} The rendered title element.
|
|
10348
|
-
* @see {@link https://bulma.io/documentation/elements/title/ | Bulma Title documentation}
|
|
10349
|
-
*/
|
|
10350
|
-
const Title = ({ className, size, isSpaced, as = 'h1', hasSkeleton, textColor, bgColor, children, ...props }) => {
|
|
10351
|
-
/**
|
|
10352
|
-
* Generates Bulma helper classes and separates out remaining props.
|
|
10353
|
-
*/
|
|
10354
|
-
const { bulmaHelperClasses, rest } = useBulmaClasses({
|
|
10355
|
-
color: textColor,
|
|
10356
|
-
backgroundColor: bgColor,
|
|
10357
|
-
...props,
|
|
10358
|
-
});
|
|
10359
|
-
// Validate 'as' prop at runtime
|
|
10360
|
-
const element = validTitleElements.includes(as) ? as : 'h1';
|
|
10361
|
-
// Validate 'size' prop at runtime (numeric sizes normalize to strings)
|
|
10362
|
-
const normalized = size == null ? undefined : String(size);
|
|
10363
|
-
const validSize = normalized && validTitleSizes.includes(normalized)
|
|
10364
|
-
? normalized
|
|
10365
|
-
: undefined;
|
|
10366
|
-
const bulmaClasses = usePrefixedClassNames('title', {
|
|
10367
|
-
[`is-${validSize}`]: validSize,
|
|
10368
|
-
'is-spaced': isSpaced,
|
|
10369
|
-
'has-skeleton': hasSkeleton,
|
|
10370
|
-
});
|
|
10371
|
-
const titleClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
|
|
10372
|
-
// Determine the tag based on 'element' and 'validSize'
|
|
10373
|
-
const Tag = element === 'p' ? 'p' : validSize ? `h${validSize}` : element;
|
|
10374
|
-
return (jsxRuntime.jsx(Tag, { className: titleClasses, ...rest, children: children }));
|
|
10375
|
-
};
|
|
10376
|
-
|
|
10377
10503
|
/**
|
|
10378
10504
|
* The `UnorderedList` component renders a styled unordered list (`<ul>`) element with Bulma helper class integration.
|
|
10379
10505
|
*
|
|
@@ -10568,7 +10694,8 @@ const FieldLabelForContext = React.createContext({});
|
|
|
10568
10694
|
* renders with, if it has one. An unlabeled Field passes both through
|
|
10569
10695
|
* together, so a control in the inner Field of a horizontal layout can tell
|
|
10570
10696
|
* the outer label names it, and point at that label by its id, as a range
|
|
10571
|
-
* Slider's thumbs (#981) and Autocomplete's
|
|
10697
|
+
* Slider's thumbs (#981) and Autocomplete's and Taginput's suggestion lists
|
|
10698
|
+
* (#998, #1082) do. The
|
|
10572
10699
|
* `htmlFor` is only ever compared with a control's own id, never adopted, so
|
|
10573
10700
|
* it hands out no id for a control. It keeps the generated id after the
|
|
10574
10701
|
* label drops a `for` nothing took (#1004), which no control's id matches
|
|
@@ -10823,9 +10950,9 @@ Control.displayName = 'Control';
|
|
|
10823
10950
|
* `id`, or give the horizontal `Field` a `label` prop, which renders this
|
|
10824
10951
|
* column and its `<label>` for you. A `<label>` you put here names the
|
|
10825
10952
|
* control through its `htmlFor` alone: a range `Slider`'s thumbs and an
|
|
10826
|
-
* `Autocomplete`'s suggestion list point `aria-labelledby` at
|
|
10827
|
-
* label only when its `label` prop renders it and it names the
|
|
10828
|
-
* Where it would not name the control on its own, as in an inner `Field`,
|
|
10953
|
+
* `Autocomplete`'s or `Taginput`'s suggestion list point `aria-labelledby` at
|
|
10954
|
+
* a `Field`'s label only when its `label` prop renders it and it names the
|
|
10955
|
+
* control. Where it would not name the control on its own, as in an inner `Field`,
|
|
10829
10956
|
* pass `labelProps={{ htmlFor, id }}` with a matching `id` on the control.
|
|
10830
10957
|
*
|
|
10831
10958
|
* @function
|
|
@@ -12834,7 +12961,7 @@ Autocomplete.displayName = 'Autocomplete';
|
|
|
12834
12961
|
*/
|
|
12835
12962
|
const Taginput = React.forwardRef(({
|
|
12836
12963
|
// Field props
|
|
12837
|
-
label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName, value: controlledValue, defaultValue = [], data
|
|
12964
|
+
label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName, value: controlledValue, defaultValue = [], data: dataProp, placeholder, field = 'label', allowNew = true, allowDuplicates = false, openOnFocus = false, removeOnKeys = true, confirmKeys = ['Enter', ','], closable = true, attached = false, maxTags, maxlength, disabled = false, readonly = false, rounded = false, ellipsis = false, hasCounter = true, onPasteSeparators = [','], beforeAdding, createTag, keepFirst = false, keepOpen = true, loading = false, ariaCloseLabel, icon, iconLibrary: iconLibraryProp, iconVariant, iconFeatures, color, tagColor, size, onChange, onAdd, onRemove, onTyping, tagTemplate, name, form, id, className, ...props }, ref) => {
|
|
12838
12965
|
const insideField = useInsideField();
|
|
12839
12966
|
const insideControl = useInsideControl();
|
|
12840
12967
|
const ownField = rendersOwnField('Taginput', {
|
|
@@ -12857,17 +12984,26 @@ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName,
|
|
|
12857
12984
|
const [inputValue, setInputValue] = React.useState('');
|
|
12858
12985
|
const [isActive, setIsActive] = React.useState(false);
|
|
12859
12986
|
const [highlightedIndex, setHighlightedIndex] = React.useState(-1);
|
|
12987
|
+
const listboxId = `${React.useId()}-listbox`;
|
|
12988
|
+
// With `data` the input offers suggestions, so it's a combobox, even
|
|
12989
|
+
// while none match. Without it there's never a list, so it stays a plain
|
|
12990
|
+
// text field.
|
|
12991
|
+
const hasSuggestions = dataProp !== undefined;
|
|
12992
|
+
const data = dataProp ?? [];
|
|
12860
12993
|
// Use controlled or internal tags
|
|
12861
12994
|
const tags = controlledValue !== undefined ? controlledValue : internalTags;
|
|
12862
12995
|
const isMaxReached = maxTags !== undefined && tags.length >= maxTags;
|
|
12863
12996
|
// At the tag limit the text input is not rendered, so there is nothing to
|
|
12864
|
-
// wire a label to, its own or a surrounding Field's.
|
|
12865
|
-
|
|
12997
|
+
// wire a label to, its own or a surrounding Field's. The suggestion list
|
|
12998
|
+
// points `aria-labelledby` at the label that names the input, so with
|
|
12999
|
+
// suggestions it needs that label's id.
|
|
13000
|
+
const { controlId, fieldLabelProps, labelled, labelId: listLabelId, } = useAutoLabelId({
|
|
12866
13001
|
label,
|
|
12867
13002
|
id,
|
|
12868
13003
|
labelProps,
|
|
12869
13004
|
rendersLabel: ownField && !isMaxReached,
|
|
12870
13005
|
hasInput: !isMaxReached,
|
|
13006
|
+
needsLabelId: hasSuggestions,
|
|
12871
13007
|
});
|
|
12872
13008
|
// Get display value from tag
|
|
12873
13009
|
const getDisplayValue = (tag) => {
|
|
@@ -12887,6 +13023,7 @@ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName,
|
|
|
12887
13023
|
const hasInput = inputValue || (openOnFocus && isActive);
|
|
12888
13024
|
return matchesInput && notAlreadyAdded && hasInput;
|
|
12889
13025
|
});
|
|
13026
|
+
const listOpen = isActive && filteredData.length > 0;
|
|
12890
13027
|
// keepFirst: auto-highlight first item when dropdown opens
|
|
12891
13028
|
React.useEffect(() => {
|
|
12892
13029
|
if (keepFirst && isActive && filteredData.length > 0) {
|
|
@@ -13127,15 +13264,35 @@ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName,
|
|
|
13127
13264
|
[`is-${messageColor}`]: !!messageColor,
|
|
13128
13265
|
});
|
|
13129
13266
|
const messageEl = message ? jsxRuntime.jsx("p", { className: helpClass, children: message }) : null;
|
|
13267
|
+
// The combobox controls its listbox and points at the highlighted option,
|
|
13268
|
+
// the way Autocomplete's does. The listbox takes its name from the label
|
|
13269
|
+
// that names the combobox, the one this renders or a surrounding Field's,
|
|
13270
|
+
// and keeps a fallback name when no label names it or the one that does
|
|
13271
|
+
// has no id to point at. While no suggestion matches there's no listbox,
|
|
13272
|
+
// so the combobox is collapsed and points at nothing. It skips a
|
|
13273
|
+
// highlighted suggestion that's blank once trimmed, since that one can't
|
|
13274
|
+
// become a tag, and the option it points at is the one marked selected.
|
|
13275
|
+
const optionId = (index) => `${listboxId}-${index}`;
|
|
13276
|
+
const activeOptionId = listOpen && filteredData[highlightedIndex]?.trim()
|
|
13277
|
+
? optionId(highlightedIndex)
|
|
13278
|
+
: undefined;
|
|
13279
|
+
const comboboxProps = {
|
|
13280
|
+
role: 'combobox',
|
|
13281
|
+
'aria-expanded': listOpen,
|
|
13282
|
+
'aria-controls': listOpen ? listboxId : undefined,
|
|
13283
|
+
'aria-activedescendant': activeOptionId,
|
|
13284
|
+
'aria-haspopup': 'listbox',
|
|
13285
|
+
'aria-autocomplete': 'list',
|
|
13286
|
+
};
|
|
13130
13287
|
const taginputElement = (jsxRuntime.jsxs("div", { ref: containerRef, className: combinedClasses, onClick: handleContainerClick, ...rest, children: [jsxRuntime.jsxs("div", { className: containerClasses, children: [icon && (jsxRuntime.jsx(Icon, { name: icon, library: resolvedLibrary, variant: iconVariant, features: iconFeatures, containerClassName: pcn('icon', 'is-left') })), jsxRuntime.jsxs("div", { className: tagsClasses, children: [tags.map((tag, index) => {
|
|
13131
13288
|
const displayVal = getDisplayValue(tag);
|
|
13132
13289
|
return (jsxRuntime.jsxs("span", { className: tagClasses, children: [tagTemplate ? (tagTemplate(tag)) : ellipsis ? (jsxRuntime.jsx("span", { title: displayVal, children: displayVal })) : (displayVal), closable && !disabled && !readonly && (jsxRuntime.jsx("button", { type: "button", className: pcn('delete', 'is-small'), onClick: e => {
|
|
13133
13290
|
e.stopPropagation();
|
|
13134
13291
|
removeTag(index);
|
|
13135
13292
|
}, "aria-label": ariaCloseLabel || `Remove ${displayVal}` }))] }, index));
|
|
13136
|
-
}), !isMaxReached && (jsxRuntime.jsx("input", { ref: combinedRef, type: "text", className: inputClasses, id: controlId, value: inputValue, placeholder: tags.length === 0 ? placeholder : undefined, disabled: disabled, readOnly: readonly, maxLength: maxlength, onChange: handleInputChange, onFocus: handleFocus, onKeyDown: handleKeyDown, onPaste: handlePaste, "aria-label": labelled ? undefined : 'Add tag' }))] }), loading && (jsxRuntime.jsx("span", { className: iconRightClass, children: jsxRuntime.jsx("span", { className: loaderClass }) }))] }),
|
|
13293
|
+
}), !isMaxReached && (jsxRuntime.jsx("input", { ref: combinedRef, type: "text", className: inputClasses, id: controlId, value: inputValue, placeholder: tags.length === 0 ? placeholder : undefined, disabled: disabled, readOnly: readonly, maxLength: maxlength, onChange: handleInputChange, onFocus: handleFocus, onKeyDown: handleKeyDown, onPaste: handlePaste, ...(hasSuggestions && comboboxProps), "aria-label": labelled ? undefined : 'Add tag' }))] }), loading && (jsxRuntime.jsx("span", { className: iconRightClass, children: jsxRuntime.jsx("span", { className: loaderClass }) }))] }), listOpen && (jsxRuntime.jsx("div", { className: dropdownMenuClasses, children: jsxRuntime.jsx("div", { ref: dropdownRef, id: listboxId, className: dropdownContentClasses, role: "listbox", "aria-labelledby": listLabelId, "aria-label": listLabelId ? undefined : 'Suggestions', children: filteredData.map((item, index) => (jsxRuntime.jsx("a", { id: optionId(index), className: pcn('dropdown-item', {
|
|
13137
13294
|
'is-active': index === highlightedIndex,
|
|
13138
|
-
}), onClick: () => addTag(item), onMouseEnter: () => setHighlightedIndex(index), role: "option", "aria-selected": index ===
|
|
13295
|
+
}), onClick: () => addTag(item), onMouseEnter: () => setHighlightedIndex(index), role: "option", "aria-selected": optionId(index) === activeOptionId, children: item }, index))) }) })), showCounter && jsxRuntime.jsx("small", { className: counterClasses, children: counterText }), name &&
|
|
13139
13296
|
tags.map((tag, i) => (jsxRuntime.jsx("input", { type: "hidden", name: name, value: getDisplayValue(tag), form: form }, `tag-input-${i}`)))] }));
|
|
13140
13297
|
if (ownField) {
|
|
13141
13298
|
return (jsxRuntime.jsxs(Field, { label: label, labelSize: labelSize, labelProps: fieldLabelProps, horizontal: horizontal, className: fieldClassName, children: [taginputElement, messageEl] }));
|
|
@@ -19463,6 +19620,11 @@ exports.Dropdown = Dropdown;
|
|
|
19463
19620
|
exports.DropdownDivider = DropdownDivider;
|
|
19464
19621
|
exports.DropdownItem = DropdownItem;
|
|
19465
19622
|
exports.Emphasis = Emphasis;
|
|
19623
|
+
exports.EmptyState = EmptyState;
|
|
19624
|
+
exports.EmptyStateActions = EmptyStateActions;
|
|
19625
|
+
exports.EmptyStateDescription = EmptyStateDescription;
|
|
19626
|
+
exports.EmptyStateIndicator = EmptyStateIndicator;
|
|
19627
|
+
exports.EmptyStateTitle = EmptyStateTitle;
|
|
19466
19628
|
exports.Field = Field;
|
|
19467
19629
|
exports.FieldBody = FieldBody;
|
|
19468
19630
|
exports.FieldLabel = FieldLabel;
|