@bitrise/bitkit-v2 0.3.502-beta.3237 → 0.3.502-beta.3238
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/AGENTS.md +1 -1
- package/dist/components/BitkitButton/BitkitButton.d.ts +4 -4
- package/dist/components/BitkitButton/BitkitButton.js +6 -5
- package/dist/components/BitkitButton/BitkitButton.js.map +1 -1
- package/dist/components/BitkitIconButton/BitkitIconButton.d.ts +5 -4
- package/dist/components/BitkitIconButton/BitkitIconButton.js +16 -23
- package/dist/components/BitkitIconButton/BitkitIconButton.js.map +1 -1
- package/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -111,7 +111,7 @@ Bitkit components use consistent prop names across the library:
|
|
|
111
111
|
- **A component that branches on `size` must resolve the default itself** — the recipe's `defaultVariants` applies in CSS, so an omitted `size` still reaches the component as `undefined`. Any JS that derives an icon or addon size from it then takes the wrong branch, and the element renders at one size with its icons at another. Give the prop a default where it is destructured (`size = 'lg'`), or resolve it at the point of use (`(props.size ?? 'lg') === 'lg' ? … : …`) when the component destructures in several places. Never rely on phrasing the condition so `undefined` happens to fall through — that is how `BitkitNumberInput`, `BitkitSelect`, `BitkitIconButton` and `BitkitCloseButton` each ended up mismatched.
|
|
112
112
|
The fallback must equal the recipe's `defaultVariants.size`; both are needed, since the recipe's is what raw Chakra and slot consumers get. And when a component calls its own recipe, pass the variant (`recipe({ size })`, never `recipe()`).
|
|
113
113
|
- **`state` prop replaces `disabled`/`loading`** — use `<BitkitButton state="disabled">`, not `<BitkitButton disabled>`. Values: `'disabled' | 'loading' | 'skeleton'`.
|
|
114
|
-
- **Say why a button is disabled with `disabledReason`** — on `BitkitButton` and `BitkitIconButton`, next to `state="disabled"`. The button shows it in a tooltip, stays focusable and is announced as disabled. Don't wrap a disabled button in `BitkitTooltip wrapTrigger`, and don't turn the icon button's label tooltip off for it:
|
|
114
|
+
- **Say why a button is disabled with `disabledReason`** — on `BitkitButton` and `BitkitIconButton`, next to `state="disabled"`. The button shows it in a tooltip, stays focusable and is announced as disabled. Pass it unconditionally (it only shows while disabled), so the button isn't remounted when it gets enabled. Don't wrap a disabled button in `BitkitTooltip wrapTrigger`, and don't turn the icon button's label tooltip off for it:
|
|
115
115
|
```tsx
|
|
116
116
|
<BitkitIconButton disabledReason="Only owners can delete" icon={IconTrash} label="Delete" state="disabled" />
|
|
117
117
|
```
|
|
@@ -11,10 +11,10 @@ export interface BitkitButtonAsButtonProps extends BitkitButtonCommonProps, Omit
|
|
|
11
11
|
asChild?: false;
|
|
12
12
|
children: string;
|
|
13
13
|
/**
|
|
14
|
-
* Why the button is disabled, shown in a tooltip on it
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* disabled button in `BitkitTooltip`.
|
|
14
|
+
* Why the button is disabled, shown in a tooltip on it while `state` is `"disabled"`. The button
|
|
15
|
+
* then stays focusable (`aria-disabled` instead of the native attribute), so the tooltip opens on
|
|
16
|
+
* hover and focus, and screen readers announce the reason. Pass it whether or not the button is
|
|
17
|
+
* disabled right now. Use it instead of wrapping a disabled button in `BitkitTooltip`.
|
|
18
18
|
*/
|
|
19
19
|
disabledReason?: string;
|
|
20
20
|
href?: undefined;
|
|
@@ -87,16 +87,16 @@ var BitkitButton = forwardRef((props, ref) => {
|
|
|
87
87
|
}) : anchorElement;
|
|
88
88
|
}
|
|
89
89
|
const { children, disabledReason, icon: _icon, state, suffixIcon: _suffixIcon, ...buttonRest } = props;
|
|
90
|
-
const
|
|
90
|
+
const hasReason = isDisabled && !!disabledReason;
|
|
91
91
|
const buttonElement = /* @__PURE__ */ jsx(Skeleton, {
|
|
92
92
|
asChild: true,
|
|
93
93
|
loading: isSkeleton,
|
|
94
94
|
children: /* @__PURE__ */ jsxs(Button, {
|
|
95
|
-
disabled: isDisabled && !
|
|
95
|
+
disabled: isDisabled && !hasReason,
|
|
96
96
|
loading: state === "loading",
|
|
97
97
|
ref,
|
|
98
98
|
...buttonRest,
|
|
99
|
-
...
|
|
99
|
+
...hasReason && focusableDisabledProps,
|
|
100
100
|
children: [
|
|
101
101
|
Icon && /* @__PURE__ */ jsx(Icon, {
|
|
102
102
|
marginInlineStart: "-4",
|
|
@@ -110,8 +110,9 @@ var BitkitButton = forwardRef((props, ref) => {
|
|
|
110
110
|
]
|
|
111
111
|
})
|
|
112
112
|
});
|
|
113
|
-
return
|
|
114
|
-
|
|
113
|
+
return disabledReason ? /* @__PURE__ */ jsx(BitkitTooltip, {
|
|
114
|
+
disabled: !isDisabled,
|
|
115
|
+
text: disabledReason,
|
|
115
116
|
children: buttonElement
|
|
116
117
|
}) : buttonElement;
|
|
117
118
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BitkitButton.js","names":["_Fragment"],"sources":["../../../lib/components/BitkitButton/BitkitButton.tsx"],"sourcesContent":["import { Button, type ButtonProps } from '@chakra-ui/react/button';\nimport { Skeleton } from '@chakra-ui/react/skeleton';\nimport { chakra, type HTMLChakraProps, type RecipeProps, useRecipe } from '@chakra-ui/react/styled-system';\nimport {\n Children,\n cloneElement,\n forwardRef,\n type KeyboardEvent,\n type MouseEvent,\n type ReactElement,\n type ReactNode,\n type Ref,\n} from 'react';\n\nimport { type BitkitIconComponent } from '../../icons';\nimport focusableDisabledProps from '../../utilities/focusableDisabledProps';\nimport BitkitTooltip from '../BitkitTooltip/BitkitTooltip';\n\ntype BitkitButtonOmittedFromButton =\n 'asChild' | 'children' | 'colorPalette' | 'disabled' | 'loading' | 'loadingText' | 'spinner' | 'spinnerPlacement';\n\ninterface BitkitButtonCommonProps {\n icon?: BitkitIconComponent;\n suffixIcon?: BitkitIconComponent;\n}\n\nexport interface BitkitButtonAsButtonProps\n extends BitkitButtonCommonProps, Omit<ButtonProps, BitkitButtonOmittedFromButton> {\n asChild?: false;\n children: string;\n /**\n * Why the button is disabled, shown in a tooltip on it
|
|
1
|
+
{"version":3,"file":"BitkitButton.js","names":["_Fragment"],"sources":["../../../lib/components/BitkitButton/BitkitButton.tsx"],"sourcesContent":["import { Button, type ButtonProps } from '@chakra-ui/react/button';\nimport { Skeleton } from '@chakra-ui/react/skeleton';\nimport { chakra, type HTMLChakraProps, type RecipeProps, useRecipe } from '@chakra-ui/react/styled-system';\nimport {\n Children,\n cloneElement,\n forwardRef,\n type KeyboardEvent,\n type MouseEvent,\n type ReactElement,\n type ReactNode,\n type Ref,\n} from 'react';\n\nimport { type BitkitIconComponent } from '../../icons';\nimport focusableDisabledProps from '../../utilities/focusableDisabledProps';\nimport BitkitTooltip from '../BitkitTooltip/BitkitTooltip';\n\ntype BitkitButtonOmittedFromButton =\n 'asChild' | 'children' | 'colorPalette' | 'disabled' | 'loading' | 'loadingText' | 'spinner' | 'spinnerPlacement';\n\ninterface BitkitButtonCommonProps {\n icon?: BitkitIconComponent;\n suffixIcon?: BitkitIconComponent;\n}\n\nexport interface BitkitButtonAsButtonProps\n extends BitkitButtonCommonProps, Omit<ButtonProps, BitkitButtonOmittedFromButton> {\n asChild?: false;\n children: string;\n /**\n * Why the button is disabled, shown in a tooltip on it while `state` is `\"disabled\"`. The button\n * then stays focusable (`aria-disabled` instead of the native attribute), so the tooltip opens on\n * hover and focus, and screen readers announce the reason. Pass it whether or not the button is\n * disabled right now. Use it instead of wrapping a disabled button in `BitkitTooltip`.\n */\n disabledReason?: string;\n href?: undefined;\n isExternal?: undefined;\n state?: 'disabled' | 'loading' | 'skeleton';\n}\n\nexport interface BitkitButtonAsAnchorProps\n extends BitkitButtonCommonProps, Omit<HTMLChakraProps<'a'>, 'children' | 'colorPalette'>, RecipeProps<'button'> {\n asChild?: false;\n children: string;\n href: string;\n isExternal?: boolean;\n state?: 'disabled' | 'skeleton';\n}\n\ninterface BitkitButtonAsChildProps extends BitkitButtonCommonProps, Omit<ButtonProps, BitkitButtonOmittedFromButton> {\n asChild: true;\n children: ReactElement;\n href?: undefined;\n isExternal?: undefined;\n state?: 'disabled' | 'loading' | 'skeleton';\n}\n\nexport type BitkitButtonProps = BitkitButtonAsButtonProps | BitkitButtonAsAnchorProps | BitkitButtonAsChildProps;\n\n// The forwarded ref is typed as HTMLElement to cover all three modes (button, anchor, slot/asChild),\n// since asChild can render any element (router Link, custom wrappers, etc.). Inner narrow casts\n// satisfy the wrapped Chakra components without affecting the consumer-visible ref shape.\nconst BitkitButton = forwardRef<HTMLElement, BitkitButtonProps>((props, ref) => {\n const buttonRecipe = useRecipe({ key: 'button' });\n const Icon = props.icon;\n const SuffixIcon = props.suffixIcon;\n // Resolve the size the recipe would apply before branching on it: an unset `size` reaches us as\n // `undefined` because the recipe's default lives in CSS, not on the prop. Keep the fallback equal\n // to `defaultVariants.size` in Button.recipe.ts.\n const iconSize = (props.size ?? 'lg') === 'lg' ? '24' : '16';\n const isDisabled = props.state === 'disabled';\n const isSkeleton = props.state === 'skeleton';\n\n // --- Slot mode (asChild) ---\n if (props.asChild) {\n const { asChild: _asChild, children, icon: _icon, state, suffixIcon: _suffixIcon, ...buttonRest } = props;\n const onlyChild = Children.only(children) as ReactElement<{ children?: ReactNode }>;\n const buttonElement = (\n <Button\n asChild\n disabled={isDisabled}\n loading={state === 'loading'}\n ref={ref as Ref<HTMLButtonElement>}\n {...buttonRest}\n >\n {cloneElement(\n onlyChild,\n undefined,\n <>\n {Icon && <Icon marginInlineStart=\"-4\" size={iconSize} />}\n {onlyChild.props.children}\n {SuffixIcon && <SuffixIcon marginInlineEnd=\"-4\" size={iconSize} />}\n </>,\n )}\n </Button>\n );\n return isSkeleton ? (\n <Skeleton asChild loading>\n {buttonElement}\n </Skeleton>\n ) : (\n buttonElement\n );\n }\n\n // --- Anchor mode (href) ---\n if (props.href !== undefined) {\n const {\n children,\n css,\n href,\n icon: _icon,\n isExternal,\n onClick,\n onKeyDown,\n rel,\n size,\n state: _state,\n suffixIcon: _suffixIcon,\n target,\n variant,\n ...anchorRest\n } = props;\n const effectiveTarget = isExternal ? '_blank' : target;\n const effectiveRel = isExternal ? (rel ? `${rel} noreferrer noopener` : 'noreferrer noopener') : rel;\n const handleClick = isDisabled\n ? (e: MouseEvent<HTMLAnchorElement>) => {\n e.preventDefault();\n e.stopPropagation();\n }\n : onClick;\n const handleKeyDown = isDisabled\n ? (e: KeyboardEvent<HTMLAnchorElement>) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n e.stopPropagation();\n }\n }\n : onKeyDown;\n const anchorElement = (\n <chakra.a\n ref={ref as Ref<HTMLAnchorElement>}\n {...anchorRest}\n aria-disabled={isDisabled || undefined}\n // A native <a> can't be `:disabled`, so mark `data-disabled` too: it's the styling/interaction hook the\n // recipe's `_hover`/`_active` conditions exclude (`:not(…, [data-disabled])`), while `aria-disabled` stays\n // for a11y. Without it, hover/active would still match a disabled anchor and override the disabled colors.\n css={[buttonRecipe({ size, variant }), css]}\n data-disabled={isDisabled || undefined}\n href={isDisabled ? undefined : href}\n onClick={handleClick}\n onKeyDown={handleKeyDown}\n rel={effectiveRel}\n target={effectiveTarget}\n >\n {Icon && <Icon marginInlineStart=\"-4\" size={iconSize} />}\n {children}\n {SuffixIcon && <SuffixIcon marginInlineEnd=\"-4\" size={iconSize} />}\n </chakra.a>\n );\n return isSkeleton ? (\n <Skeleton asChild loading>\n {anchorElement}\n </Skeleton>\n ) : (\n anchorElement\n );\n }\n\n // --- Button mode (default) ---\n const { children, disabledReason, icon: _icon, state, suffixIcon: _suffixIcon, ...buttonRest } = props;\n const hasReason = isDisabled && !!disabledReason;\n const buttonElement = (\n <Skeleton asChild loading={isSkeleton}>\n <Button\n disabled={isDisabled && !hasReason}\n loading={state === 'loading'}\n ref={ref as Ref<HTMLButtonElement>}\n {...buttonRest}\n {...(hasReason && focusableDisabledProps)}\n >\n {Icon && <Icon marginInlineStart=\"-4\" size={iconSize} />}\n {children}\n {SuffixIcon && <SuffixIcon marginInlineEnd=\"-4\" size={iconSize} />}\n </Button>\n </Skeleton>\n );\n // Keyed on the prop, not on `state`: wrapping the button only while it's disabled would remount it\n // whenever it gets enabled or disabled, and drop its focus.\n return disabledReason ? (\n <BitkitTooltip disabled={!isDisabled} text={disabledReason}>\n {buttonElement}\n </BitkitTooltip>\n ) : (\n buttonElement\n );\n});\n\nBitkitButton.displayName = 'BitkitButton';\n\nexport default BitkitButton;\n"],"mappings":";;;;;;;;AAgEA,IAAM,eAAe,YAA4C,OAAO,QAAQ;CAC9E,MAAM,eAAe,UAAU,EAAE,KAAK,SAAS,CAAC;CAChD,MAAM,OAAO,MAAM;CACnB,MAAM,aAAa,MAAM;CAIzB,MAAM,YAAY,MAAM,QAAQ,UAAU,OAAO,OAAO;CACxD,MAAM,aAAa,MAAM,UAAU;CACnC,MAAM,aAAa,MAAM,UAAU;CAGnC,IAAI,MAAM,SAAS;EACjB,MAAM,EAAE,SAAS,UAAU,UAAU,MAAM,OAAO,OAAO,YAAY,aAAa,GAAG,eAAe;EACpG,MAAM,YAAY,SAAS,KAAK,QAAQ;EACxC,MAAM,gBACJ,oBAAC,QAAD;GACE,SAAA;GACA,UAAU;GACV,SAAS,UAAU;GACd;GACL,GAAI;GAEH,UAAA,aACC,WACA,KAAA,GACA,qBAAAA,YAAA,EAAA,UAAA;IACG,QAAQ,oBAAC,MAAD;KAAM,mBAAkB;KAAK,MAAM;IAAW,CAAA;IACtD,UAAU,MAAM;IAChB,cAAc,oBAAC,YAAD;KAAY,iBAAgB;KAAK,MAAM;IAAW,CAAA;GACjE,EAAA,CAAA,CACJ;EACM,CAAA;EAEV,OAAO,aACL,oBAAC,UAAD;GAAU,SAAA;GAAQ,SAAA;GACf,UAAA;EACO,CAAA,IAEV;CAEJ;CAGA,IAAI,MAAM,SAAS,KAAA,GAAW;EAC5B,MAAM,EACJ,UACA,KACA,MACA,MAAM,OACN,YACA,SACA,WACA,KACA,MACA,OAAO,QACP,YAAY,aACZ,QACA,SACA,GAAG,eACD;EACJ,MAAM,kBAAkB,aAAa,WAAW;EAChD,MAAM,eAAe,aAAc,MAAM,GAAG,IAAI,wBAAwB,wBAAyB;EACjG,MAAM,cAAc,cACf,MAAqC;GACpC,EAAE,eAAe;GACjB,EAAE,gBAAgB;EACpB,IACA;EACJ,MAAM,gBAAgB,cACjB,MAAwC;GACvC,IAAI,EAAE,QAAQ,WAAW,EAAE,QAAQ,KAAK;IACtC,EAAE,eAAe;IACjB,EAAE,gBAAgB;GACpB;EACF,IACA;EACJ,MAAM,gBACJ,qBAAC,OAAO,GAAR;GACO;GACL,GAAI;GACJ,iBAAe,cAAc,KAAA;GAI7B,KAAK,CAAC,aAAa;IAAE;IAAM;GAAQ,CAAC,GAAG,GAAG;GAC1C,iBAAe,cAAc,KAAA;GAC7B,MAAM,aAAa,KAAA,IAAY;GAC/B,SAAS;GACT,WAAW;GACX,KAAK;GACL,QAAQ;GAbV,UAAA;IAeG,QAAQ,oBAAC,MAAD;KAAM,mBAAkB;KAAK,MAAM;IAAW,CAAA;IACtD;IACA,cAAc,oBAAC,YAAD;KAAY,iBAAgB;KAAK,MAAM;IAAW,CAAA;GACzD;;EAEZ,OAAO,aACL,oBAAC,UAAD;GAAU,SAAA;GAAQ,SAAA;GACf,UAAA;EACO,CAAA,IAEV;CAEJ;CAGA,MAAM,EAAE,UAAU,gBAAgB,MAAM,OAAO,OAAO,YAAY,aAAa,GAAG,eAAe;CACjG,MAAM,YAAY,cAAc,CAAC,CAAC;CAClC,MAAM,gBACJ,oBAAC,UAAD;EAAU,SAAA;EAAQ,SAAS;EACzB,UAAA,qBAAC,QAAD;GACE,UAAU,cAAc,CAAC;GACzB,SAAS,UAAU;GACd;GACL,GAAI;GACJ,GAAK,aAAa;GALpB,UAAA;IAOG,QAAQ,oBAAC,MAAD;KAAM,mBAAkB;KAAK,MAAM;IAAW,CAAA;IACtD;IACA,cAAc,oBAAC,YAAD;KAAY,iBAAgB;KAAK,MAAM;IAAW,CAAA;GAC3D;;CACA,CAAA;CAIZ,OAAO,iBACL,oBAAC,eAAD;EAAe,UAAU,CAAC;EAAY,MAAM;EACzC,UAAA;CACY,CAAA,IAEf;AAEJ,CAAC;AAED,aAAa,cAAc"}
|
|
@@ -17,10 +17,11 @@ interface BitkitIconButtonCommonProps {
|
|
|
17
17
|
interface BitkitIconButtonAsButtonProps extends BitkitIconButtonCommonProps, Omit<IconButtonProps, BitkitIconButtonOmittedFromButton> {
|
|
18
18
|
asChild?: false;
|
|
19
19
|
/**
|
|
20
|
-
* Why the button is disabled, shown in
|
|
21
|
-
* `
|
|
22
|
-
*
|
|
23
|
-
*
|
|
20
|
+
* Why the button is disabled, shown in its tooltip in place of the label while `state` is
|
|
21
|
+
* `"disabled"`. The button then stays focusable (`aria-disabled` instead of the native attribute),
|
|
22
|
+
* so the tooltip opens on hover and focus, and screen readers announce the reason after the
|
|
23
|
+
* label. Pass it whether or not the button is disabled right now. Use it instead of wrapping a
|
|
24
|
+
* disabled button in `BitkitTooltip`.
|
|
24
25
|
*/
|
|
25
26
|
disabledReason?: string;
|
|
26
27
|
href?: undefined;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import BitkitLabelTooltip from "../BitkitLabelTooltip/BitkitLabelTooltip.js";
|
|
2
2
|
import opensPopup from "../../utilities/opensPopup.js";
|
|
3
3
|
import focusableDisabledProps from "../../utilities/focusableDisabledProps.js";
|
|
4
|
-
import BitkitTooltip from "../BitkitTooltip/BitkitTooltip.js";
|
|
5
4
|
import { chakra, useRecipe } from "@chakra-ui/react/styled-system";
|
|
6
5
|
import { Children, cloneElement, forwardRef } from "react";
|
|
7
6
|
import { jsx } from "react/jsx-runtime";
|
|
@@ -88,29 +87,23 @@ var BitkitIconButton = forwardRef((props, ref) => {
|
|
|
88
87
|
}
|
|
89
88
|
const { disabledReason, icon: _icon, label, state, tooltipProps, ...buttonRest } = props;
|
|
90
89
|
const reason = isDisabled ? disabledReason : void 0;
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
children: /* @__PURE__ */ jsx(IconButton, {
|
|
95
|
-
"aria-label": label,
|
|
96
|
-
disabled: isDisabled && !reason,
|
|
97
|
-
loading: state === "loading",
|
|
98
|
-
ref,
|
|
99
|
-
...buttonRest,
|
|
100
|
-
...reason && focusableDisabledProps,
|
|
101
|
-
children: /* @__PURE__ */ jsx(Icon, { size: iconSize })
|
|
102
|
-
})
|
|
103
|
-
});
|
|
104
|
-
return reason ? /* @__PURE__ */ jsx(BitkitTooltip, {
|
|
105
|
-
disablePortal: tooltipProps?.disablePortal,
|
|
106
|
-
placement: tooltipProps?.placement,
|
|
107
|
-
text: reason,
|
|
108
|
-
children: buttonElement
|
|
109
|
-
}) : /* @__PURE__ */ jsx(BitkitLabelTooltip, {
|
|
110
|
-
text: label,
|
|
111
|
-
disabled: isPopupTrigger,
|
|
90
|
+
return /* @__PURE__ */ jsx(BitkitLabelTooltip, {
|
|
91
|
+
text: reason ?? label,
|
|
92
|
+
disabled: !reason && isPopupTrigger,
|
|
112
93
|
...tooltipProps,
|
|
113
|
-
children:
|
|
94
|
+
children: /* @__PURE__ */ jsx(Skeleton, {
|
|
95
|
+
asChild: true,
|
|
96
|
+
loading: isSkeleton,
|
|
97
|
+
children: /* @__PURE__ */ jsx(IconButton, {
|
|
98
|
+
"aria-label": label,
|
|
99
|
+
disabled: isDisabled && !reason,
|
|
100
|
+
loading: state === "loading",
|
|
101
|
+
ref,
|
|
102
|
+
...buttonRest,
|
|
103
|
+
...reason && focusableDisabledProps,
|
|
104
|
+
children: /* @__PURE__ */ jsx(Icon, { size: iconSize })
|
|
105
|
+
})
|
|
106
|
+
})
|
|
114
107
|
});
|
|
115
108
|
});
|
|
116
109
|
BitkitIconButton.displayName = "BitkitIconButton";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BitkitIconButton.js","names":[],"sources":["../../../lib/components/BitkitIconButton/BitkitIconButton.tsx"],"sourcesContent":["import { IconButton, type IconButtonProps } from '@chakra-ui/react/button';\nimport { Skeleton } from '@chakra-ui/react/skeleton';\nimport { chakra, type HTMLChakraProps, type RecipeProps, useRecipe } from '@chakra-ui/react/styled-system';\nimport {\n Children,\n cloneElement,\n forwardRef,\n type KeyboardEvent,\n type MouseEvent,\n type ReactElement,\n type Ref,\n} from 'react';\n\nimport { type BitkitIconComponent } from '../../icons';\nimport focusableDisabledProps from '../../utilities/focusableDisabledProps';\nimport opensPopup from '../../utilities/opensPopup';\nimport BitkitLabelTooltip, { type BitkitLabelTooltipProps } from '../BitkitLabelTooltip/BitkitLabelTooltip';\
|
|
1
|
+
{"version":3,"file":"BitkitIconButton.js","names":[],"sources":["../../../lib/components/BitkitIconButton/BitkitIconButton.tsx"],"sourcesContent":["import { IconButton, type IconButtonProps } from '@chakra-ui/react/button';\nimport { Skeleton } from '@chakra-ui/react/skeleton';\nimport { chakra, type HTMLChakraProps, type RecipeProps, useRecipe } from '@chakra-ui/react/styled-system';\nimport {\n Children,\n cloneElement,\n forwardRef,\n type KeyboardEvent,\n type MouseEvent,\n type ReactElement,\n type Ref,\n} from 'react';\n\nimport { type BitkitIconComponent } from '../../icons';\nimport focusableDisabledProps from '../../utilities/focusableDisabledProps';\nimport opensPopup from '../../utilities/opensPopup';\nimport BitkitLabelTooltip, { type BitkitLabelTooltipProps } from '../BitkitLabelTooltip/BitkitLabelTooltip';\n\ntype BitkitIconButtonOmittedFromButton =\n | 'aria-label'\n | 'asChild'\n | 'children'\n | 'colorPalette'\n | 'disabled'\n | 'loading'\n | 'loadingText'\n | 'spinner'\n | 'spinnerPlacement';\n\ninterface BitkitIconButtonCommonProps {\n icon: BitkitIconComponent;\n label: string;\n /**\n * Overrides for the label tooltip. It is off by default when this control opens a popup — a menu,\n * select or popover trigger — where the panel is the affordance and a tooltip would hover over it.\n * Pass `{ disabled: false }` to bring it back. The accessible name comes from `label` either way.\n */\n tooltipProps?: Partial<Omit<BitkitLabelTooltipProps, 'children' | 'text'>>;\n}\n\ninterface BitkitIconButtonAsButtonProps\n extends BitkitIconButtonCommonProps, Omit<IconButtonProps, BitkitIconButtonOmittedFromButton> {\n asChild?: false;\n /**\n * Why the button is disabled, shown in its tooltip in place of the label while `state` is\n * `\"disabled\"`. The button then stays focusable (`aria-disabled` instead of the native attribute),\n * so the tooltip opens on hover and focus, and screen readers announce the reason after the\n * label. Pass it whether or not the button is disabled right now. Use it instead of wrapping a\n * disabled button in `BitkitTooltip`.\n */\n disabledReason?: string;\n href?: undefined;\n isExternal?: undefined;\n state?: 'disabled' | 'loading' | 'skeleton';\n}\n\ninterface BitkitIconButtonAsAnchorProps\n extends\n BitkitIconButtonCommonProps,\n Omit<HTMLChakraProps<'a'>, 'aria-label' | 'children' | 'colorPalette'>,\n RecipeProps<'button'> {\n asChild?: false;\n href: string;\n isExternal?: boolean;\n state?: 'disabled' | 'skeleton';\n}\n\ninterface BitkitIconButtonAsChildProps\n extends BitkitIconButtonCommonProps, Omit<IconButtonProps, BitkitIconButtonOmittedFromButton> {\n asChild: true;\n children: ReactElement;\n href?: undefined;\n isExternal?: undefined;\n state?: 'disabled' | 'loading' | 'skeleton';\n}\n\nexport type BitkitIconButtonProps =\n BitkitIconButtonAsButtonProps | BitkitIconButtonAsAnchorProps | BitkitIconButtonAsChildProps;\n\n// The forwarded ref is typed as HTMLElement to cover all three modes (button, anchor, slot/asChild),\n// since asChild can render any element (router Link, custom wrappers, etc.). Inner narrow casts\n// satisfy the wrapped Chakra components without affecting the consumer-visible ref shape.\nconst BitkitIconButton = forwardRef<HTMLElement, BitkitIconButtonProps>((props, ref) => {\n const buttonRecipe = useRecipe({ key: 'button' });\n const Icon = props.icon;\n // An unset `size` reaches us as `undefined` — the `button` recipe's `lg` default lives in CSS,\n // not on the prop — so resolve it here rather than letting it fall to the 16px branch.\n const iconSize = (props.size ?? 'lg') === 'lg' ? '24' : '16';\n const isDisabled = props.state === 'disabled';\n const isSkeleton = props.state === 'skeleton';\n const isPopupTrigger = opensPopup(props);\n\n // --- Slot mode (asChild) ---\n if (props.asChild) {\n const { asChild: _asChild, children, icon: _icon, label, state, tooltipProps, ...buttonRest } = props;\n const onlyChild = Children.only(children) as ReactElement<{ 'aria-label'?: string }>;\n const iconButtonElement = (\n <IconButton\n asChild\n aria-label={label}\n disabled={isDisabled}\n loading={state === 'loading'}\n ref={ref as Ref<HTMLButtonElement>}\n {...buttonRest}\n >\n {cloneElement(onlyChild, { 'aria-label': onlyChild.props['aria-label'] ?? label }, <Icon size={iconSize} />)}\n </IconButton>\n );\n return (\n <BitkitLabelTooltip text={label} disabled={isPopupTrigger} {...tooltipProps}>\n {isSkeleton ? (\n <Skeleton asChild loading>\n {iconButtonElement}\n </Skeleton>\n ) : (\n iconButtonElement\n )}\n </BitkitLabelTooltip>\n );\n }\n\n // --- Anchor mode (href) ---\n if (props.href !== undefined) {\n const {\n css,\n href,\n icon: _icon,\n isExternal,\n label,\n onClick,\n onKeyDown,\n rel,\n size,\n state: _state,\n target,\n tooltipProps,\n variant,\n ...anchorRest\n } = props;\n const effectiveTarget = isExternal ? '_blank' : target;\n const effectiveRel = isExternal ? (rel ? `${rel} noreferrer noopener` : 'noreferrer noopener') : rel;\n const handleClick = isDisabled\n ? (e: MouseEvent<HTMLAnchorElement>) => {\n e.preventDefault();\n e.stopPropagation();\n }\n : onClick;\n const handleKeyDown = isDisabled\n ? (e: KeyboardEvent<HTMLAnchorElement>) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n e.stopPropagation();\n }\n }\n : onKeyDown;\n const anchorElement = (\n <chakra.a\n ref={ref as Ref<HTMLAnchorElement>}\n {...anchorRest}\n aria-disabled={isDisabled || undefined}\n aria-label={label}\n css={[buttonRecipe({ size, variant }), { paddingBlock: 0, paddingInline: 0 }, css]}\n href={isDisabled ? undefined : href}\n onClick={handleClick}\n onKeyDown={handleKeyDown}\n rel={effectiveRel}\n target={effectiveTarget}\n >\n <Icon size={iconSize} />\n </chakra.a>\n );\n return (\n <BitkitLabelTooltip text={label} disabled={isPopupTrigger} {...tooltipProps}>\n {isSkeleton ? (\n <Skeleton asChild loading>\n {anchorElement}\n </Skeleton>\n ) : (\n anchorElement\n )}\n </BitkitLabelTooltip>\n );\n }\n\n // --- Button mode (default) ---\n const { disabledReason, icon: _icon, label, state, tooltipProps, ...buttonRest } = props;\n const reason = isDisabled ? disabledReason : undefined;\n // One tooltip whose text switches, rather than a second tooltip component: swapping components\n // when the state changes would remount the button and drop its focus. The label is still\n // announced as the button's name, and a popup trigger shows the reason since the popup can't open.\n return (\n <BitkitLabelTooltip text={reason ?? label} disabled={!reason && isPopupTrigger} {...tooltipProps}>\n <Skeleton asChild loading={isSkeleton}>\n <IconButton\n aria-label={label}\n disabled={isDisabled && !reason}\n loading={state === 'loading'}\n ref={ref as Ref<HTMLButtonElement>}\n {...buttonRest}\n {...(reason && focusableDisabledProps)}\n >\n <Icon size={iconSize} />\n </IconButton>\n </Skeleton>\n </BitkitLabelTooltip>\n );\n});\n\nBitkitIconButton.displayName = 'BitkitIconButton';\n\nexport default BitkitIconButton;\n"],"mappings":";;;;;;;;;AAkFA,IAAM,mBAAmB,YAAgD,OAAO,QAAQ;CACtF,MAAM,eAAe,UAAU,EAAE,KAAK,SAAS,CAAC;CAChD,MAAM,OAAO,MAAM;CAGnB,MAAM,YAAY,MAAM,QAAQ,UAAU,OAAO,OAAO;CACxD,MAAM,aAAa,MAAM,UAAU;CACnC,MAAM,aAAa,MAAM,UAAU;CACnC,MAAM,iBAAiB,WAAW,KAAK;CAGvC,IAAI,MAAM,SAAS;EACjB,MAAM,EAAE,SAAS,UAAU,UAAU,MAAM,OAAO,OAAO,OAAO,cAAc,GAAG,eAAe;EAChG,MAAM,YAAY,SAAS,KAAK,QAAQ;EACxC,MAAM,oBACJ,oBAAC,YAAD;GACE,SAAA;GACA,cAAY;GACZ,UAAU;GACV,SAAS,UAAU;GACd;GACL,GAAI;GAEH,UAAA,aAAa,WAAW,EAAE,cAAc,UAAU,MAAM,iBAAiB,MAAM,GAAG,oBAAC,MAAD,EAAM,MAAM,SAAW,CAAA,CAAC;EACjG,CAAA;EAEd,OACE,oBAAC,oBAAD;GAAoB,MAAM;GAAO,UAAU;GAAgB,GAAI;GAC5D,UAAA,aACC,oBAAC,UAAD;IAAU,SAAA;IAAQ,SAAA;IACf,UAAA;GACO,CAAA,IAEV;EAEgB,CAAA;CAExB;CAGA,IAAI,MAAM,SAAS,KAAA,GAAW;EAC5B,MAAM,EACJ,KACA,MACA,MAAM,OACN,YACA,OACA,SACA,WACA,KACA,MACA,OAAO,QACP,QACA,cACA,SACA,GAAG,eACD;EACJ,MAAM,kBAAkB,aAAa,WAAW;EAChD,MAAM,eAAe,aAAc,MAAM,GAAG,IAAI,wBAAwB,wBAAyB;EACjG,MAAM,cAAc,cACf,MAAqC;GACpC,EAAE,eAAe;GACjB,EAAE,gBAAgB;EACpB,IACA;EACJ,MAAM,gBAAgB,cACjB,MAAwC;GACvC,IAAI,EAAE,QAAQ,WAAW,EAAE,QAAQ,KAAK;IACtC,EAAE,eAAe;IACjB,EAAE,gBAAgB;GACpB;EACF,IACA;EACJ,MAAM,gBACJ,oBAAC,OAAO,GAAR;GACO;GACL,GAAI;GACJ,iBAAe,cAAc,KAAA;GAC7B,cAAY;GACZ,KAAK;IAAC,aAAa;KAAE;KAAM;IAAQ,CAAC;IAAG;KAAE,cAAc;KAAG,eAAe;IAAE;IAAG;GAAG;GACjF,MAAM,aAAa,KAAA,IAAY;GAC/B,SAAS;GACT,WAAW;GACX,KAAK;GACL,QAAQ;GAER,UAAA,oBAAC,MAAD,EAAM,MAAM,SAAW,CAAA;EACf,CAAA;EAEZ,OACE,oBAAC,oBAAD;GAAoB,MAAM;GAAO,UAAU;GAAgB,GAAI;GAC5D,UAAA,aACC,oBAAC,UAAD;IAAU,SAAA;IAAQ,SAAA;IACf,UAAA;GACO,CAAA,IAEV;EAEgB,CAAA;CAExB;CAGA,MAAM,EAAE,gBAAgB,MAAM,OAAO,OAAO,OAAO,cAAc,GAAG,eAAe;CACnF,MAAM,SAAS,aAAa,iBAAiB,KAAA;CAI7C,OACE,oBAAC,oBAAD;EAAoB,MAAM,UAAU;EAAO,UAAU,CAAC,UAAU;EAAgB,GAAI;EAClF,UAAA,oBAAC,UAAD;GAAU,SAAA;GAAQ,SAAS;GACzB,UAAA,oBAAC,YAAD;IACE,cAAY;IACZ,UAAU,cAAc,CAAC;IACzB,SAAS,UAAU;IACd;IACL,GAAI;IACJ,GAAK,UAAU;IAEf,UAAA,oBAAC,MAAD,EAAM,MAAM,SAAW,CAAA;GACb,CAAA;EACJ,CAAA;CACQ,CAAA;AAExB,CAAC;AAED,iBAAiB,cAAc"}
|