@bitrise/bitkit-v2 0.3.508 → 0.3.509
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/BitkitOverflowTooltip/BitkitOverflowTooltip.d.ts +4 -4
- package/dist/components/BitkitOverflowTooltip/BitkitOverflowTooltip.js +11 -4
- package/dist/components/BitkitOverflowTooltip/BitkitOverflowTooltip.js.map +1 -1
- package/dist/components/BitkitPageSidebar/BitkitPageSidebar.d.ts +7 -0
- package/dist/components/BitkitPageSidebar/BitkitPageSidebar.js +19 -6
- package/dist/components/BitkitPageSidebar/BitkitPageSidebar.js.map +1 -1
- package/dist/components/BitkitTooltip/BitkitTooltip.d.ts +3 -3
- package/dist/components/BitkitTooltip/BitkitTooltip.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 `
|
|
114
|
+
- **Say why a button is disabled with `disabledReason`** — on `BitkitButton`, `BitkitIconButton` and `BitkitPageSidebar.Item`, next to `state="disabled"`. The component shows it in a tooltip, stays focusable and is announced as disabled. Don't wrap a disabled button or sidebar item in `BitkitTooltip wrapTrigger` (or a focusable `Box`), 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
|
```
|
|
@@ -23,9 +23,9 @@ export type BitkitOverflowTooltipProps = {
|
|
|
23
23
|
* clipping container by definition, and an inline tooltip would be cut off by the very `overflow`
|
|
24
24
|
* that truncated the text. Dialogs turn the portal off through `BitkitPortalContext`;
|
|
25
25
|
* `disablePortal` is the manual override.
|
|
26
|
+
*
|
|
27
|
+
* It forwards its ref, and any props an outer `asChild` trigger hands it, to the element it wraps,
|
|
28
|
+
* so it can itself sit inside a `BitkitTooltip` with both opening on the same element.
|
|
26
29
|
*/
|
|
27
|
-
declare const BitkitOverflowTooltip:
|
|
28
|
-
({ children, disablePortal, disabled, placement, text, }: BitkitOverflowTooltipProps): import('react').JSX.Element;
|
|
29
|
-
displayName: string;
|
|
30
|
-
};
|
|
30
|
+
declare const BitkitOverflowTooltip: import('react').ForwardRefExoticComponent<BitkitOverflowTooltipProps & import('react').RefAttributes<HTMLElement>>;
|
|
31
31
|
export default BitkitOverflowTooltip;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import BitkitPortalContext from "../../utilities/BitkitPortalContext.js";
|
|
2
2
|
import { rem } from "../../theme/themeUtils.js";
|
|
3
|
-
import { useContext, useRef } from "react";
|
|
3
|
+
import { forwardRef, useContext, useMemo, useRef } from "react";
|
|
4
4
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
5
|
import { Portal } from "@chakra-ui/react/portal";
|
|
6
6
|
import { Tooltip } from "@chakra-ui/react/tooltip";
|
|
7
|
+
import { mergeRefs } from "@chakra-ui/react";
|
|
7
8
|
//#region lib/components/BitkitOverflowTooltip/BitkitOverflowTooltip.tsx
|
|
8
9
|
/**
|
|
9
10
|
* Reveals text that its container had to truncate. Light, roomy (up to 800px) and plain — it repeats
|
|
@@ -13,10 +14,15 @@ import { Tooltip } from "@chakra-ui/react/tooltip";
|
|
|
13
14
|
* clipping container by definition, and an inline tooltip would be cut off by the very `overflow`
|
|
14
15
|
* that truncated the text. Dialogs turn the portal off through `BitkitPortalContext`;
|
|
15
16
|
* `disablePortal` is the manual override.
|
|
17
|
+
*
|
|
18
|
+
* It forwards its ref, and any props an outer `asChild` trigger hands it, to the element it wraps,
|
|
19
|
+
* so it can itself sit inside a `BitkitTooltip` with both opening on the same element.
|
|
16
20
|
*/
|
|
17
|
-
var BitkitOverflowTooltip = (
|
|
21
|
+
var BitkitOverflowTooltip = forwardRef((props, ref) => {
|
|
22
|
+
const { children, disablePortal, disabled, placement = "right", text, ...outerTriggerProps } = props;
|
|
18
23
|
const { disablePortal: disablePortalFromContext } = useContext(BitkitPortalContext);
|
|
19
24
|
const triggerRef = useRef(null);
|
|
25
|
+
const setTriggerRef = useMemo(() => mergeRefs(triggerRef, ref), [ref]);
|
|
20
26
|
return /* @__PURE__ */ jsxs(Tooltip.Root, {
|
|
21
27
|
disabled,
|
|
22
28
|
paddingSize: "sm",
|
|
@@ -31,8 +37,9 @@ var BitkitOverflowTooltip = ({ children, disablePortal, disabled, placement = "r
|
|
|
31
37
|
textStyle: "sm/regular",
|
|
32
38
|
variant: "light",
|
|
33
39
|
children: [/* @__PURE__ */ jsx(Tooltip.Trigger, {
|
|
34
|
-
ref:
|
|
40
|
+
ref: setTriggerRef,
|
|
35
41
|
asChild: true,
|
|
42
|
+
...outerTriggerProps,
|
|
36
43
|
children
|
|
37
44
|
}), /* @__PURE__ */ jsx(Portal, {
|
|
38
45
|
disabled: disablePortal || disablePortalFromContext,
|
|
@@ -42,7 +49,7 @@ var BitkitOverflowTooltip = ({ children, disablePortal, disabled, placement = "r
|
|
|
42
49
|
}) })
|
|
43
50
|
})]
|
|
44
51
|
});
|
|
45
|
-
};
|
|
52
|
+
});
|
|
46
53
|
BitkitOverflowTooltip.displayName = "BitkitOverflowTooltip";
|
|
47
54
|
//#endregion
|
|
48
55
|
export { BitkitOverflowTooltip as default };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BitkitOverflowTooltip.js","names":[],"sources":["../../../lib/components/BitkitOverflowTooltip/BitkitOverflowTooltip.tsx"],"sourcesContent":["
|
|
1
|
+
{"version":3,"file":"BitkitOverflowTooltip.js","names":[],"sources":["../../../lib/components/BitkitOverflowTooltip/BitkitOverflowTooltip.tsx"],"sourcesContent":["// eslint-disable-next-line no-restricted-imports\nimport { mergeRefs } from '@chakra-ui/react';\nimport { Portal } from '@chakra-ui/react/portal';\nimport { Tooltip, type TooltipRootProps } from '@chakra-ui/react/tooltip';\nimport { forwardRef, type ReactNode, type Ref, useContext, useMemo, useRef } from 'react';\n\nimport { rem } from '../../theme/themeUtils';\nimport BitkitPortalContext from '../../utilities/BitkitPortalContext';\n\nexport type BitkitOverflowTooltipProps = {\n /** The truncated element the tooltip is anchored to. Rendered via `asChild`, so it must accept a ref. */\n children: ReactNode;\n /** Render the trigger without a tooltip — pass `true` when the text isn't actually truncated. */\n disabled?: TooltipRootProps['disabled'];\n /**\n * Render the tooltip inline instead of in a portal. Only needed when it must stay inside its\n * parent's DOM subtree — a dialog already sets this for you.\n */\n disablePortal?: boolean;\n /** Where the tooltip sits relative to the trigger (default `right`). It flips if space is short. */\n placement?: NonNullable<TooltipRootProps['positioning']>['placement'];\n /** The full, untruncated text. */\n text: string;\n};\n\n/**\n * Reveals text that its container had to truncate. Light, roomy (up to 800px) and plain — it repeats\n * content the user can already partly see, so it carries no icon, link or action.\n *\n * The content renders in a portal, which matters more here than elsewhere: the trigger lives in a\n * clipping container by definition, and an inline tooltip would be cut off by the very `overflow`\n * that truncated the text. Dialogs turn the portal off through `BitkitPortalContext`;\n * `disablePortal` is the manual override.\n *\n * It forwards its ref, and any props an outer `asChild` trigger hands it, to the element it wraps,\n * so it can itself sit inside a `BitkitTooltip` with both opening on the same element.\n */\nconst BitkitOverflowTooltip = forwardRef<HTMLElement, BitkitOverflowTooltipProps>((props, ref) => {\n const { children, disablePortal, disabled, placement = 'right', text, ...outerTriggerProps } = props;\n const { disablePortal: disablePortalFromContext } = useContext(BitkitPortalContext);\n\n // Anchor positioning to the trigger node by ref rather than relying on Zag's\n // id/data-scope lookup. When the trigger is also wrapped by another `asChild`\n // trigger (e.g. Menu.Trigger), the outer trigger's id and data-scope/data-part\n // overwrite ours on the shared node, so `getTriggerEl()` can't find it and the\n // tooltip loses its anchor. `getAnchorElement` takes precedence over the id\n // lookup, so this is safe in the standalone case too.\n const triggerRef = useRef<HTMLElement>(null);\n const setTriggerRef = useMemo(() => mergeRefs<HTMLElement>(triggerRef, ref), [ref]);\n\n return (\n <Tooltip.Root\n disabled={disabled}\n paddingSize=\"sm\"\n positioning={{ getAnchorElement: () => triggerRef.current, placement, offset: { mainAxis: 4, crossAxis: 0 } }}\n textStyle=\"sm/regular\"\n variant=\"light\"\n >\n <Tooltip.Trigger ref={setTriggerRef as Ref<HTMLButtonElement>} asChild {...outerTriggerProps}>\n {children}\n </Tooltip.Trigger>\n <Portal disabled={disablePortal || disablePortalFromContext}>\n <Tooltip.Positioner>\n <Tooltip.Content css={{ maxWidth: rem(800) }}>{text}</Tooltip.Content>\n </Tooltip.Positioner>\n </Portal>\n </Tooltip.Root>\n );\n});\n\nBitkitOverflowTooltip.displayName = 'BitkitOverflowTooltip';\n\nexport default BitkitOverflowTooltip;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAqCA,IAAM,wBAAwB,YAAqD,OAAO,QAAQ;CAChG,MAAM,EAAE,UAAU,eAAe,UAAU,YAAY,SAAS,MAAM,GAAG,sBAAsB;CAC/F,MAAM,EAAE,eAAe,6BAA6B,WAAW,mBAAmB;CAQlF,MAAM,aAAa,OAAoB,IAAI;CAC3C,MAAM,gBAAgB,cAAc,UAAuB,YAAY,GAAG,GAAG,CAAC,GAAG,CAAC;CAElF,OACE,qBAAC,QAAQ,MAAT;EACY;EACV,aAAY;EACZ,aAAa;GAAE,wBAAwB,WAAW;GAAS;GAAW,QAAQ;IAAE,UAAU;IAAG,WAAW;GAAE;EAAE;EAC5G,WAAU;EACV,SAAQ;EALV,UAAA,CAOE,oBAAC,QAAQ,SAAT;GAAiB,KAAK;GAAyC,SAAA;GAAQ,GAAI;GACxE;EACc,CAAA,GACjB,oBAAC,QAAD;GAAQ,UAAU,iBAAiB;GACjC,UAAA,oBAAC,QAAQ,YAAT,EAAA,UACE,oBAAC,QAAQ,SAAT;IAAiB,KAAK,EAAE,UAAU,IAAI,GAAG,EAAE;IAAI,UAAA;GAAsB,CAAA,EACnD,CAAA;EACd,CAAA,CACI;;AAElB,CAAC;AAED,sBAAsB,cAAc"}
|
|
@@ -30,6 +30,13 @@ interface BitkitPageSidebarItemCommonProps {
|
|
|
30
30
|
* an ellipsis, and the overflow tooltip needs the full text to reveal.
|
|
31
31
|
*/
|
|
32
32
|
children: string;
|
|
33
|
+
/**
|
|
34
|
+
* Why the item is unavailable, shown in a tooltip on it while `state` is `"disabled"`. The item
|
|
35
|
+
* then stays focusable and is announced as disabled (`aria-disabled`), so the tooltip opens on
|
|
36
|
+
* hover and focus. It still neither navigates nor activates. Use it instead of wrapping a disabled
|
|
37
|
+
* item in `BitkitTooltip`.
|
|
38
|
+
*/
|
|
39
|
+
disabledReason?: string;
|
|
33
40
|
icon?: BitkitIconComponent;
|
|
34
41
|
/**
|
|
35
42
|
* Marks the item as the current page. Mutually exclusive with `state="disabled"` — you cannot be
|
|
@@ -2,6 +2,8 @@ import { withSubComponents } from "../../utilities/withSubComponents.js";
|
|
|
2
2
|
import IconChevronLeft from "../../icons/IconChevronLeft.js";
|
|
3
3
|
import BitkitCloseButton from "../BitkitCloseButton/BitkitCloseButton.js";
|
|
4
4
|
import BitkitControlButton from "../BitkitControlButton/BitkitControlButton.js";
|
|
5
|
+
import focusableDisabledProps from "../../utilities/focusableDisabledProps.js";
|
|
6
|
+
import BitkitTooltip from "../BitkitTooltip/BitkitTooltip.js";
|
|
5
7
|
import BitkitGroupHeading from "../BitkitGroupHeading/BitkitGroupHeading.js";
|
|
6
8
|
import useResponsive from "../../hooks/useResponsive.js";
|
|
7
9
|
import BitkitOverflowTooltip from "../BitkitOverflowTooltip/BitkitOverflowTooltip.js";
|
|
@@ -101,8 +103,9 @@ var BitkitPageSidebar = forwardRef((props, ref) => {
|
|
|
101
103
|
});
|
|
102
104
|
BitkitPageSidebar.displayName = "BitkitPageSidebar";
|
|
103
105
|
var BitkitPageSidebarItem = forwardRef((props, ref) => {
|
|
104
|
-
const { badge, children, icon: Icon, selected, state, suffixIcon: SuffixIcon, ...rest } = props;
|
|
106
|
+
const { badge, children, disabledReason, icon: Icon, selected, state, suffixIcon: SuffixIcon, ...rest } = props;
|
|
105
107
|
const isDisabled = state === "disabled";
|
|
108
|
+
const hasReason = isDisabled && !!disabledReason;
|
|
106
109
|
const isSelected = selected === true && !isDisabled;
|
|
107
110
|
const styles = useSlotRecipe({ key: "pageSidebar" })({
|
|
108
111
|
isDisabled,
|
|
@@ -140,10 +143,15 @@ var BitkitPageSidebarItem = forwardRef((props, ref) => {
|
|
|
140
143
|
css: styles.selectionMarker
|
|
141
144
|
})
|
|
142
145
|
] });
|
|
143
|
-
const withTooltip = (row) => /* @__PURE__ */ jsx(
|
|
144
|
-
disabled: !
|
|
145
|
-
|
|
146
|
-
|
|
146
|
+
const withTooltip = (row) => /* @__PURE__ */ jsx(BitkitTooltip, {
|
|
147
|
+
disabled: !hasReason,
|
|
148
|
+
placement: "right",
|
|
149
|
+
text: disabledReason ?? "",
|
|
150
|
+
children: /* @__PURE__ */ jsx(BitkitOverflowTooltip, {
|
|
151
|
+
disabled: !isTruncated || hasReason,
|
|
152
|
+
text: children,
|
|
153
|
+
children: row
|
|
154
|
+
})
|
|
147
155
|
});
|
|
148
156
|
if (rest.href !== void 0) {
|
|
149
157
|
const { className, href, onClick, onKeyDown, ...linkRest } = rest;
|
|
@@ -167,6 +175,10 @@ var BitkitPageSidebarItem = forwardRef((props, ref) => {
|
|
|
167
175
|
href: isDisabled ? void 0 : href,
|
|
168
176
|
onClick: handleClick,
|
|
169
177
|
onKeyDown: handleKeyDown,
|
|
178
|
+
...hasReason && {
|
|
179
|
+
role: "link",
|
|
180
|
+
tabIndex: 0
|
|
181
|
+
},
|
|
170
182
|
children: itemContent
|
|
171
183
|
}));
|
|
172
184
|
}
|
|
@@ -177,8 +189,9 @@ var BitkitPageSidebarItem = forwardRef((props, ref) => {
|
|
|
177
189
|
"aria-current": isSelected ? "page" : void 0,
|
|
178
190
|
className: className ? `group ${className}` : "group",
|
|
179
191
|
css: styles.item,
|
|
180
|
-
disabled: isDisabled,
|
|
192
|
+
disabled: isDisabled && !hasReason,
|
|
181
193
|
type: "button",
|
|
194
|
+
...hasReason && focusableDisabledProps,
|
|
182
195
|
children: itemContent
|
|
183
196
|
}));
|
|
184
197
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BitkitPageSidebar.js","names":["_Fragment"],"sources":["../../../lib/components/BitkitPageSidebar/BitkitPageSidebar.tsx"],"sourcesContent":["import { Box, type BoxProps } from '@chakra-ui/react/box';\nimport { Drawer } from '@chakra-ui/react/drawer';\nimport { Portal } from '@chakra-ui/react/portal';\nimport { Separator, type SeparatorProps } from '@chakra-ui/react/separator';\nimport { chakra, type HTMLChakraProps, useChakraContext, useSlotRecipe } from '@chakra-ui/react/styled-system';\nimport { Text } from '@chakra-ui/react/text';\nimport { forwardRef, type KeyboardEvent, type MouseEvent, type ReactNode, type Ref } from 'react';\n\nimport useResponsive from '../../hooks/useResponsive';\nimport { type BitkitIconComponent } from '../../icons';\nimport IconChevronLeft from '../../icons/IconChevronLeft';\nimport { type BitkitColorToken } from '../../theme/themeUtils';\nimport useIsTruncated from '../../utilities/useIsTruncated';\nimport { withSubComponents } from '../../utilities/withSubComponents';\nimport BitkitCloseButton from '../BitkitCloseButton/BitkitCloseButton';\nimport BitkitControlButton from '../BitkitControlButton/BitkitControlButton';\nimport BitkitGroupHeading, { type BitkitGroupHeadingProps } from '../BitkitGroupHeading/BitkitGroupHeading';\nimport BitkitOverflowTooltip from '../BitkitOverflowTooltip/BitkitOverflowTooltip';\n\n// The 16 and 24 icons are separate drawings, not one scaled to two sizes, so the breakpoint swap\n// cannot live in the recipe — both are rendered and one is hidden. A `useBreakpointValue` would\n// render a single icon, but it only re-reads on mount: resize a live page across `tablet` and the\n// row resizes underneath an icon that keeps its old size.\nconst showAtDesktop = { display: { base: 'none', tablet: 'block' } };\nconst showAtMobile = { display: { base: 'block', tablet: 'none' } };\n\n// ----- BitkitPageSidebar (Root) -----\n\nexport interface BitkitPageSidebarProps extends BoxProps {\n children: ReactNode;\n /**\n * Header of the mobile drawer panel, naming what the navigation belongs to — pass a\n * `BitkitPageSidebar.DrawerHeader`. Only rendered in drawer mode.\n */\n drawerHeader?: ReactNode;\n /**\n * Mobile drawer visibility. Controlled, because the page header's menu button is what opens it.\n * Passing `onOpenChange` is what opts the sidebar into the drawer below `tablet`; without it the\n * sidebar stays in the flow at full width there. Both are ignored from `tablet` up.\n *\n * Style props and `css` apply to the sidebar beside the page only — the drawer panel sizes\n * itself, so a desktop `width` or padding never reaches the navigation inside it.\n */\n onOpenChange?: (open: boolean) => void;\n open?: boolean;\n}\n\n/**\n * Page-level navigation sidebar. It expects three children, in this order:\n *\n * ```tsx\n * <BitkitPageSidebar>\n * <BitkitPageSidebar.Title>Title</BitkitPageSidebar.Title>\n * <BitkitPageSidebar.Body>\n * <BitkitPageSidebar.Item icon={IconWorkflow}>Workflows</BitkitPageSidebar.Item>\n * </BitkitPageSidebar.Body>\n * <BitkitPageSidebar.Footer>…</BitkitPageSidebar.Footer>\n * </BitkitPageSidebar>\n * ```\n *\n * `Title` and `Footer` are optional. The sidebar scrolls as a single block: when the navigation is\n * taller than the viewport the whole thing — footer included — grows and scrolls with the page,\n * which is why `Body` keeps a minimum gap above the footer so the two blocks never touch.\n *\n * The `<nav>` carries a default `aria-label` so it is not announced as an unnamed landmark. A page\n * with more than one sidebar should name each one — pass your own `aria-label` (or\n * `aria-labelledby`), which overrides the default.\n *\n * It stands at least viewport-tall by default. An embedded panel that should size to its content\n * opts out with `minHeight=\"auto\"`.\n *\n * Below the `tablet` breakpoint it switches to the mobile design on its own — full width, 48px\n * rows, larger icons and labels, a squared-off selection edge. A sidebar that sits beside page\n * content has nowhere to go at that width, so it can become a drawer instead: pass `open` and\n * `onOpenChange` (opened by `BitkitPageHeader`'s menu button) and below `tablet` it slides in\n * from the trailing edge over a backdrop, with its close button on the backdrop beside the panel\n * and a `drawerHeader` naming the page. Both props are ignored from `tablet` up.\n */\nconst BitkitPageSidebar = forwardRef<HTMLElement, BitkitPageSidebarProps>((props, ref) => {\n const { children, css, drawerHeader, onOpenChange, open = false, ...rest } = props;\n\n const { isMobile } = useResponsive();\n const { splitCssProps } = useChakraContext();\n const hasDrawer = onOpenChange !== undefined;\n const isDrawer = isMobile && hasDrawer;\n\n const recipe = useSlotRecipe({ key: 'pageSidebar' });\n const styles = recipe({ layout: isDrawer ? 'drawer' : hasDrawer ? 'hidden' : 'static' });\n\n if (!isDrawer) {\n return (\n <Box ref={ref} aria-label=\"Page navigation\" asChild css={[styles.root, css]} {...rest}>\n <nav>{children}</nav>\n </Box>\n );\n }\n\n // The consumer's sizing is for the sidebar beside the page; inside the panel only the recipe\n // lays the navigation out.\n const [, navProps] = splitCssProps(rest);\n\n return (\n <Drawer.Root onOpenChange={(details) => onOpenChange(details.open)} open={open}>\n <Portal>\n <Drawer.Backdrop css={styles.drawerBackdrop} />\n <Drawer.Positioner>\n <Drawer.Content aria-label=\"Navigation\" css={styles.drawerContent}>\n {drawerHeader}\n <Box ref={ref} aria-label=\"Page navigation\" asChild css={styles.root} {...navProps}>\n <nav>{children}</nav>\n </Box>\n <Box css={styles.drawerCloseButton}>\n <BitkitCloseButton\n aria-label=\"Close navigation\"\n colorVariant=\"white\"\n onClick={() => onOpenChange(false)}\n size=\"md\"\n />\n </Box>\n </Drawer.Content>\n </Drawer.Positioner>\n </Portal>\n </Drawer.Root>\n );\n});\n\nBitkitPageSidebar.displayName = 'BitkitPageSidebar';\n\n// ----- BitkitPageSidebar.Item -----\n\ninterface BitkitPageSidebarItemCommonProps {\n badge?: ReactNode;\n /**\n * The item's label. Typed as a string rather than `ReactNode` because the row truncates it with\n * an ellipsis, and the overflow tooltip needs the full text to reveal.\n */\n children: string;\n icon?: BitkitIconComponent;\n /**\n * Marks the item as the current page. Mutually exclusive with `state=\"disabled\"` — you cannot be\n * on a page whose nav item is unavailable — but the two are not type-checked against each other:\n * the combination only ever arrives from a computed `selected`, which no union can catch, and\n * checking it would break the ordinary `selected={item.id === currentId}` mapping. If both do\n * turn up, disabled wins, since that is the one of the two the design actually defines.\n */\n selected?: boolean;\n state?: 'disabled';\n suffixIcon?: BitkitIconComponent;\n}\n\ninterface BitkitPageSidebarItemLinkProps\n extends Omit<HTMLChakraProps<'a'>, 'children'>, BitkitPageSidebarItemCommonProps {\n href: string;\n}\n\ninterface BitkitPageSidebarItemButtonProps\n extends Omit<HTMLChakraProps<'button'>, 'children' | 'disabled'>, BitkitPageSidebarItemCommonProps {\n href?: never;\n}\n\nexport type BitkitPageSidebarItemProps = BitkitPageSidebarItemButtonProps | BitkitPageSidebarItemLinkProps;\n\nconst BitkitPageSidebarItem = forwardRef<HTMLButtonElement | HTMLAnchorElement, BitkitPageSidebarItemProps>(\n (props, ref) => {\n const { badge, children, icon: Icon, selected, state, suffixIcon: SuffixIcon, ...rest } = props;\n\n const isDisabled = state === 'disabled';\n // `selected` and `state=\"disabled\"` are not checked against each other in the types, so this is\n // the single place the overlap is resolved: disabled wins, and the two recipe variants can never\n // both claim the same slot.\n const isSelected = selected === true && !isDisabled;\n\n const recipe = useSlotRecipe({ key: 'pageSidebar' });\n const styles = recipe({ isDisabled, selected: isSelected });\n\n // The design gives neither a selected row nor a disabled one a trailing icon — the desktop\n // (`20014:6498`, `20014:6494`) and mobile (`9221:4417`) items drop it in those states.\n const VisibleSuffixIcon = isSelected || isDisabled ? undefined : SuffixIcon;\n\n const { isTruncated, ref: labelRef } = useIsTruncated<HTMLSpanElement>(children);\n\n const itemContent = (\n <>\n {/* `span`, not the default `div`: in button mode these sit inside a `<button>`, whose\n content model allows phrasing content only. */}\n <Box as=\"span\" css={styles.contentBlock}>\n {Icon && (\n <>\n <Icon css={[styles.itemIcon, showAtDesktop]} size=\"16\" />\n <Icon css={[styles.itemIcon, showAtMobile]} size=\"24\" />\n </>\n )}\n <Text as=\"span\" css={styles.itemLabel} ref={labelRef}>\n {children}\n </Text>\n </Box>\n {badge}\n {VisibleSuffixIcon && (\n <>\n <VisibleSuffixIcon css={[styles.suffixIcon, showAtDesktop]} size=\"16\" />\n <VisibleSuffixIcon css={[styles.suffixIcon, showAtMobile]} size=\"24\" />\n </>\n )}\n {isSelected && <Box as=\"span\" css={styles.selectionMarker} />}\n </>\n );\n\n // The overflow tooltip anchors to the row itself, which is the focusable element: anchored to an\n // inner node it would only ever open on hover, leaving keyboard users unable to read a truncated\n // label. `Tooltip.Trigger` renders `asChild` and composes refs, so the consumer's forwarded ref\n // survives, and the ref measuring the truncation sits on the label further in.\n const withTooltip = (row: ReactNode) => (\n <BitkitOverflowTooltip disabled={!isTruncated} text={children}>\n {row}\n </BitkitOverflowTooltip>\n );\n\n // `chakra.a` / `chakra.button` rather than a named primitive: the item carries its own slot recipe,\n // and Chakra's `Button`/`Link` would layer their own recipe styles on top of it.\n if (rest.href !== undefined) {\n const { className, href, onClick, onKeyDown, ...linkRest } = rest;\n const handleClick = isDisabled\n ? (event: MouseEvent<HTMLAnchorElement>) => {\n event.preventDefault();\n event.stopPropagation();\n }\n : onClick;\n const handleKeyDown = isDisabled\n ? (event: KeyboardEvent<HTMLAnchorElement>) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n event.stopPropagation();\n }\n }\n : onKeyDown;\n\n return withTooltip(\n <chakra.a\n ref={ref as Ref<HTMLAnchorElement>}\n {...linkRest}\n aria-current={isSelected ? 'page' : undefined}\n aria-disabled={isDisabled || undefined}\n className={className ? `group ${className}` : 'group'}\n css={styles.item}\n // Dropping the attribute is what actually disables the link, the way `BitkitLink` and\n // `BitkitControlButton` do it. The handlers below only block `click` and `Enter`/`Space`;\n // a middle click fires `auxclick`, and \"Open link in new tab\" never reaches the page at\n // all, so both would still navigate while an `href` is there to follow.\n href={isDisabled ? undefined : href}\n onClick={handleClick}\n onKeyDown={handleKeyDown}\n >\n {itemContent}\n </chakra.a>,\n );\n }\n\n const { className, href: _href, ...buttonRest } = rest;\n\n return withTooltip(\n <chakra.button\n ref={ref as Ref<HTMLButtonElement>}\n {...buttonRest}\n aria-current={isSelected ? 'page' : undefined}\n className={className ? `group ${className}` : 'group'}\n css={styles.item}\n disabled={isDisabled}\n type=\"button\"\n >\n {itemContent}\n </chakra.button>,\n );\n },\n);\n\nBitkitPageSidebarItem.displayName = 'BitkitPageSidebarItem';\n\n// ----- BitkitPageSidebar.ProgressItem -----\n\nexport interface BitkitPageSidebarProgressItemProps extends Omit<HTMLChakraProps<'button'>, 'children' | 'disabled'> {\n /**\n * Trailing content between the label and the connector — a `BitkitBadge` counting what the stage\n * is waiting on. Sized to itself; the connector takes the rest.\n *\n * The design draws it 20px tall, which is `size=\"xs\"`. The badge arrives as a node, so the\n * component cannot set that for you — a taller one grows the row past its 40px.\n */\n badge?: ReactNode;\n /** The row's label. */\n children: ReactNode;\n /**\n * The status icon. Unlike `Item`, whose icon colour the sidebar owns, this one carries the\n * stage's status, so `iconColor` is the consumer's to set — `selected`, `notRequired` and\n * `state=\"disabled\"` still win over it, since those describe the row rather than the status.\n */\n icon: BitkitIconComponent;\n /**\n * A semantic token for the status the icon is showing. Left out, the icon takes the sidebar's own\n * `icon/secondary` — which is what the design gives a stage whose icon carries no status, such as\n * an approvals step.\n *\n * Narrowed to the `icon/*` and `color/*` tokens, so a name that does not exist is a compile error\n * rather than a colour that silently does not apply. Both families are here because the statuses\n * need both: the design takes success and failure from `icon/*` but in-progress from\n * `color/orange/bold`, which the `icon/*` set has no equivalent of.\n */\n iconColor?: BitkitColorToken<'color' | 'icon'>;\n /**\n * A stage the release does not have to clear. Steps the icon and the label down one level, so the\n * row reads as present but not awaited.\n */\n notRequired?: boolean;\n /** Follows the label after a middle dot — `auto-upload`, `not required`. */\n secondaryText?: string;\n selected?: boolean;\n state?: 'disabled';\n /**\n * Draws the dashed rule that runs from the badge to the trailing edge, tying one stage to the\n * next. The last stage in a list has nothing to tie to, so it goes without.\n */\n withConnector?: boolean;\n}\n\n/**\n * A stage of something that progresses, as a row of the page sidebar — a release running through\n * its steps, and whatever comes to look like it.\n *\n * It is a sibling of `Item` rather than a variant of it: the row reports where a stage stands\n * rather than navigating, so its icon is status-coloured, its label does not grow, and a dashed\n * connector runs to the trailing edge between the stages.\n *\n * Always a `<button>`, and always interactive — it carries the shared row's pointer cursor and\n * hover background, so a row without an `onClick` looks actionable and is not. `Item`'s\n * button-or-anchor union is deliberately not here: a stage is selected within the page it is\n * already on rather than navigated to.\n */\nconst BitkitPageSidebarProgressItem = forwardRef<HTMLButtonElement, BitkitPageSidebarProgressItemProps>(\n (props, ref) => {\n const {\n badge,\n children,\n className,\n css,\n icon: Icon,\n iconColor,\n notRequired,\n secondaryText,\n selected,\n state,\n withConnector,\n ...rest\n } = props;\n\n const isDisabled = state === 'disabled';\n // Disabled is the whole state of the row, so the other two are dropped rather than merged: the\n // recipe resolves a combination in its own order, and a disabled row came out of it carrying\n // the selected icon colour. Nothing about a disabled stage is selected or not-required.\n const isSelected = selected && !isDisabled;\n // And `selected` wins over `notRequired`, which the design decides rather than declaration\n // order: its `selected=yes, notRequired=true` variant draws the selected row's icon and label\n // and keeps `not required` in the secondary text -- which is what dropping this produces.\n const isNotRequired = notRequired && !isDisabled && !isSelected;\n\n const statusColor = isDisabled || isNotRequired || isSelected ? undefined : iconColor;\n\n const recipe = useSlotRecipe({ key: 'pageSidebar' });\n const styles = recipe({ isDisabled, notRequired: isNotRequired, selected: isSelected });\n\n return (\n <chakra.button\n ref={ref}\n {...rest}\n // `step` rather than the `page` an `Item` carries: these are stages of one thing, not\n // separate pages.\n aria-current={isSelected ? 'step' : undefined}\n className={className ? `group ${className}` : 'group'}\n css={[styles.item, styles.progressRow, css]}\n disabled={isDisabled}\n type=\"button\"\n >\n <Box as=\"span\" css={[styles.contentBlock, styles.progressContentBlock]}>\n {/* Two icons, one per size, for the reason `Item` has two -- see `showAtDesktop`.\n The status colour applies while the row is ordinary: `selected`, `notRequired` and\n disabled are states of the row rather than of the status, and the design recolours the\n icon in all three, so the recipe takes it back there. */}\n <Icon color={statusColor} css={[styles.progressIcon, showAtDesktop]} size=\"16\" />\n <Icon color={statusColor} css={[styles.progressIcon, showAtMobile]} size=\"24\" />\n <Box as=\"span\" css={styles.progressTextBlock}>\n <Text as=\"span\" css={styles.progressLabel}>\n {children}\n </Text>\n {secondaryText && (\n <Text as=\"span\" css={styles.progressSecondaryText}>\n {`\\u00b7 ${secondaryText}`}\n </Text>\n )}\n </Box>\n {(badge || withConnector) && (\n <Box as=\"span\" css={styles.progressBlocker}>\n {badge}\n {/* Decorative: it ties the stages together visually and has nothing to separate in\n the button's accessibility tree. */}\n {withConnector && <Separator aria-hidden css={styles.progressConnector} />}\n </Box>\n )}\n </Box>\n {isSelected && <Box as=\"span\" css={styles.selectionMarker} />}\n </chakra.button>\n );\n },\n);\n\nBitkitPageSidebarProgressItem.displayName = 'BitkitPageSidebarProgressItem';\n\n// ----- BitkitPageSidebar.Title -----\n\ninterface BitkitPageSidebarTitleCommonProps extends BoxProps {\n backLabel?: string;\n children: ReactNode;\n}\n\ninterface BitkitPageSidebarTitleLinkProps extends BitkitPageSidebarTitleCommonProps {\n backHref: string;\n onBackClick?: never;\n}\n\ninterface BitkitPageSidebarTitleButtonProps extends BitkitPageSidebarTitleCommonProps {\n backHref?: never;\n onBackClick: () => void;\n}\n\ninterface BitkitPageSidebarTitlePlainProps extends BitkitPageSidebarTitleCommonProps {\n backHref?: never;\n onBackClick?: never;\n}\n\nexport type BitkitPageSidebarTitleProps =\n BitkitPageSidebarTitleButtonProps | BitkitPageSidebarTitleLinkProps | BitkitPageSidebarTitlePlainProps;\n\nconst BitkitPageSidebarTitle = forwardRef<HTMLDivElement, BitkitPageSidebarTitleProps>((props, ref) => {\n const { backHref, backLabel = 'Back', children, css, onBackClick, ...rest } = props;\n\n const hasBack = backHref !== undefined || onBackClick !== undefined;\n\n const recipe = useSlotRecipe({ key: 'pageSidebar' });\n const styles = recipe({ hasBack });\n\n return (\n <Box ref={ref} css={[styles.title, css]} {...rest}>\n <Box css={styles.titleBlock}>\n {/* The label tooltip opens to the right. Its default `top` has nowhere to go here — the\n button sits at the very top of the sidebar, so the tooltip jams against the viewport\n edge and covers the header instead of flipping. */}\n {hasBack &&\n (backHref !== undefined ? (\n <BitkitControlButton\n href={backHref}\n icon={IconChevronLeft}\n label={backLabel}\n size=\"xs\"\n tooltipProps={{ placement: 'right' }}\n />\n ) : (\n <BitkitControlButton\n icon={IconChevronLeft}\n label={backLabel}\n onClick={onBackClick}\n size=\"xs\"\n tooltipProps={{ placement: 'right' }}\n />\n ))}\n <Text as=\"span\" css={styles.titleText}>\n {children}\n </Text>\n </Box>\n </Box>\n );\n});\n\nBitkitPageSidebarTitle.displayName = 'BitkitPageSidebarTitle';\n\n// ----- BitkitPageSidebar.Body -----\n\nexport interface BitkitPageSidebarBodyProps extends BoxProps {\n children: ReactNode;\n}\n\n/**\n * Holds the navigation items. It absorbs the space left between `Title` and `Footer`, so the footer\n * sits at the bottom of the viewport on a short navigation, and keeps a minimum gap above the\n * footer so the two blocks stay visually separate however long the list gets.\n */\nconst BitkitPageSidebarBody = forwardRef<HTMLDivElement, BitkitPageSidebarBodyProps>((props, ref) => {\n const { children, css, ...rest } = props;\n\n const recipe = useSlotRecipe({ key: 'pageSidebar' });\n const styles = recipe();\n\n return (\n <Box ref={ref} css={[styles.body, css]} {...rest}>\n {children}\n </Box>\n );\n});\n\nBitkitPageSidebarBody.displayName = 'BitkitPageSidebarBody';\n\n// ----- BitkitPageSidebar.Footer -----\n\nexport interface BitkitPageSidebarFooterProps extends BoxProps {\n children: ReactNode;\n}\n\nconst BitkitPageSidebarFooter = forwardRef<HTMLDivElement, BitkitPageSidebarFooterProps>((props, ref) => {\n const { children, css, ...rest } = props;\n\n const recipe = useSlotRecipe({ key: 'pageSidebar' });\n const styles = recipe();\n\n return (\n <Box ref={ref} css={[styles.footer, css]} {...rest}>\n {children}\n </Box>\n );\n});\n\nBitkitPageSidebarFooter.displayName = 'BitkitPageSidebarFooter';\n\n// ----- BitkitPageSidebar.Project -----\n\nexport interface BitkitPageSidebarProjectProps extends BoxProps {\n label: string;\n value: string;\n}\n\nconst BitkitPageSidebarProject = forwardRef<HTMLDivElement, BitkitPageSidebarProjectProps>((props, ref) => {\n const { css, label, value, ...rest } = props;\n\n const recipe = useSlotRecipe({ key: 'pageSidebar' });\n const styles = recipe();\n\n const { isTruncated, ref: valueRef } = useIsTruncated<HTMLSpanElement>(value);\n\n return (\n <Box ref={ref} css={[styles.project, css]} {...rest}>\n <Text as=\"span\" css={styles.projectLabel}>\n {label}\n </Text>\n {/* Wrapper for the same reason as in Item: the trigger cannot be the element being measured. */}\n <BitkitOverflowTooltip disabled={!isTruncated} text={value}>\n <Box css={styles.projectValueWrapper}>\n <Text as=\"span\" css={styles.projectValue} ref={valueRef}>\n {value}\n </Text>\n </Box>\n </BitkitOverflowTooltip>\n </Box>\n );\n});\n\nBitkitPageSidebarProject.displayName = 'BitkitPageSidebarProject';\n\n// ----- BitkitPageSidebar.GroupHeading -----\n\nexport type BitkitPageSidebarGroupHeadingProps = BitkitGroupHeadingProps;\n\nconst BitkitPageSidebarGroupHeading = forwardRef<HTMLDivElement, BitkitPageSidebarGroupHeadingProps>((props, ref) => {\n return <BitkitGroupHeading ref={ref} paddingBlockEnd=\"8\" paddingBlockStart=\"16\" paddingInline=\"24\" {...props} />;\n});\n\nBitkitPageSidebarGroupHeading.displayName = 'BitkitPageSidebarGroupHeading';\n\n// ----- BitkitPageSidebar.DrawerHeader -----\n\nexport interface BitkitPageSidebarDrawerHeaderProps extends Omit<BoxProps, 'title'> {\n /** App icon or user avatar before the title — a 32px `BitkitAvatar`. */\n avatar?: ReactNode;\n /** The entity's secondary line, an app's bundle id or a user's email. Its presence selects the entity layout. */\n secondaryText?: string;\n /** What the navigation belongs to: the page, app or user. */\n title: string;\n}\n\n/**\n * Header of the sidebar's mobile drawer (Figma `mobilePageSidebarHeader`). Pass it as the\n * sidebar's `drawerHeader`; it is what the panel opens with, above the navigation.\n */\nconst BitkitPageSidebarDrawerHeader = forwardRef<HTMLDivElement, BitkitPageSidebarDrawerHeaderProps>((props, ref) => {\n const { avatar, css, secondaryText, title, ...rest } = props;\n\n const recipe = useSlotRecipe({ key: 'pageSidebar' });\n const styles = recipe({ hasSecondaryText: secondaryText !== undefined });\n\n return (\n <Box ref={ref} css={[styles.drawerHeader, css]} {...rest}>\n {avatar !== undefined && <Box css={styles.drawerHeaderAvatar}>{avatar}</Box>}\n <Box css={styles.drawerHeaderTextBlock}>\n <Text css={styles.drawerHeaderTitle}>{title}</Text>\n {secondaryText !== undefined && <Text css={styles.drawerHeaderSecondaryText}>{secondaryText}</Text>}\n </Box>\n </Box>\n );\n});\n\nBitkitPageSidebarDrawerHeader.displayName = 'BitkitPageSidebarDrawerHeader';\n\n// ----- BitkitPageSidebar.Divider -----\n\nexport type BitkitPageSidebarDividerProps = SeparatorProps;\n\nconst BitkitPageSidebarDivider = forwardRef<HTMLDivElement, BitkitPageSidebarDividerProps>((props, ref) => {\n const { css, ...rest } = props;\n\n const recipe = useSlotRecipe({ key: 'pageSidebar' });\n const styles = recipe();\n\n return <Separator ref={ref} css={[styles.divider, css]} {...rest} />;\n});\n\nBitkitPageSidebarDivider.displayName = 'BitkitPageSidebarDivider';\n\nexport default withSubComponents(BitkitPageSidebar, {\n Body: BitkitPageSidebarBody,\n Divider: BitkitPageSidebarDivider,\n DrawerHeader: BitkitPageSidebarDrawerHeader,\n Footer: BitkitPageSidebarFooter,\n GroupHeading: BitkitPageSidebarGroupHeading,\n Item: BitkitPageSidebarItem,\n ProgressItem: BitkitPageSidebarProgressItem,\n Project: BitkitPageSidebarProject,\n Title: BitkitPageSidebarTitle,\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"BitkitPageSidebar.js","names":["_Fragment"],"sources":["../../../lib/components/BitkitPageSidebar/BitkitPageSidebar.tsx"],"sourcesContent":["import { Box, type BoxProps } from '@chakra-ui/react/box';\nimport { Drawer } from '@chakra-ui/react/drawer';\nimport { Portal } from '@chakra-ui/react/portal';\nimport { Separator, type SeparatorProps } from '@chakra-ui/react/separator';\nimport { chakra, type HTMLChakraProps, useChakraContext, useSlotRecipe } from '@chakra-ui/react/styled-system';\nimport { Text } from '@chakra-ui/react/text';\nimport { forwardRef, type KeyboardEvent, type MouseEvent, type ReactNode, type Ref } from 'react';\n\nimport useResponsive from '../../hooks/useResponsive';\nimport { type BitkitIconComponent } from '../../icons';\nimport IconChevronLeft from '../../icons/IconChevronLeft';\nimport { type BitkitColorToken } from '../../theme/themeUtils';\nimport focusableDisabledProps from '../../utilities/focusableDisabledProps';\nimport useIsTruncated from '../../utilities/useIsTruncated';\nimport { withSubComponents } from '../../utilities/withSubComponents';\nimport BitkitCloseButton from '../BitkitCloseButton/BitkitCloseButton';\nimport BitkitControlButton from '../BitkitControlButton/BitkitControlButton';\nimport BitkitGroupHeading, { type BitkitGroupHeadingProps } from '../BitkitGroupHeading/BitkitGroupHeading';\nimport BitkitOverflowTooltip from '../BitkitOverflowTooltip/BitkitOverflowTooltip';\nimport BitkitTooltip from '../BitkitTooltip/BitkitTooltip';\n\n// The 16 and 24 icons are separate drawings, not one scaled to two sizes, so the breakpoint swap\n// cannot live in the recipe — both are rendered and one is hidden. A `useBreakpointValue` would\n// render a single icon, but it only re-reads on mount: resize a live page across `tablet` and the\n// row resizes underneath an icon that keeps its old size.\nconst showAtDesktop = { display: { base: 'none', tablet: 'block' } };\nconst showAtMobile = { display: { base: 'block', tablet: 'none' } };\n\n// ----- BitkitPageSidebar (Root) -----\n\nexport interface BitkitPageSidebarProps extends BoxProps {\n children: ReactNode;\n /**\n * Header of the mobile drawer panel, naming what the navigation belongs to — pass a\n * `BitkitPageSidebar.DrawerHeader`. Only rendered in drawer mode.\n */\n drawerHeader?: ReactNode;\n /**\n * Mobile drawer visibility. Controlled, because the page header's menu button is what opens it.\n * Passing `onOpenChange` is what opts the sidebar into the drawer below `tablet`; without it the\n * sidebar stays in the flow at full width there. Both are ignored from `tablet` up.\n *\n * Style props and `css` apply to the sidebar beside the page only — the drawer panel sizes\n * itself, so a desktop `width` or padding never reaches the navigation inside it.\n */\n onOpenChange?: (open: boolean) => void;\n open?: boolean;\n}\n\n/**\n * Page-level navigation sidebar. It expects three children, in this order:\n *\n * ```tsx\n * <BitkitPageSidebar>\n * <BitkitPageSidebar.Title>Title</BitkitPageSidebar.Title>\n * <BitkitPageSidebar.Body>\n * <BitkitPageSidebar.Item icon={IconWorkflow}>Workflows</BitkitPageSidebar.Item>\n * </BitkitPageSidebar.Body>\n * <BitkitPageSidebar.Footer>…</BitkitPageSidebar.Footer>\n * </BitkitPageSidebar>\n * ```\n *\n * `Title` and `Footer` are optional. The sidebar scrolls as a single block: when the navigation is\n * taller than the viewport the whole thing — footer included — grows and scrolls with the page,\n * which is why `Body` keeps a minimum gap above the footer so the two blocks never touch.\n *\n * The `<nav>` carries a default `aria-label` so it is not announced as an unnamed landmark. A page\n * with more than one sidebar should name each one — pass your own `aria-label` (or\n * `aria-labelledby`), which overrides the default.\n *\n * It stands at least viewport-tall by default. An embedded panel that should size to its content\n * opts out with `minHeight=\"auto\"`.\n *\n * Below the `tablet` breakpoint it switches to the mobile design on its own — full width, 48px\n * rows, larger icons and labels, a squared-off selection edge. A sidebar that sits beside page\n * content has nowhere to go at that width, so it can become a drawer instead: pass `open` and\n * `onOpenChange` (opened by `BitkitPageHeader`'s menu button) and below `tablet` it slides in\n * from the trailing edge over a backdrop, with its close button on the backdrop beside the panel\n * and a `drawerHeader` naming the page. Both props are ignored from `tablet` up.\n */\nconst BitkitPageSidebar = forwardRef<HTMLElement, BitkitPageSidebarProps>((props, ref) => {\n const { children, css, drawerHeader, onOpenChange, open = false, ...rest } = props;\n\n const { isMobile } = useResponsive();\n const { splitCssProps } = useChakraContext();\n const hasDrawer = onOpenChange !== undefined;\n const isDrawer = isMobile && hasDrawer;\n\n const recipe = useSlotRecipe({ key: 'pageSidebar' });\n const styles = recipe({ layout: isDrawer ? 'drawer' : hasDrawer ? 'hidden' : 'static' });\n\n if (!isDrawer) {\n return (\n <Box ref={ref} aria-label=\"Page navigation\" asChild css={[styles.root, css]} {...rest}>\n <nav>{children}</nav>\n </Box>\n );\n }\n\n // The consumer's sizing is for the sidebar beside the page; inside the panel only the recipe\n // lays the navigation out.\n const [, navProps] = splitCssProps(rest);\n\n return (\n <Drawer.Root onOpenChange={(details) => onOpenChange(details.open)} open={open}>\n <Portal>\n <Drawer.Backdrop css={styles.drawerBackdrop} />\n <Drawer.Positioner>\n <Drawer.Content aria-label=\"Navigation\" css={styles.drawerContent}>\n {drawerHeader}\n <Box ref={ref} aria-label=\"Page navigation\" asChild css={styles.root} {...navProps}>\n <nav>{children}</nav>\n </Box>\n <Box css={styles.drawerCloseButton}>\n <BitkitCloseButton\n aria-label=\"Close navigation\"\n colorVariant=\"white\"\n onClick={() => onOpenChange(false)}\n size=\"md\"\n />\n </Box>\n </Drawer.Content>\n </Drawer.Positioner>\n </Portal>\n </Drawer.Root>\n );\n});\n\nBitkitPageSidebar.displayName = 'BitkitPageSidebar';\n\n// ----- BitkitPageSidebar.Item -----\n\ninterface BitkitPageSidebarItemCommonProps {\n badge?: ReactNode;\n /**\n * The item's label. Typed as a string rather than `ReactNode` because the row truncates it with\n * an ellipsis, and the overflow tooltip needs the full text to reveal.\n */\n children: string;\n /**\n * Why the item is unavailable, shown in a tooltip on it while `state` is `\"disabled\"`. The item\n * then stays focusable and is announced as disabled (`aria-disabled`), so the tooltip opens on\n * hover and focus. It still neither navigates nor activates. Use it instead of wrapping a disabled\n * item in `BitkitTooltip`.\n */\n disabledReason?: string;\n icon?: BitkitIconComponent;\n /**\n * Marks the item as the current page. Mutually exclusive with `state=\"disabled\"` — you cannot be\n * on a page whose nav item is unavailable — but the two are not type-checked against each other:\n * the combination only ever arrives from a computed `selected`, which no union can catch, and\n * checking it would break the ordinary `selected={item.id === currentId}` mapping. If both do\n * turn up, disabled wins, since that is the one of the two the design actually defines.\n */\n selected?: boolean;\n state?: 'disabled';\n suffixIcon?: BitkitIconComponent;\n}\n\ninterface BitkitPageSidebarItemLinkProps\n extends Omit<HTMLChakraProps<'a'>, 'children'>, BitkitPageSidebarItemCommonProps {\n href: string;\n}\n\ninterface BitkitPageSidebarItemButtonProps\n extends Omit<HTMLChakraProps<'button'>, 'children' | 'disabled'>, BitkitPageSidebarItemCommonProps {\n href?: never;\n}\n\nexport type BitkitPageSidebarItemProps = BitkitPageSidebarItemButtonProps | BitkitPageSidebarItemLinkProps;\n\nconst BitkitPageSidebarItem = forwardRef<HTMLButtonElement | HTMLAnchorElement, BitkitPageSidebarItemProps>(\n (props, ref) => {\n const { badge, children, disabledReason, icon: Icon, selected, state, suffixIcon: SuffixIcon, ...rest } = props;\n\n const isDisabled = state === 'disabled';\n const hasReason = isDisabled && !!disabledReason;\n // `selected` and `state=\"disabled\"` are not checked against each other in the types, so this is\n // the single place the overlap is resolved: disabled wins, and the two recipe variants can never\n // both claim the same slot.\n const isSelected = selected === true && !isDisabled;\n\n const recipe = useSlotRecipe({ key: 'pageSidebar' });\n const styles = recipe({ isDisabled, selected: isSelected });\n\n // The design gives neither a selected row nor a disabled one a trailing icon — the desktop\n // (`20014:6498`, `20014:6494`) and mobile (`9221:4417`) items drop it in those states.\n const VisibleSuffixIcon = isSelected || isDisabled ? undefined : SuffixIcon;\n\n const { isTruncated, ref: labelRef } = useIsTruncated<HTMLSpanElement>(children);\n\n const itemContent = (\n <>\n {/* `span`, not the default `div`: in button mode these sit inside a `<button>`, whose\n content model allows phrasing content only. */}\n <Box as=\"span\" css={styles.contentBlock}>\n {Icon && (\n <>\n <Icon css={[styles.itemIcon, showAtDesktop]} size=\"16\" />\n <Icon css={[styles.itemIcon, showAtMobile]} size=\"24\" />\n </>\n )}\n <Text as=\"span\" css={styles.itemLabel} ref={labelRef}>\n {children}\n </Text>\n </Box>\n {badge}\n {VisibleSuffixIcon && (\n <>\n <VisibleSuffixIcon css={[styles.suffixIcon, showAtDesktop]} size=\"16\" />\n <VisibleSuffixIcon css={[styles.suffixIcon, showAtMobile]} size=\"24\" />\n </>\n )}\n {isSelected && <Box as=\"span\" css={styles.selectionMarker} />}\n </>\n );\n\n // The overflow tooltip anchors to the row itself, which is the focusable element: anchored to an\n // inner node it would only ever open on hover, leaving keyboard users unable to read a truncated\n // label. `Tooltip.Trigger` renders `asChild` and composes refs, so the consumer's forwarded ref\n // survives, and the ref measuring the truncation sits on the label further in.\n // The reason tooltip is always rendered so a state change can't remount the row and drop its focus.\n const withTooltip = (row: ReactNode) => (\n <BitkitTooltip disabled={!hasReason} placement=\"right\" text={disabledReason ?? ''}>\n <BitkitOverflowTooltip disabled={!isTruncated || hasReason} text={children}>\n {row}\n </BitkitOverflowTooltip>\n </BitkitTooltip>\n );\n\n // `chakra.a` / `chakra.button` rather than a named primitive: the item carries its own slot recipe,\n // and Chakra's `Button`/`Link` would layer their own recipe styles on top of it.\n if (rest.href !== undefined) {\n const { className, href, onClick, onKeyDown, ...linkRest } = rest;\n const handleClick = isDisabled\n ? (event: MouseEvent<HTMLAnchorElement>) => {\n event.preventDefault();\n event.stopPropagation();\n }\n : onClick;\n const handleKeyDown = isDisabled\n ? (event: KeyboardEvent<HTMLAnchorElement>) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n event.stopPropagation();\n }\n }\n : onKeyDown;\n\n return withTooltip(\n <chakra.a\n ref={ref as Ref<HTMLAnchorElement>}\n {...linkRest}\n aria-current={isSelected ? 'page' : undefined}\n aria-disabled={isDisabled || undefined}\n className={className ? `group ${className}` : 'group'}\n css={styles.item}\n // Dropping the attribute is what actually disables the link, the way `BitkitLink` and\n // `BitkitControlButton` do it. The handlers below only block `click` and `Enter`/`Space`;\n // a middle click fires `auxclick`, and \"Open link in new tab\" never reaches the page at\n // all, so both would still navigate while an `href` is there to follow.\n href={isDisabled ? undefined : href}\n onClick={handleClick}\n onKeyDown={handleKeyDown}\n // Without `href` the anchor is neither a link nor focusable, and the reason tooltip needs both.\n {...(hasReason && { role: 'link', tabIndex: 0 })}\n >\n {itemContent}\n </chakra.a>,\n );\n }\n\n const { className, href: _href, ...buttonRest } = rest;\n\n return withTooltip(\n <chakra.button\n ref={ref as Ref<HTMLButtonElement>}\n {...buttonRest}\n aria-current={isSelected ? 'page' : undefined}\n className={className ? `group ${className}` : 'group'}\n css={styles.item}\n disabled={isDisabled && !hasReason}\n type=\"button\"\n {...(hasReason && focusableDisabledProps)}\n >\n {itemContent}\n </chakra.button>,\n );\n },\n);\n\nBitkitPageSidebarItem.displayName = 'BitkitPageSidebarItem';\n\n// ----- BitkitPageSidebar.ProgressItem -----\n\nexport interface BitkitPageSidebarProgressItemProps extends Omit<HTMLChakraProps<'button'>, 'children' | 'disabled'> {\n /**\n * Trailing content between the label and the connector — a `BitkitBadge` counting what the stage\n * is waiting on. Sized to itself; the connector takes the rest.\n *\n * The design draws it 20px tall, which is `size=\"xs\"`. The badge arrives as a node, so the\n * component cannot set that for you — a taller one grows the row past its 40px.\n */\n badge?: ReactNode;\n /** The row's label. */\n children: ReactNode;\n /**\n * The status icon. Unlike `Item`, whose icon colour the sidebar owns, this one carries the\n * stage's status, so `iconColor` is the consumer's to set — `selected`, `notRequired` and\n * `state=\"disabled\"` still win over it, since those describe the row rather than the status.\n */\n icon: BitkitIconComponent;\n /**\n * A semantic token for the status the icon is showing. Left out, the icon takes the sidebar's own\n * `icon/secondary` — which is what the design gives a stage whose icon carries no status, such as\n * an approvals step.\n *\n * Narrowed to the `icon/*` and `color/*` tokens, so a name that does not exist is a compile error\n * rather than a colour that silently does not apply. Both families are here because the statuses\n * need both: the design takes success and failure from `icon/*` but in-progress from\n * `color/orange/bold`, which the `icon/*` set has no equivalent of.\n */\n iconColor?: BitkitColorToken<'color' | 'icon'>;\n /**\n * A stage the release does not have to clear. Steps the icon and the label down one level, so the\n * row reads as present but not awaited.\n */\n notRequired?: boolean;\n /** Follows the label after a middle dot — `auto-upload`, `not required`. */\n secondaryText?: string;\n selected?: boolean;\n state?: 'disabled';\n /**\n * Draws the dashed rule that runs from the badge to the trailing edge, tying one stage to the\n * next. The last stage in a list has nothing to tie to, so it goes without.\n */\n withConnector?: boolean;\n}\n\n/**\n * A stage of something that progresses, as a row of the page sidebar — a release running through\n * its steps, and whatever comes to look like it.\n *\n * It is a sibling of `Item` rather than a variant of it: the row reports where a stage stands\n * rather than navigating, so its icon is status-coloured, its label does not grow, and a dashed\n * connector runs to the trailing edge between the stages.\n *\n * Always a `<button>`, and always interactive — it carries the shared row's pointer cursor and\n * hover background, so a row without an `onClick` looks actionable and is not. `Item`'s\n * button-or-anchor union is deliberately not here: a stage is selected within the page it is\n * already on rather than navigated to.\n */\nconst BitkitPageSidebarProgressItem = forwardRef<HTMLButtonElement, BitkitPageSidebarProgressItemProps>(\n (props, ref) => {\n const {\n badge,\n children,\n className,\n css,\n icon: Icon,\n iconColor,\n notRequired,\n secondaryText,\n selected,\n state,\n withConnector,\n ...rest\n } = props;\n\n const isDisabled = state === 'disabled';\n // Disabled is the whole state of the row, so the other two are dropped rather than merged: the\n // recipe resolves a combination in its own order, and a disabled row came out of it carrying\n // the selected icon colour. Nothing about a disabled stage is selected or not-required.\n const isSelected = selected && !isDisabled;\n // And `selected` wins over `notRequired`, which the design decides rather than declaration\n // order: its `selected=yes, notRequired=true` variant draws the selected row's icon and label\n // and keeps `not required` in the secondary text -- which is what dropping this produces.\n const isNotRequired = notRequired && !isDisabled && !isSelected;\n\n const statusColor = isDisabled || isNotRequired || isSelected ? undefined : iconColor;\n\n const recipe = useSlotRecipe({ key: 'pageSidebar' });\n const styles = recipe({ isDisabled, notRequired: isNotRequired, selected: isSelected });\n\n return (\n <chakra.button\n ref={ref}\n {...rest}\n // `step` rather than the `page` an `Item` carries: these are stages of one thing, not\n // separate pages.\n aria-current={isSelected ? 'step' : undefined}\n className={className ? `group ${className}` : 'group'}\n css={[styles.item, styles.progressRow, css]}\n disabled={isDisabled}\n type=\"button\"\n >\n <Box as=\"span\" css={[styles.contentBlock, styles.progressContentBlock]}>\n {/* Two icons, one per size, for the reason `Item` has two -- see `showAtDesktop`.\n The status colour applies while the row is ordinary: `selected`, `notRequired` and\n disabled are states of the row rather than of the status, and the design recolours the\n icon in all three, so the recipe takes it back there. */}\n <Icon color={statusColor} css={[styles.progressIcon, showAtDesktop]} size=\"16\" />\n <Icon color={statusColor} css={[styles.progressIcon, showAtMobile]} size=\"24\" />\n <Box as=\"span\" css={styles.progressTextBlock}>\n <Text as=\"span\" css={styles.progressLabel}>\n {children}\n </Text>\n {secondaryText && (\n <Text as=\"span\" css={styles.progressSecondaryText}>\n {`\\u00b7 ${secondaryText}`}\n </Text>\n )}\n </Box>\n {(badge || withConnector) && (\n <Box as=\"span\" css={styles.progressBlocker}>\n {badge}\n {/* Decorative: it ties the stages together visually and has nothing to separate in\n the button's accessibility tree. */}\n {withConnector && <Separator aria-hidden css={styles.progressConnector} />}\n </Box>\n )}\n </Box>\n {isSelected && <Box as=\"span\" css={styles.selectionMarker} />}\n </chakra.button>\n );\n },\n);\n\nBitkitPageSidebarProgressItem.displayName = 'BitkitPageSidebarProgressItem';\n\n// ----- BitkitPageSidebar.Title -----\n\ninterface BitkitPageSidebarTitleCommonProps extends BoxProps {\n backLabel?: string;\n children: ReactNode;\n}\n\ninterface BitkitPageSidebarTitleLinkProps extends BitkitPageSidebarTitleCommonProps {\n backHref: string;\n onBackClick?: never;\n}\n\ninterface BitkitPageSidebarTitleButtonProps extends BitkitPageSidebarTitleCommonProps {\n backHref?: never;\n onBackClick: () => void;\n}\n\ninterface BitkitPageSidebarTitlePlainProps extends BitkitPageSidebarTitleCommonProps {\n backHref?: never;\n onBackClick?: never;\n}\n\nexport type BitkitPageSidebarTitleProps =\n BitkitPageSidebarTitleButtonProps | BitkitPageSidebarTitleLinkProps | BitkitPageSidebarTitlePlainProps;\n\nconst BitkitPageSidebarTitle = forwardRef<HTMLDivElement, BitkitPageSidebarTitleProps>((props, ref) => {\n const { backHref, backLabel = 'Back', children, css, onBackClick, ...rest } = props;\n\n const hasBack = backHref !== undefined || onBackClick !== undefined;\n\n const recipe = useSlotRecipe({ key: 'pageSidebar' });\n const styles = recipe({ hasBack });\n\n return (\n <Box ref={ref} css={[styles.title, css]} {...rest}>\n <Box css={styles.titleBlock}>\n {/* The label tooltip opens to the right. Its default `top` has nowhere to go here — the\n button sits at the very top of the sidebar, so the tooltip jams against the viewport\n edge and covers the header instead of flipping. */}\n {hasBack &&\n (backHref !== undefined ? (\n <BitkitControlButton\n href={backHref}\n icon={IconChevronLeft}\n label={backLabel}\n size=\"xs\"\n tooltipProps={{ placement: 'right' }}\n />\n ) : (\n <BitkitControlButton\n icon={IconChevronLeft}\n label={backLabel}\n onClick={onBackClick}\n size=\"xs\"\n tooltipProps={{ placement: 'right' }}\n />\n ))}\n <Text as=\"span\" css={styles.titleText}>\n {children}\n </Text>\n </Box>\n </Box>\n );\n});\n\nBitkitPageSidebarTitle.displayName = 'BitkitPageSidebarTitle';\n\n// ----- BitkitPageSidebar.Body -----\n\nexport interface BitkitPageSidebarBodyProps extends BoxProps {\n children: ReactNode;\n}\n\n/**\n * Holds the navigation items. It absorbs the space left between `Title` and `Footer`, so the footer\n * sits at the bottom of the viewport on a short navigation, and keeps a minimum gap above the\n * footer so the two blocks stay visually separate however long the list gets.\n */\nconst BitkitPageSidebarBody = forwardRef<HTMLDivElement, BitkitPageSidebarBodyProps>((props, ref) => {\n const { children, css, ...rest } = props;\n\n const recipe = useSlotRecipe({ key: 'pageSidebar' });\n const styles = recipe();\n\n return (\n <Box ref={ref} css={[styles.body, css]} {...rest}>\n {children}\n </Box>\n );\n});\n\nBitkitPageSidebarBody.displayName = 'BitkitPageSidebarBody';\n\n// ----- BitkitPageSidebar.Footer -----\n\nexport interface BitkitPageSidebarFooterProps extends BoxProps {\n children: ReactNode;\n}\n\nconst BitkitPageSidebarFooter = forwardRef<HTMLDivElement, BitkitPageSidebarFooterProps>((props, ref) => {\n const { children, css, ...rest } = props;\n\n const recipe = useSlotRecipe({ key: 'pageSidebar' });\n const styles = recipe();\n\n return (\n <Box ref={ref} css={[styles.footer, css]} {...rest}>\n {children}\n </Box>\n );\n});\n\nBitkitPageSidebarFooter.displayName = 'BitkitPageSidebarFooter';\n\n// ----- BitkitPageSidebar.Project -----\n\nexport interface BitkitPageSidebarProjectProps extends BoxProps {\n label: string;\n value: string;\n}\n\nconst BitkitPageSidebarProject = forwardRef<HTMLDivElement, BitkitPageSidebarProjectProps>((props, ref) => {\n const { css, label, value, ...rest } = props;\n\n const recipe = useSlotRecipe({ key: 'pageSidebar' });\n const styles = recipe();\n\n const { isTruncated, ref: valueRef } = useIsTruncated<HTMLSpanElement>(value);\n\n return (\n <Box ref={ref} css={[styles.project, css]} {...rest}>\n <Text as=\"span\" css={styles.projectLabel}>\n {label}\n </Text>\n {/* Wrapper for the same reason as in Item: the trigger cannot be the element being measured. */}\n <BitkitOverflowTooltip disabled={!isTruncated} text={value}>\n <Box css={styles.projectValueWrapper}>\n <Text as=\"span\" css={styles.projectValue} ref={valueRef}>\n {value}\n </Text>\n </Box>\n </BitkitOverflowTooltip>\n </Box>\n );\n});\n\nBitkitPageSidebarProject.displayName = 'BitkitPageSidebarProject';\n\n// ----- BitkitPageSidebar.GroupHeading -----\n\nexport type BitkitPageSidebarGroupHeadingProps = BitkitGroupHeadingProps;\n\nconst BitkitPageSidebarGroupHeading = forwardRef<HTMLDivElement, BitkitPageSidebarGroupHeadingProps>((props, ref) => {\n return <BitkitGroupHeading ref={ref} paddingBlockEnd=\"8\" paddingBlockStart=\"16\" paddingInline=\"24\" {...props} />;\n});\n\nBitkitPageSidebarGroupHeading.displayName = 'BitkitPageSidebarGroupHeading';\n\n// ----- BitkitPageSidebar.DrawerHeader -----\n\nexport interface BitkitPageSidebarDrawerHeaderProps extends Omit<BoxProps, 'title'> {\n /** App icon or user avatar before the title — a 32px `BitkitAvatar`. */\n avatar?: ReactNode;\n /** The entity's secondary line, an app's bundle id or a user's email. Its presence selects the entity layout. */\n secondaryText?: string;\n /** What the navigation belongs to: the page, app or user. */\n title: string;\n}\n\n/**\n * Header of the sidebar's mobile drawer (Figma `mobilePageSidebarHeader`). Pass it as the\n * sidebar's `drawerHeader`; it is what the panel opens with, above the navigation.\n */\nconst BitkitPageSidebarDrawerHeader = forwardRef<HTMLDivElement, BitkitPageSidebarDrawerHeaderProps>((props, ref) => {\n const { avatar, css, secondaryText, title, ...rest } = props;\n\n const recipe = useSlotRecipe({ key: 'pageSidebar' });\n const styles = recipe({ hasSecondaryText: secondaryText !== undefined });\n\n return (\n <Box ref={ref} css={[styles.drawerHeader, css]} {...rest}>\n {avatar !== undefined && <Box css={styles.drawerHeaderAvatar}>{avatar}</Box>}\n <Box css={styles.drawerHeaderTextBlock}>\n <Text css={styles.drawerHeaderTitle}>{title}</Text>\n {secondaryText !== undefined && <Text css={styles.drawerHeaderSecondaryText}>{secondaryText}</Text>}\n </Box>\n </Box>\n );\n});\n\nBitkitPageSidebarDrawerHeader.displayName = 'BitkitPageSidebarDrawerHeader';\n\n// ----- BitkitPageSidebar.Divider -----\n\nexport type BitkitPageSidebarDividerProps = SeparatorProps;\n\nconst BitkitPageSidebarDivider = forwardRef<HTMLDivElement, BitkitPageSidebarDividerProps>((props, ref) => {\n const { css, ...rest } = props;\n\n const recipe = useSlotRecipe({ key: 'pageSidebar' });\n const styles = recipe();\n\n return <Separator ref={ref} css={[styles.divider, css]} {...rest} />;\n});\n\nBitkitPageSidebarDivider.displayName = 'BitkitPageSidebarDivider';\n\nexport default withSubComponents(BitkitPageSidebar, {\n Body: BitkitPageSidebarBody,\n Divider: BitkitPageSidebarDivider,\n DrawerHeader: BitkitPageSidebarDrawerHeader,\n Footer: BitkitPageSidebarFooter,\n GroupHeading: BitkitPageSidebarGroupHeading,\n Item: BitkitPageSidebarItem,\n ProgressItem: BitkitPageSidebarProgressItem,\n Project: BitkitPageSidebarProject,\n Title: BitkitPageSidebarTitle,\n});\n"],"mappings":"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"}
|
|
@@ -36,8 +36,8 @@ export type BitkitTooltipProps = {
|
|
|
36
36
|
* that cannot take focus — on a focusable one this adds a second, redundant tab stop for the same
|
|
37
37
|
* control. A disabled `<button>` dispatches no pointer events and is out of the tab order, so
|
|
38
38
|
* without a wrapper the tooltip never opens — in the one state whose explanation the user actually
|
|
39
|
-
* needs. For a disabled `BitkitButton` or `
|
|
40
|
-
*
|
|
39
|
+
* needs. For a disabled `BitkitButton`, `BitkitIconButton` or `BitkitPageSidebar.Item`, pass its
|
|
40
|
+
* `disabledReason` instead: it stays focusable and announced as disabled, with no wrapper.
|
|
41
41
|
*
|
|
42
42
|
* Chakra v2's `Tooltip` had this built in as `shouldWrapChildren`, rendering a
|
|
43
43
|
* `<span display="inline-block" tabIndex={0}>` that carried the trigger props. Ark's tooltip has no
|
|
@@ -47,7 +47,7 @@ export type BitkitTooltipProps = {
|
|
|
47
47
|
*
|
|
48
48
|
* The wrapper is a plain focusable span, so focusing it announces the trigger's label and the
|
|
49
49
|
* tooltip text, not the trigger's own state — a disabled button inside is not re-announced as
|
|
50
|
-
* disabled; that is what `disabledReason`
|
|
50
|
+
* disabled; that is what `disabledReason` fixes.
|
|
51
51
|
*/
|
|
52
52
|
wrapTrigger?: boolean;
|
|
53
53
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BitkitTooltip.js","names":[],"sources":["../../../lib/components/BitkitTooltip/BitkitTooltip.tsx"],"sourcesContent":["import { Box } from '@chakra-ui/react/box';\nimport { Portal } from '@chakra-ui/react/portal';\nimport { Tooltip, type TooltipRootProps } from '@chakra-ui/react/tooltip';\nimport { isValidElement, type ReactNode, type Ref, useContext, useRef } from 'react';\n\nimport { type BitkitIconComponent } from '../../icons';\nimport BitkitPortalContext from '../../utilities/BitkitPortalContext';\n\nexport type BitkitTooltipProps = {\n /**\n * The trigger the tooltip is anchored to. Rendered via `asChild`, so it must accept a ref —\n * unless `wrapTrigger` puts a wrapper in between.\n */\n children: ReactNode;\n /** Dismiss the tooltip when the trigger is clicked. */\n closeOnClick?: TooltipRootProps['closeOnClick'];\n /**\n * Render the tooltip inline instead of in a portal. Only needed when it must stay inside its\n * parent's DOM subtree — a dialog already sets this for you.\n */\n disablePortal?: boolean;\n /** Render the trigger without a tooltip — for hints that only apply in some states. */\n disabled?: TooltipRootProps['disabled'];\n /**\n * Icon rendered after `text`, at size 16. Use it for the affordance the tooltip describes — an\n * `IconArrowNortheast` when the trigger opens something in a new tab, for instance.\n */\n icon?: BitkitIconComponent;\n /** Called when the tooltip opens or closes, including from hover and focus. */\n onOpenChange?: TooltipRootProps['onOpenChange'];\n /** Controlled open state. Leave unset to let hover and focus drive it. */\n open?: TooltipRootProps['open'];\n /** Delay in ms before an uncontrolled tooltip opens on hover. */\n openDelay?: TooltipRootProps['openDelay'];\n /** Where the tooltip sits relative to the trigger (default `top`). It flips if space is short. */\n placement?: NonNullable<TooltipRootProps['positioning']>['placement'];\n /** The tooltip's text. Plain string only — use `icon` for a glyph after it. */\n text: string;\n /**\n * Render the trigger inside a focusable wrapper instead of on the trigger itself. Only for triggers\n * that cannot take focus — on a focusable one this adds a second, redundant tab stop for the same\n * control. A disabled `<button>` dispatches no pointer events and is out of the tab order, so\n * without a wrapper the tooltip never opens — in the one state whose explanation the user actually\n * needs. For a disabled `BitkitButton` or `
|
|
1
|
+
{"version":3,"file":"BitkitTooltip.js","names":[],"sources":["../../../lib/components/BitkitTooltip/BitkitTooltip.tsx"],"sourcesContent":["import { Box } from '@chakra-ui/react/box';\nimport { Portal } from '@chakra-ui/react/portal';\nimport { Tooltip, type TooltipRootProps } from '@chakra-ui/react/tooltip';\nimport { isValidElement, type ReactNode, type Ref, useContext, useRef } from 'react';\n\nimport { type BitkitIconComponent } from '../../icons';\nimport BitkitPortalContext from '../../utilities/BitkitPortalContext';\n\nexport type BitkitTooltipProps = {\n /**\n * The trigger the tooltip is anchored to. Rendered via `asChild`, so it must accept a ref —\n * unless `wrapTrigger` puts a wrapper in between.\n */\n children: ReactNode;\n /** Dismiss the tooltip when the trigger is clicked. */\n closeOnClick?: TooltipRootProps['closeOnClick'];\n /**\n * Render the tooltip inline instead of in a portal. Only needed when it must stay inside its\n * parent's DOM subtree — a dialog already sets this for you.\n */\n disablePortal?: boolean;\n /** Render the trigger without a tooltip — for hints that only apply in some states. */\n disabled?: TooltipRootProps['disabled'];\n /**\n * Icon rendered after `text`, at size 16. Use it for the affordance the tooltip describes — an\n * `IconArrowNortheast` when the trigger opens something in a new tab, for instance.\n */\n icon?: BitkitIconComponent;\n /** Called when the tooltip opens or closes, including from hover and focus. */\n onOpenChange?: TooltipRootProps['onOpenChange'];\n /** Controlled open state. Leave unset to let hover and focus drive it. */\n open?: TooltipRootProps['open'];\n /** Delay in ms before an uncontrolled tooltip opens on hover. */\n openDelay?: TooltipRootProps['openDelay'];\n /** Where the tooltip sits relative to the trigger (default `top`). It flips if space is short. */\n placement?: NonNullable<TooltipRootProps['positioning']>['placement'];\n /** The tooltip's text. Plain string only — use `icon` for a glyph after it. */\n text: string;\n /**\n * Render the trigger inside a focusable wrapper instead of on the trigger itself. Only for triggers\n * that cannot take focus — on a focusable one this adds a second, redundant tab stop for the same\n * control. A disabled `<button>` dispatches no pointer events and is out of the tab order, so\n * without a wrapper the tooltip never opens — in the one state whose explanation the user actually\n * needs. For a disabled `BitkitButton`, `BitkitIconButton` or `BitkitPageSidebar.Item`, pass its\n * `disabledReason` instead: it stays focusable and announced as disabled, with no wrapper.\n *\n * Chakra v2's `Tooltip` had this built in as `shouldWrapChildren`, rendering a\n * `<span display=\"inline-block\" tabIndex={0}>` that carried the trigger props. Ark's tooltip has no\n * equivalent, and Chakra's own v2->v3 codemod replaces it with a bare `<span>` — which restores\n * hover but silently drops the tab stop, so the tooltip stays unreachable by keyboard. This prop is\n * that escape hatch, `tabIndex` included.\n *\n * The wrapper is a plain focusable span, so focusing it announces the trigger's label and the\n * tooltip text, not the trigger's own state — a disabled button inside is not re-announced as\n * disabled; that is what `disabledReason` fixes.\n */\n wrapTrigger?: boolean;\n};\n\n/**\n * A hover/focus tooltip anchored to its child: `text` carries the message and `icon` appends a\n * glyph after it. Text only — a tooltip closes when focus leaves its trigger and ignores touch, so a\n * link or a button inside one couldn't be reached. For those use `BitkitToggletip`, which looks the\n * same and opens on click.\n *\n * The content renders in a portal, so it escapes any ancestor that traps stacking (`isolation`,\n * `transform`, a `z-index`ed positioned box) or clips overflow. Dialogs turn that off through\n * `BitkitPortalContext`; `disablePortal` is the manual override.\n */\nconst BitkitTooltip = ({\n children,\n closeOnClick,\n disablePortal,\n disabled,\n icon: Icon,\n onOpenChange,\n open,\n openDelay,\n placement = 'top',\n text,\n wrapTrigger,\n}: BitkitTooltipProps) => {\n const { disablePortal: disablePortalFromContext } = useContext(BitkitPortalContext);\n\n // Anchor positioning to the trigger node by ref rather than relying on Zag's\n // id/data-scope lookup. When the trigger is also wrapped by another `asChild`\n // trigger (e.g. Menu.Trigger), the outer trigger's id and data-scope/data-part\n // overwrite ours on the shared node, so `getTriggerEl()` can't find it and the\n // tooltip loses its anchor. `getAnchorElement` takes precedence over the id\n // lookup, so this is safe in the standalone case too.\n const triggerRef = useRef<HTMLElement>(null);\n\n return (\n <Tooltip.Root\n closeOnClick={closeOnClick}\n disabled={disabled}\n onOpenChange={onOpenChange}\n open={open}\n openDelay={openDelay}\n positioning={{ getAnchorElement: () => triggerRef.current, placement, offset: { mainAxis: 4, crossAxis: 0 } }}\n >\n <Tooltip.Trigger ref={triggerRef as Ref<HTMLButtonElement>} asChild>\n {wrapTrigger || !isValidElement(children) ? (\n // inline-flex so the wrapper shrink-wraps the trigger: a plain inline span adds descender\n // space below it and shifts the alignment of whatever sits next to it.\n <Box\n as=\"span\"\n display=\"inline-flex\"\n tabIndex={0}\n css={{\n // The wrapper owns the tab stop, but the focus ring belongs on the trigger: an outline\n // follows the border-radius of the element it is drawn on, and the wrapper has none —\n // so leaving it here squares off a pill or a round trigger.\n _focusVisible: {\n outline: 'none',\n '& > *': {\n outlineColor: 'var(--focus-ring-color)',\n outlineOffset: 'var(--focus-ring-offset, 2px)',\n outlineStyle: 'var(--focus-ring-style, solid)',\n outlineWidth: 'var(--focus-ring-width, 2px)',\n },\n },\n }}\n >\n {children}\n </Box>\n ) : (\n children\n )}\n </Tooltip.Trigger>\n <Portal disabled={disablePortal || disablePortalFromContext}>\n <Tooltip.Positioner>\n <Tooltip.Content>\n <Tooltip.Arrow>\n <Tooltip.ArrowTip />\n </Tooltip.Arrow>\n <Box display=\"flex\" gap=\"8\">\n {text}\n {Icon && <Icon flexShrink=\"0\" size=\"16\" />}\n </Box>\n </Tooltip.Content>\n </Tooltip.Positioner>\n </Portal>\n </Tooltip.Root>\n );\n};\n\nBitkitTooltip.displayName = 'BitkitTooltip';\n\nexport default BitkitTooltip;\n"],"mappings":";;;;;;;;;;;;;;;;;AAqEA,IAAM,iBAAiB,EACrB,UACA,cACA,eACA,UACA,MAAM,MACN,cACA,MACA,WACA,YAAY,OACZ,MACA,kBACwB;CACxB,MAAM,EAAE,eAAe,6BAA6B,WAAW,mBAAmB;CAQlF,MAAM,aAAa,OAAoB,IAAI;CAE3C,OACE,qBAAC,QAAQ,MAAT;EACgB;EACJ;EACI;EACR;EACK;EACX,aAAa;GAAE,wBAAwB,WAAW;GAAS;GAAW,QAAQ;IAAE,UAAU;IAAG,WAAW;GAAE;EAAE;EAN9G,UAAA,CAQE,oBAAC,QAAQ,SAAT;GAAiB,KAAK;GAAsC,SAAA;GACzD,UAAA,eAAe,CAAC,eAAe,QAAQ,IAGtC,oBAAC,KAAD;IACE,IAAG;IACH,SAAQ;IACR,UAAU;IACV,KAAK,EAIH,eAAe;KACb,SAAS;KACT,SAAS;MACP,cAAc;MACd,eAAe;MACf,cAAc;MACd,cAAc;KAChB;IACF,EACF;IAEC;GACE,CAAA,IAEL;EAEa,CAAA,GACjB,oBAAC,QAAD;GAAQ,UAAU,iBAAiB;GACjC,UAAA,oBAAC,QAAQ,YAAT,EAAA,UACE,qBAAC,QAAQ,SAAT,EAAA,UAAA,CACE,oBAAC,QAAQ,OAAT,EAAA,UACE,oBAAC,QAAQ,UAAT,CAAmB,CAAA,EACN,CAAA,GACf,qBAAC,KAAD;IAAK,SAAQ;IAAO,KAAI;IAAxB,UAAA,CACG,MACA,QAAQ,oBAAC,MAAD;KAAM,YAAW;KAAI,MAAK;IAAM,CAAA,CACtC;GACU,CAAA,CAAA,EAAA,CAAA,EACC,CAAA;EACd,CAAA,CACI;;AAElB;AAEA,cAAc,cAAc"}
|