pixelize-design-library 2.4.2-beta.35 → 2.4.2-beta.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Components/AppSwitcher/AppSwitcher.js +2 -45
- package/dist/Components/SegmentedControl/SegmentedControl.d.ts +1 -1
- package/dist/Components/SegmentedControl/SegmentedControl.js +40 -10
- package/dist/Components/SegmentedControl/SegmentedControlProps.d.ts +3 -0
- package/dist/Components/Toggle/TableToggle.js +68 -18
- package/dist/Components/Toggle/TableToggleProps.d.ts +3 -0
- package/dist/Components/ToggleSwitch/ToggleSwitch.js +1 -1
- package/dist/esm/Components/AppSwitcher/AppSwitcher.js +2 -45
- package/dist/esm/Components/SegmentedControl/SegmentedControl.js +42 -12
- package/dist/esm/Components/Toggle/TableToggle.js +67 -17
- package/dist/esm/Components/ToggleSwitch/ToggleSwitch.js +1 -1
- package/package.json +1 -1
|
@@ -70,11 +70,6 @@ const validate = (apps) => {
|
|
|
70
70
|
warnOnce(`url-${app.id || i}`, `app "${app.name}" has no url; it cannot be opened.`);
|
|
71
71
|
});
|
|
72
72
|
};
|
|
73
|
-
// Past a pointer's pause threshold, so crossing the launcher on the way elsewhere does not open it,
|
|
74
|
-
// but short enough that deliberately resting on it feels instant.
|
|
75
|
-
const HOVER_OPEN_DELAY = 450;
|
|
76
|
-
// Long enough to travel the gap between the launcher and the panel below it.
|
|
77
|
-
const HOVER_CLOSE_GRACE = 250;
|
|
78
73
|
// Flip back to true to restore `searchThreshold` opening the field on its own.
|
|
79
74
|
const SEARCH_OPENS_BY_COUNT = false;
|
|
80
75
|
const AppSwitcher = ({ apps, currentAppId, onSelect, onOpenChange, isSearchable, searchThreshold = 8, shortcut = null, showShortcutHint = true, hideWhenAlone = true, emptyMessage = "No app matches that name.", footer, ariaLabel = "Switch app", panelWidth, maxHeight = "24rem", isLoading = false, }) => {
|
|
@@ -85,11 +80,8 @@ const AppSwitcher = ({ apps, currentAppId, onSelect, onOpenChange, isSearchable,
|
|
|
85
80
|
const [activeIndex, setActiveIndex] = (0, react_1.useState)(0);
|
|
86
81
|
const searchRef = (0, react_1.useRef)(null);
|
|
87
82
|
const triggerRef = (0, react_1.useRef)(null);
|
|
88
|
-
const panelRef = (0, react_1.useRef)(null);
|
|
89
83
|
const itemRefs = (0, react_1.useRef)([]);
|
|
90
84
|
const typeahead = (0, react_1.useRef)({ buffer: "", at: 0 });
|
|
91
|
-
const hoverTimer = (0, react_1.useRef)();
|
|
92
|
-
const openedByHover = (0, react_1.useRef)(false);
|
|
93
85
|
(0, react_1.useEffect)(() => validate(apps), [apps]);
|
|
94
86
|
// Search is off while the panel's design settles: the app count alone no longer opens it. An
|
|
95
87
|
// explicit `isSearchable` still wins, so nothing about the prop changed.
|
|
@@ -106,41 +98,6 @@ const AppSwitcher = ({ apps, currentAppId, onSelect, onOpenChange, isSearchable,
|
|
|
106
98
|
}
|
|
107
99
|
onOpenChange === null || onOpenChange === void 0 ? void 0 : onOpenChange(next);
|
|
108
100
|
}, [onOpen, onClose, onOpenChange, requestExclusiveOpen]);
|
|
109
|
-
const cancelHoverTimer = (0, react_1.useCallback)(() => {
|
|
110
|
-
if (hoverTimer.current)
|
|
111
|
-
clearTimeout(hoverTimer.current);
|
|
112
|
-
hoverTimer.current = undefined;
|
|
113
|
-
}, []);
|
|
114
|
-
(0, react_1.useEffect)(() => cancelHoverTimer, [cancelHoverTimer]);
|
|
115
|
-
// Touch and stylus report no hover; a timer there would open the panel on a tap-and-hold.
|
|
116
|
-
const pointerHovers = () => {
|
|
117
|
-
var _a;
|
|
118
|
-
return typeof window !== "undefined" &&
|
|
119
|
-
((_a = window.matchMedia) === null || _a === void 0 ? void 0 : _a.call(window, "(hover: hover) and (pointer: fine)").matches);
|
|
120
|
-
};
|
|
121
|
-
const handleHoverStart = (0, react_1.useCallback)(() => {
|
|
122
|
-
if (isOpen || !pointerHovers())
|
|
123
|
-
return;
|
|
124
|
-
cancelHoverTimer();
|
|
125
|
-
hoverTimer.current = setTimeout(() => {
|
|
126
|
-
openedByHover.current = true;
|
|
127
|
-
setOpen(true);
|
|
128
|
-
}, HOVER_OPEN_DELAY);
|
|
129
|
-
}, [isOpen, cancelHoverTimer, setOpen]);
|
|
130
|
-
// A click-opened panel is sticky, and a keyboard user inside it is not "hovering away".
|
|
131
|
-
const handleHoverEnd = (0, react_1.useCallback)(() => {
|
|
132
|
-
cancelHoverTimer();
|
|
133
|
-
if (!isOpen || !openedByHover.current)
|
|
134
|
-
return;
|
|
135
|
-
hoverTimer.current = setTimeout(() => {
|
|
136
|
-
var _a;
|
|
137
|
-
if ((_a = panelRef.current) === null || _a === void 0 ? void 0 : _a.contains(document.activeElement))
|
|
138
|
-
return;
|
|
139
|
-
openedByHover.current = false;
|
|
140
|
-
setOpen(false);
|
|
141
|
-
setQuery("");
|
|
142
|
-
}, HOVER_CLOSE_GRACE);
|
|
143
|
-
}, [isOpen, cancelHoverTimer, setOpen]);
|
|
144
101
|
const closeAndRefocus = (0, react_1.useCallback)(() => {
|
|
145
102
|
var _a;
|
|
146
103
|
setOpen(false);
|
|
@@ -264,7 +221,7 @@ const AppSwitcher = ({ apps, currentAppId, onSelect, onOpenChange, isSearchable,
|
|
|
264
221
|
const currentSub = (0, outlineRung_1.liftToContrast)(theme.colors.textMuted, currentBg, accentText_1.AA_NORMAL_TEXT);
|
|
265
222
|
const width = panelWidth !== null && panelWidth !== void 0 ? panelWidth : "28rem";
|
|
266
223
|
const hint = shortcut && showShortcutHint ? (0, AppSwitcherProps_1.formatShortcut)(shortcut) : null;
|
|
267
|
-
return ((0, jsx_runtime_1.jsxs)(react_2.Popover, { isOpen: isOpen, onOpen: () => setOpen(true), onClose: () => { setOpen(false); setQuery(""); }, placement: "bottom-end", initialFocusRef: showSearch ? searchRef : undefined, isLazy: true, children: [(0, jsx_runtime_1.jsx)(react_2.PopoverTrigger, { children: (0, jsx_runtime_1.jsx)(react_2.Flex, { ref: triggerRef, as: "button", type: "button", "aria-label": ariaLabel, align: "center", justify: "center", boxSize: s.trigger, borderRadius: "lg", border: "1px solid", borderColor: isOpen ? theme.colors.boxborder[500] : theme.colors.boxborder[200], bg: isOpen ? theme.colors.backgroundColor.tertiary : "transparent", color: isOpen ? theme.colors.text[900] : theme.colors.textMuted, cursor: "pointer", transition: "background 0.15s ease, border-color 0.15s ease", _hover: { bg: rowHoverBg, color: theme.colors.text[900] },
|
|
224
|
+
return ((0, jsx_runtime_1.jsxs)(react_2.Popover, { isOpen: isOpen, onOpen: () => setOpen(true), onClose: () => { setOpen(false); setQuery(""); }, placement: "bottom-end", initialFocusRef: showSearch ? searchRef : undefined, isLazy: true, children: [(0, jsx_runtime_1.jsx)(react_2.PopoverTrigger, { children: (0, jsx_runtime_1.jsx)(react_2.Flex, { ref: triggerRef, as: "button", type: "button", "aria-label": ariaLabel, align: "center", justify: "center", boxSize: s.trigger, borderRadius: "lg", border: "1px solid", borderColor: isOpen ? theme.colors.boxborder[500] : theme.colors.boxborder[200], bg: isOpen ? theme.colors.backgroundColor.tertiary : "transparent", color: isOpen ? theme.colors.text[900] : theme.colors.textMuted, cursor: "pointer", transition: "background 0.15s ease, border-color 0.15s ease", _hover: { bg: rowHoverBg, color: theme.colors.text[900] }, onKeyDown: (e) => {
|
|
268
225
|
if (!isOpen) {
|
|
269
226
|
if (e.key === "ArrowDown") {
|
|
270
227
|
e.preventDefault();
|
|
@@ -273,7 +230,7 @@ const AppSwitcher = ({ apps, currentAppId, onSelect, onOpenChange, isSearchable,
|
|
|
273
230
|
return;
|
|
274
231
|
}
|
|
275
232
|
handlePanelKeyDown(e);
|
|
276
|
-
}, children: (0, jsx_runtime_1.jsx)(lucide_react_1.Grip, { size: s.icon + 1 }) }) }), (0, jsx_runtime_1.jsx)(react_2.PopoverContent, {
|
|
233
|
+
}, children: (0, jsx_runtime_1.jsx)(lucide_react_1.Grip, { size: s.icon + 1 }) }) }), (0, jsx_runtime_1.jsx)(react_2.PopoverContent, { w: width, maxW: "calc(100vw - 1.5rem)", textAlign: "start", onKeyDown: handlePanelKeyDown, bg: theme.colors.backgroundColor.light, borderRadius: "xl", overflow: "hidden", boxShadow: `0 10px 30px ${theme.colors.boxShadow.default}`, _focus: { outline: "none", boxShadow: `0 10px 30px ${theme.colors.boxShadow.default}` }, children: (0, jsx_runtime_1.jsxs)(react_2.PopoverBody, { p: 0, children: [(0, jsx_runtime_1.jsxs)(react_2.Flex, { align: "center", gap: 2, px: 3, pt: 3, pb: showSearch ? 2 : 1, children: [(0, jsx_runtime_1.jsx)(react_2.Text, { fontSize: "0.6875rem", fontWeight: 600, letterSpacing: "0.08em", textTransform: "uppercase", color: theme.colors.textMuted, children: HEADING }), (0, jsx_runtime_1.jsx)(react_2.Box, { flex: "1" }), hint && ((0, jsx_runtime_1.jsx)(react_2.Text, { as: "kbd", fontSize: "0.6875rem", fontFamily: "inherit", color: theme.colors.textMuted, border: "1px solid", borderRadius: "base", px: 1.5, py: 0.5, children: hint }))] }), showSearch && ((0, jsx_runtime_1.jsx)(react_2.Box, { px: 3, pb: 2, children: (0, jsx_runtime_1.jsxs)(react_2.InputGroup, { size: "sm", children: [(0, jsx_runtime_1.jsx)(react_2.InputLeftElement, { pointerEvents: "none", children: (0, jsx_runtime_1.jsx)(lucide_react_1.Search, { size: 15, color: theme.colors.textMuted }) }), (0, jsx_runtime_1.jsx)(react_2.Input, { ref: searchRef, value: query, onChange: (e) => setQuery(e.target.value), placeholder: "Search apps", borderRadius: "lg", "aria-label": "Search apps", onKeyDown: (e) => {
|
|
277
234
|
if (e.key === "ArrowDown" && visible.length > 0) {
|
|
278
235
|
e.preventDefault();
|
|
279
236
|
focusItem(0);
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { SegmentedControlProps } from "./SegmentedControlProps";
|
|
2
|
-
export default function SegmentedControl<T extends string = string>({ options, value, onChange, size, isFullWidth, "aria-label": ariaLabel, }: SegmentedControlProps<T>): import("react/jsx-runtime").JSX.Element;
|
|
2
|
+
export default function SegmentedControl<T extends string = string>({ options, value, onChange, size, isFullWidth, variant, "aria-label": ariaLabel, }: SegmentedControlProps<T>): import("react/jsx-runtime").JSX.Element;
|
|
@@ -5,18 +5,38 @@ const jsx_runtime_1 = require("react/jsx-runtime");
|
|
|
5
5
|
const react_1 = require("react");
|
|
6
6
|
const react_2 = require("@chakra-ui/react");
|
|
7
7
|
const useCustomTheme_1 = require("../../Theme/useCustomTheme");
|
|
8
|
+
const filledSurface_1 = require("../../Theme/tokens/builders/filledSurface");
|
|
8
9
|
const SIZES = {
|
|
9
10
|
sm: { h: "1.625rem", px: "0.625rem", fontSize: "0.75rem" },
|
|
10
11
|
md: { h: "2rem", px: "0.875rem", fontSize: "0.8125rem" },
|
|
11
12
|
};
|
|
12
|
-
function SegmentedControl({ options, value, onChange, size = "md", isFullWidth = false, "aria-label": ariaLabel, }) {
|
|
13
|
+
function SegmentedControl({ options, value, onChange, size = "md", isFullWidth = false, variant = "default", "aria-label": ariaLabel, }) {
|
|
13
14
|
const { colors } = (0, useCustomTheme_1.useCustomTheme)();
|
|
14
15
|
const dims = SIZES[size];
|
|
15
16
|
const refs = (0, react_1.useRef)([]);
|
|
17
|
+
const trackRef = (0, react_1.useRef)(null);
|
|
18
|
+
const isBrandSlide = variant === "brandSlide";
|
|
19
|
+
const isUnderlineSlide = variant === "underlineSlide";
|
|
20
|
+
const hasIndicator = isBrandSlide || isUnderlineSlide;
|
|
21
|
+
const brandFill = (0, filledSurface_1.resolveFilledSurface)(colors.primary, colors);
|
|
22
|
+
const [pill, setPill] = (0, react_1.useState)(null);
|
|
16
23
|
const selectedIndex = options.findIndex((o) => o.value === value);
|
|
17
24
|
const enabled = options.reduce((acc, o, i) => (o.isDisabled ? acc : [...acc, i]), []);
|
|
18
25
|
// WAI-APG: with nothing selected, the first enabled segment owns the tab stop.
|
|
19
26
|
const tabIndex = selectedIndex >= 0 ? selectedIndex : enabled[0];
|
|
27
|
+
(0, react_1.useLayoutEffect)(() => {
|
|
28
|
+
if (!hasIndicator)
|
|
29
|
+
return;
|
|
30
|
+
const track = trackRef.current;
|
|
31
|
+
const node = refs.current[selectedIndex];
|
|
32
|
+
if (!track || !node) {
|
|
33
|
+
setPill(null);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const trackRect = track.getBoundingClientRect();
|
|
37
|
+
const nodeRect = node.getBoundingClientRect();
|
|
38
|
+
setPill({ left: nodeRect.left - trackRect.left, width: nodeRect.width });
|
|
39
|
+
}, [hasIndicator, selectedIndex, options, isFullWidth, size]);
|
|
20
40
|
const selectAt = (index) => {
|
|
21
41
|
var _a;
|
|
22
42
|
(_a = refs.current[index]) === null || _a === void 0 ? void 0 : _a.focus();
|
|
@@ -46,13 +66,23 @@ function SegmentedControl({ options, value, onChange, size = "md", isFullWidth =
|
|
|
46
66
|
selectAt(enabled[enabled.length - 1]);
|
|
47
67
|
}
|
|
48
68
|
};
|
|
49
|
-
return ((0, jsx_runtime_1.
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
69
|
+
return ((0, jsx_runtime_1.jsxs)(react_2.Flex, { ref: trackRef, role: "radiogroup", "aria-label": ariaLabel, onKeyDown: handleKeyDown, position: "relative", display: "inline-flex", align: "center", gap: isUnderlineSlide ? "0" : "0.125rem", p: isUnderlineSlide ? "0" : "0.125rem", borderRadius: isUnderlineSlide ? "none" : "md", borderBottom: isUnderlineSlide ? `0.0625rem solid ${colors.boxborder[200]}` : undefined, bg: isBrandSlide ? colors.primary.opacity[8] : isUnderlineSlide ? "transparent" : colors.background[600], w: isFullWidth ? "100%" : "fit-content", children: [isBrandSlide && pill && ((0, jsx_runtime_1.jsx)(react_2.Box, { "aria-hidden": "true", position: "absolute", top: "0.125rem", left: 0, h: dims.h, w: `${pill.width}px`, borderRadius: "base", bg: brandFill.fill, boxShadow: `0 0.125rem 0.5rem ${colors.boxShadow.primary}`, transform: `translateX(${pill.left}px)`, transition: "transform 0.24s cubic-bezier(0.2, 0.8, 0.2, 1), width 0.24s cubic-bezier(0.2, 0.8, 0.2, 1)" })), isUnderlineSlide && pill && ((0, jsx_runtime_1.jsx)(react_2.Box, { "aria-hidden": "true", position: "absolute", bottom: "-0.0625rem", left: 0, h: "0.125rem", w: `${pill.width}px`, borderRadius: "full", bg: colors.primary[500], transform: `translateX(${pill.left}px)`, transition: "transform 0.24s cubic-bezier(0.2, 0.8, 0.2, 1), width 0.24s cubic-bezier(0.2, 0.8, 0.2, 1)" })), options.map((option, index) => {
|
|
70
|
+
const isSelected = option.value === value;
|
|
71
|
+
return ((0, jsx_runtime_1.jsxs)(react_2.Box, { as: "button", type: "button", ref: (node) => {
|
|
72
|
+
refs.current[index] = node;
|
|
73
|
+
}, role: "radio", "aria-checked": isSelected, disabled: option.isDisabled, tabIndex: index === tabIndex ? 0 : -1, onClick: () => onChange(option.value), position: "relative", zIndex: 1, display: "inline-flex", alignItems: "center", justifyContent: "center", gap: "0.375rem", flex: isFullWidth ? "1" : undefined, h: dims.h, px: dims.px, borderRadius: isUnderlineSlide ? "none" : "base", borderWidth: "1px", borderStyle: "solid", borderColor: hasIndicator
|
|
74
|
+
? "transparent"
|
|
75
|
+
: isSelected
|
|
76
|
+
? colors.primary.opacity[32]
|
|
77
|
+
: "transparent", bg: hasIndicator ? "transparent" : isSelected ? colors.backgroundColor.light : "transparent", boxShadow: hasIndicator ? "none" : isSelected ? "sm" : "none", color: isBrandSlide
|
|
78
|
+
? isSelected
|
|
79
|
+
? brandFill.color
|
|
80
|
+
: colors.text[600]
|
|
81
|
+
: isSelected
|
|
82
|
+
? colors.accentText
|
|
83
|
+
: colors.text[600], fontSize: dims.fontSize, fontWeight: isSelected ? 600 : 500, whiteSpace: "nowrap", opacity: option.isDisabled ? 0.5 : 1, cursor: option.isDisabled ? "not-allowed" : "pointer", transition: "color 0.2s ease", _hover: option.isDisabled || isSelected ? undefined : { color: colors.text[900] }, _focusVisible: {
|
|
84
|
+
outline: "none",
|
|
85
|
+
boxShadow: `0 0 0 2px ${colors.primary[300]}`,
|
|
86
|
+
}, children: [option.icon, option.label] }, option.value));
|
|
87
|
+
})] }));
|
|
58
88
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export type SegmentedControlSize = "sm" | "md";
|
|
2
|
+
export type SegmentedControlVariant = "default" | "brandSlide" | "underlineSlide";
|
|
2
3
|
export type SegmentedControlOption<T extends string = string> = {
|
|
3
4
|
value: T;
|
|
4
5
|
label: string;
|
|
@@ -12,5 +13,7 @@ export type SegmentedControlProps<T extends string = string> = {
|
|
|
12
13
|
onChange: (value: T) => void;
|
|
13
14
|
size?: SegmentedControlSize;
|
|
14
15
|
isFullWidth?: boolean;
|
|
16
|
+
/** brandSlide = primary view switcher; underlineSlide = dense toolbars/headers. */
|
|
17
|
+
variant?: SegmentedControlVariant;
|
|
15
18
|
"aria-label": string;
|
|
16
19
|
};
|
|
@@ -4,45 +4,95 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
7
|
-
const react_1 = require("
|
|
7
|
+
const react_1 = require("react");
|
|
8
|
+
const react_2 = require("@chakra-ui/react");
|
|
8
9
|
const lucide_react_1 = require("lucide-react");
|
|
9
10
|
const useCustomTheme_1 = require("../../Theme/useCustomTheme");
|
|
10
11
|
const outlineRung_1 = require("../../Theme/tokens/builders/outlineRung");
|
|
12
|
+
const filledSurface_1 = require("../../Theme/tokens/builders/filledSurface");
|
|
11
13
|
const ToolTip_1 = __importDefault(require("../ToolTip/ToolTip"));
|
|
12
|
-
const
|
|
14
|
+
const MODES = ["grid", "list"];
|
|
15
|
+
const TableToggle = ({ value, onChange, variant = "default" }) => {
|
|
13
16
|
const theme = (0, useCustomTheme_1.useCustomTheme)();
|
|
14
17
|
const { colors } = theme;
|
|
18
|
+
const isBrandSlide = variant === "brandSlide";
|
|
19
|
+
const isUnderlineSlide = variant === "underlineSlide";
|
|
20
|
+
const hasIndicator = isBrandSlide || isUnderlineSlide;
|
|
21
|
+
const brandFill = (0, filledSurface_1.resolveFilledSurface)(colors.primary, colors);
|
|
15
22
|
const restInk = (0, outlineRung_1.liftToContrast)(colors.background[800], colors.background[600], outlineRung_1.AA_NON_TEXT);
|
|
16
23
|
const hoverInk = (0, outlineRung_1.liftToContrast)(colors.background[900], colors.background[400], outlineRung_1.AA_NON_TEXT);
|
|
24
|
+
const trackRef = (0, react_1.useRef)(null);
|
|
25
|
+
const refs = (0, react_1.useRef)([]);
|
|
26
|
+
const [pill, setPill] = (0, react_1.useState)(null);
|
|
27
|
+
const selectedIndex = MODES.indexOf(value);
|
|
28
|
+
(0, react_1.useLayoutEffect)(() => {
|
|
29
|
+
if (!hasIndicator)
|
|
30
|
+
return;
|
|
31
|
+
const track = trackRef.current;
|
|
32
|
+
const node = refs.current[selectedIndex];
|
|
33
|
+
if (!track || !node) {
|
|
34
|
+
setPill(null);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const trackRect = track.getBoundingClientRect();
|
|
38
|
+
const nodeRect = node.getBoundingClientRect();
|
|
39
|
+
setPill({ left: nodeRect.left - trackRect.left, width: nodeRect.width });
|
|
40
|
+
}, [hasIndicator, selectedIndex]);
|
|
17
41
|
const styles = {
|
|
18
42
|
container: {
|
|
19
43
|
display: "flex",
|
|
20
44
|
alignItems: "center",
|
|
21
|
-
bg: colors.background[600],
|
|
22
|
-
p: "0.125rem",
|
|
23
|
-
borderRadius: "md",
|
|
45
|
+
bg: isBrandSlide ? colors.primary.opacity[8] : isUnderlineSlide ? "transparent" : colors.background[600],
|
|
46
|
+
p: isUnderlineSlide ? "0" : "0.125rem",
|
|
47
|
+
borderRadius: isUnderlineSlide ? "none" : "md",
|
|
48
|
+
borderBottom: isUnderlineSlide ? `0.0625rem solid ${colors.boxborder[200]}` : undefined,
|
|
24
49
|
height: "2rem",
|
|
25
|
-
gap: "0.125rem",
|
|
50
|
+
gap: isUnderlineSlide ? "0" : "0.125rem",
|
|
26
51
|
width: "6.3rem",
|
|
52
|
+
position: "relative",
|
|
27
53
|
},
|
|
28
54
|
button: (mode) => ({
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
55
|
+
position: "relative",
|
|
56
|
+
zIndex: 1,
|
|
57
|
+
bg: hasIndicator ? "transparent" : value === mode ? colors.backgroundColor.light : "transparent",
|
|
58
|
+
boxShadow: hasIndicator ? "none" : value === mode ? "md" : "none",
|
|
59
|
+
color: isBrandSlide
|
|
60
|
+
? value === mode
|
|
61
|
+
? brandFill.color
|
|
62
|
+
: restInk
|
|
63
|
+
: value === mode
|
|
64
|
+
? colors.accentText
|
|
65
|
+
: restInk,
|
|
66
|
+
borderRadius: isUnderlineSlide ? "none" : "base",
|
|
33
67
|
height: "1.75rem",
|
|
34
|
-
border: "0.031rem solid",
|
|
35
|
-
borderColor:
|
|
68
|
+
border: hasIndicator ? "none" : "0.031rem solid",
|
|
69
|
+
borderColor: hasIndicator
|
|
70
|
+
? "transparent"
|
|
71
|
+
: value === mode
|
|
72
|
+
? colors.primary.opacity[32]
|
|
73
|
+
: "transparent",
|
|
36
74
|
padding: "0.313rem 0.813rem",
|
|
37
75
|
_hover: {
|
|
38
|
-
bg:
|
|
39
|
-
?
|
|
40
|
-
:
|
|
41
|
-
|
|
76
|
+
bg: hasIndicator
|
|
77
|
+
? "transparent"
|
|
78
|
+
: value === mode
|
|
79
|
+
? colors.backgroundColor.light
|
|
80
|
+
: colors.background[400],
|
|
81
|
+
color: isBrandSlide
|
|
82
|
+
? value === mode
|
|
83
|
+
? brandFill.color
|
|
84
|
+
: hoverInk
|
|
85
|
+
: value === mode
|
|
86
|
+
? colors.accentText
|
|
87
|
+
: hoverInk,
|
|
42
88
|
},
|
|
43
|
-
transition: "
|
|
89
|
+
transition: "color 0.2s ease-in-out",
|
|
44
90
|
}),
|
|
45
91
|
};
|
|
46
|
-
return ((0, jsx_runtime_1.jsxs)(
|
|
92
|
+
return ((0, jsx_runtime_1.jsxs)(react_2.Box, { ref: trackRef, ...styles.container, children: [isBrandSlide && pill && ((0, jsx_runtime_1.jsx)(react_2.Box, { "aria-hidden": "true", position: "absolute", top: "0.125rem", left: 0, h: "1.75rem", w: `${pill.width}px`, borderRadius: "base", bg: brandFill.fill, boxShadow: `0 0.125rem 0.5rem ${colors.boxShadow.primary}`, transform: `translateX(${pill.left}px)`, transition: "transform 0.24s cubic-bezier(0.2, 0.8, 0.2, 1), width 0.24s cubic-bezier(0.2, 0.8, 0.2, 1)" })), isUnderlineSlide && pill && ((0, jsx_runtime_1.jsx)(react_2.Box, { "aria-hidden": "true", position: "absolute", bottom: "-0.0625rem", left: 0, h: "0.125rem", w: `${pill.width}px`, borderRadius: "full", bg: colors.primary[500], transform: `translateX(${pill.left}px)`, transition: "transform 0.24s cubic-bezier(0.2, 0.8, 0.2, 1), width 0.24s cubic-bezier(0.2, 0.8, 0.2, 1)" })), (0, jsx_runtime_1.jsx)(ToolTip_1.default, { label: "Table View", placement: "top", fontSize: 12, hasArrow: true, children: (0, jsx_runtime_1.jsx)(react_2.IconButton, { ref: (node) => {
|
|
93
|
+
refs.current[0] = node;
|
|
94
|
+
}, "aria-label": "Grid View", icon: (0, jsx_runtime_1.jsx)(lucide_react_1.Table2, { size: "1.25rem" }), variant: "ghost", onClick: () => onChange("grid"), ...styles.button("grid") }) }), (0, jsx_runtime_1.jsx)(ToolTip_1.default, { label: "Kanban View", placement: "top", fontSize: 12, hasArrow: true, children: (0, jsx_runtime_1.jsx)(react_2.IconButton, { ref: (node) => {
|
|
95
|
+
refs.current[1] = node;
|
|
96
|
+
}, "aria-label": "List View", icon: (0, jsx_runtime_1.jsx)(lucide_react_1.Columns3, { size: "1.25rem" }), variant: "ghost", onClick: () => onChange("list"), ...styles.button("list") }) })] }));
|
|
47
97
|
};
|
|
48
98
|
exports.default = TableToggle;
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
export type ViewMode = "grid" | "list";
|
|
2
|
+
export type TableToggleVariant = "default" | "brandSlide" | "underlineSlide";
|
|
2
3
|
export type ViewToggleProps = {
|
|
3
4
|
value: ViewMode;
|
|
4
5
|
onChange: (val: ViewMode) => void;
|
|
6
|
+
/** brandSlide = primary view switcher; underlineSlide = dense toolbars/headers. */
|
|
7
|
+
variant?: TableToggleVariant;
|
|
5
8
|
};
|
|
@@ -14,5 +14,5 @@ function ToggleSwitch({ isChecked, onChange, isDisabled = false, size = "md", id
|
|
|
14
14
|
return ((0, jsx_runtime_1.jsx)(react_1.Box, { as: "button", type: "button", id: id, role: "switch", "aria-checked": isChecked, "aria-label": ariaLabel, "aria-describedby": ariaDescribedBy, disabled: isDisabled, onClick: () => onChange(!isChecked), position: "relative", display: "inline-flex", alignItems: "center", flexShrink: 0, w: dims.w, h: dims.h, p: 0, borderRadius: "full", borderWidth: "1px", borderStyle: "solid", borderColor: isChecked ? colors.primary[500] : colors.boxborder[500], bg: isChecked ? colors.primary[500] : colors.background[600], opacity: isDisabled ? 0.5 : 1, cursor: isDisabled ? "not-allowed" : "pointer", transition: "background-color 0.2s ease, border-color 0.2s ease", _focusVisible: {
|
|
15
15
|
outline: "none",
|
|
16
16
|
boxShadow: `0 0 0 2px ${colors.primary[300]}`,
|
|
17
|
-
}, children: (0, jsx_runtime_1.jsx)(react_1.Box, { "aria-hidden": "true", position: "absolute", left: "0.125rem", w: dims.knob, h: dims.knob, borderRadius: "full", bg: colors.
|
|
17
|
+
}, children: (0, jsx_runtime_1.jsx)(react_1.Box, { "aria-hidden": "true", position: "absolute", left: "0.125rem", w: dims.knob, h: dims.knob, borderRadius: "full", bg: colors.white, boxShadow: "sm", transition: "transform 0.2s ease", transform: isChecked ? `translateX(${dims.travel})` : "translateX(0)" }) }));
|
|
18
18
|
}
|
|
@@ -35,11 +35,6 @@ const validate = (apps) => {
|
|
|
35
35
|
warnOnce(`url-${app.id || i}`, `app "${app.name}" has no url; it cannot be opened.`);
|
|
36
36
|
});
|
|
37
37
|
};
|
|
38
|
-
// Past a pointer's pause threshold, so crossing the launcher on the way elsewhere does not open it,
|
|
39
|
-
// but short enough that deliberately resting on it feels instant.
|
|
40
|
-
const HOVER_OPEN_DELAY = 450;
|
|
41
|
-
// Long enough to travel the gap between the launcher and the panel below it.
|
|
42
|
-
const HOVER_CLOSE_GRACE = 250;
|
|
43
38
|
// Flip back to true to restore `searchThreshold` opening the field on its own.
|
|
44
39
|
const SEARCH_OPENS_BY_COUNT = false;
|
|
45
40
|
const AppSwitcher = ({ apps, currentAppId, onSelect, onOpenChange, isSearchable, searchThreshold = 8, shortcut = null, showShortcutHint = true, hideWhenAlone = true, emptyMessage = "No app matches that name.", footer, ariaLabel = "Switch app", panelWidth, maxHeight = "24rem", isLoading = false, }) => {
|
|
@@ -50,11 +45,8 @@ const AppSwitcher = ({ apps, currentAppId, onSelect, onOpenChange, isSearchable,
|
|
|
50
45
|
const [activeIndex, setActiveIndex] = useState(0);
|
|
51
46
|
const searchRef = useRef(null);
|
|
52
47
|
const triggerRef = useRef(null);
|
|
53
|
-
const panelRef = useRef(null);
|
|
54
48
|
const itemRefs = useRef([]);
|
|
55
49
|
const typeahead = useRef({ buffer: "", at: 0 });
|
|
56
|
-
const hoverTimer = useRef();
|
|
57
|
-
const openedByHover = useRef(false);
|
|
58
50
|
useEffect(() => validate(apps), [apps]);
|
|
59
51
|
// Search is off while the panel's design settles: the app count alone no longer opens it. An
|
|
60
52
|
// explicit `isSearchable` still wins, so nothing about the prop changed.
|
|
@@ -71,41 +63,6 @@ const AppSwitcher = ({ apps, currentAppId, onSelect, onOpenChange, isSearchable,
|
|
|
71
63
|
}
|
|
72
64
|
onOpenChange === null || onOpenChange === void 0 ? void 0 : onOpenChange(next);
|
|
73
65
|
}, [onOpen, onClose, onOpenChange, requestExclusiveOpen]);
|
|
74
|
-
const cancelHoverTimer = useCallback(() => {
|
|
75
|
-
if (hoverTimer.current)
|
|
76
|
-
clearTimeout(hoverTimer.current);
|
|
77
|
-
hoverTimer.current = undefined;
|
|
78
|
-
}, []);
|
|
79
|
-
useEffect(() => cancelHoverTimer, [cancelHoverTimer]);
|
|
80
|
-
// Touch and stylus report no hover; a timer there would open the panel on a tap-and-hold.
|
|
81
|
-
const pointerHovers = () => {
|
|
82
|
-
var _a;
|
|
83
|
-
return typeof window !== "undefined" &&
|
|
84
|
-
((_a = window.matchMedia) === null || _a === void 0 ? void 0 : _a.call(window, "(hover: hover) and (pointer: fine)").matches);
|
|
85
|
-
};
|
|
86
|
-
const handleHoverStart = useCallback(() => {
|
|
87
|
-
if (isOpen || !pointerHovers())
|
|
88
|
-
return;
|
|
89
|
-
cancelHoverTimer();
|
|
90
|
-
hoverTimer.current = setTimeout(() => {
|
|
91
|
-
openedByHover.current = true;
|
|
92
|
-
setOpen(true);
|
|
93
|
-
}, HOVER_OPEN_DELAY);
|
|
94
|
-
}, [isOpen, cancelHoverTimer, setOpen]);
|
|
95
|
-
// A click-opened panel is sticky, and a keyboard user inside it is not "hovering away".
|
|
96
|
-
const handleHoverEnd = useCallback(() => {
|
|
97
|
-
cancelHoverTimer();
|
|
98
|
-
if (!isOpen || !openedByHover.current)
|
|
99
|
-
return;
|
|
100
|
-
hoverTimer.current = setTimeout(() => {
|
|
101
|
-
var _a;
|
|
102
|
-
if ((_a = panelRef.current) === null || _a === void 0 ? void 0 : _a.contains(document.activeElement))
|
|
103
|
-
return;
|
|
104
|
-
openedByHover.current = false;
|
|
105
|
-
setOpen(false);
|
|
106
|
-
setQuery("");
|
|
107
|
-
}, HOVER_CLOSE_GRACE);
|
|
108
|
-
}, [isOpen, cancelHoverTimer, setOpen]);
|
|
109
66
|
const closeAndRefocus = useCallback(() => {
|
|
110
67
|
var _a;
|
|
111
68
|
setOpen(false);
|
|
@@ -229,7 +186,7 @@ const AppSwitcher = ({ apps, currentAppId, onSelect, onOpenChange, isSearchable,
|
|
|
229
186
|
const currentSub = liftToContrast(theme.colors.textMuted, currentBg, AA_NORMAL_TEXT);
|
|
230
187
|
const width = panelWidth !== null && panelWidth !== void 0 ? panelWidth : "28rem";
|
|
231
188
|
const hint = shortcut && showShortcutHint ? formatShortcut(shortcut) : null;
|
|
232
|
-
return (_jsxs(Popover, { isOpen: isOpen, onOpen: () => setOpen(true), onClose: () => { setOpen(false); setQuery(""); }, placement: "bottom-end", initialFocusRef: showSearch ? searchRef : undefined, isLazy: true, children: [_jsx(PopoverTrigger, { children: _jsx(Flex, { ref: triggerRef, as: "button", type: "button", "aria-label": ariaLabel, align: "center", justify: "center", boxSize: s.trigger, borderRadius: "lg", border: "1px solid", borderColor: isOpen ? theme.colors.boxborder[500] : theme.colors.boxborder[200], bg: isOpen ? theme.colors.backgroundColor.tertiary : "transparent", color: isOpen ? theme.colors.text[900] : theme.colors.textMuted, cursor: "pointer", transition: "background 0.15s ease, border-color 0.15s ease", _hover: { bg: rowHoverBg, color: theme.colors.text[900] },
|
|
189
|
+
return (_jsxs(Popover, { isOpen: isOpen, onOpen: () => setOpen(true), onClose: () => { setOpen(false); setQuery(""); }, placement: "bottom-end", initialFocusRef: showSearch ? searchRef : undefined, isLazy: true, children: [_jsx(PopoverTrigger, { children: _jsx(Flex, { ref: triggerRef, as: "button", type: "button", "aria-label": ariaLabel, align: "center", justify: "center", boxSize: s.trigger, borderRadius: "lg", border: "1px solid", borderColor: isOpen ? theme.colors.boxborder[500] : theme.colors.boxborder[200], bg: isOpen ? theme.colors.backgroundColor.tertiary : "transparent", color: isOpen ? theme.colors.text[900] : theme.colors.textMuted, cursor: "pointer", transition: "background 0.15s ease, border-color 0.15s ease", _hover: { bg: rowHoverBg, color: theme.colors.text[900] }, onKeyDown: (e) => {
|
|
233
190
|
if (!isOpen) {
|
|
234
191
|
if (e.key === "ArrowDown") {
|
|
235
192
|
e.preventDefault();
|
|
@@ -238,7 +195,7 @@ const AppSwitcher = ({ apps, currentAppId, onSelect, onOpenChange, isSearchable,
|
|
|
238
195
|
return;
|
|
239
196
|
}
|
|
240
197
|
handlePanelKeyDown(e);
|
|
241
|
-
}, children: _jsx(Grip, { size: s.icon + 1 }) }) }), _jsx(PopoverContent, {
|
|
198
|
+
}, children: _jsx(Grip, { size: s.icon + 1 }) }) }), _jsx(PopoverContent, { w: width, maxW: "calc(100vw - 1.5rem)", textAlign: "start", onKeyDown: handlePanelKeyDown, bg: theme.colors.backgroundColor.light, borderRadius: "xl", overflow: "hidden", boxShadow: `0 10px 30px ${theme.colors.boxShadow.default}`, _focus: { outline: "none", boxShadow: `0 10px 30px ${theme.colors.boxShadow.default}` }, children: _jsxs(PopoverBody, { p: 0, children: [_jsxs(Flex, { align: "center", gap: 2, px: 3, pt: 3, pb: showSearch ? 2 : 1, children: [_jsx(Text, { fontSize: "0.6875rem", fontWeight: 600, letterSpacing: "0.08em", textTransform: "uppercase", color: theme.colors.textMuted, children: HEADING }), _jsx(Box, { flex: "1" }), hint && (_jsx(Text, { as: "kbd", fontSize: "0.6875rem", fontFamily: "inherit", color: theme.colors.textMuted, border: "1px solid", borderRadius: "base", px: 1.5, py: 0.5, children: hint }))] }), showSearch && (_jsx(Box, { px: 3, pb: 2, children: _jsxs(InputGroup, { size: "sm", children: [_jsx(InputLeftElement, { pointerEvents: "none", children: _jsx(Search, { size: 15, color: theme.colors.textMuted }) }), _jsx(Input, { ref: searchRef, value: query, onChange: (e) => setQuery(e.target.value), placeholder: "Search apps", borderRadius: "lg", "aria-label": "Search apps", onKeyDown: (e) => {
|
|
242
199
|
if (e.key === "ArrowDown" && visible.length > 0) {
|
|
243
200
|
e.preventDefault();
|
|
244
201
|
focusItem(0);
|
|
@@ -1,19 +1,39 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { useRef } from "react";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useLayoutEffect, useRef, useState } from "react";
|
|
3
3
|
import { Box, Flex } from "@chakra-ui/react";
|
|
4
4
|
import { useCustomTheme } from "../../Theme/useCustomTheme.js";
|
|
5
|
+
import { resolveFilledSurface } from "../../Theme/tokens/builders/filledSurface.js";
|
|
5
6
|
const SIZES = {
|
|
6
7
|
sm: { h: "1.625rem", px: "0.625rem", fontSize: "0.75rem" },
|
|
7
8
|
md: { h: "2rem", px: "0.875rem", fontSize: "0.8125rem" },
|
|
8
9
|
};
|
|
9
|
-
export default function SegmentedControl({ options, value, onChange, size = "md", isFullWidth = false, "aria-label": ariaLabel, }) {
|
|
10
|
+
export default function SegmentedControl({ options, value, onChange, size = "md", isFullWidth = false, variant = "default", "aria-label": ariaLabel, }) {
|
|
10
11
|
const { colors } = useCustomTheme();
|
|
11
12
|
const dims = SIZES[size];
|
|
12
13
|
const refs = useRef([]);
|
|
14
|
+
const trackRef = useRef(null);
|
|
15
|
+
const isBrandSlide = variant === "brandSlide";
|
|
16
|
+
const isUnderlineSlide = variant === "underlineSlide";
|
|
17
|
+
const hasIndicator = isBrandSlide || isUnderlineSlide;
|
|
18
|
+
const brandFill = resolveFilledSurface(colors.primary, colors);
|
|
19
|
+
const [pill, setPill] = useState(null);
|
|
13
20
|
const selectedIndex = options.findIndex((o) => o.value === value);
|
|
14
21
|
const enabled = options.reduce((acc, o, i) => (o.isDisabled ? acc : [...acc, i]), []);
|
|
15
22
|
// WAI-APG: with nothing selected, the first enabled segment owns the tab stop.
|
|
16
23
|
const tabIndex = selectedIndex >= 0 ? selectedIndex : enabled[0];
|
|
24
|
+
useLayoutEffect(() => {
|
|
25
|
+
if (!hasIndicator)
|
|
26
|
+
return;
|
|
27
|
+
const track = trackRef.current;
|
|
28
|
+
const node = refs.current[selectedIndex];
|
|
29
|
+
if (!track || !node) {
|
|
30
|
+
setPill(null);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const trackRect = track.getBoundingClientRect();
|
|
34
|
+
const nodeRect = node.getBoundingClientRect();
|
|
35
|
+
setPill({ left: nodeRect.left - trackRect.left, width: nodeRect.width });
|
|
36
|
+
}, [hasIndicator, selectedIndex, options, isFullWidth, size]);
|
|
17
37
|
const selectAt = (index) => {
|
|
18
38
|
var _a;
|
|
19
39
|
(_a = refs.current[index]) === null || _a === void 0 ? void 0 : _a.focus();
|
|
@@ -43,13 +63,23 @@ export default function SegmentedControl({ options, value, onChange, size = "md"
|
|
|
43
63
|
selectAt(enabled[enabled.length - 1]);
|
|
44
64
|
}
|
|
45
65
|
};
|
|
46
|
-
return (
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
66
|
+
return (_jsxs(Flex, { ref: trackRef, role: "radiogroup", "aria-label": ariaLabel, onKeyDown: handleKeyDown, position: "relative", display: "inline-flex", align: "center", gap: isUnderlineSlide ? "0" : "0.125rem", p: isUnderlineSlide ? "0" : "0.125rem", borderRadius: isUnderlineSlide ? "none" : "md", borderBottom: isUnderlineSlide ? `0.0625rem solid ${colors.boxborder[200]}` : undefined, bg: isBrandSlide ? colors.primary.opacity[8] : isUnderlineSlide ? "transparent" : colors.background[600], w: isFullWidth ? "100%" : "fit-content", children: [isBrandSlide && pill && (_jsx(Box, { "aria-hidden": "true", position: "absolute", top: "0.125rem", left: 0, h: dims.h, w: `${pill.width}px`, borderRadius: "base", bg: brandFill.fill, boxShadow: `0 0.125rem 0.5rem ${colors.boxShadow.primary}`, transform: `translateX(${pill.left}px)`, transition: "transform 0.24s cubic-bezier(0.2, 0.8, 0.2, 1), width 0.24s cubic-bezier(0.2, 0.8, 0.2, 1)" })), isUnderlineSlide && pill && (_jsx(Box, { "aria-hidden": "true", position: "absolute", bottom: "-0.0625rem", left: 0, h: "0.125rem", w: `${pill.width}px`, borderRadius: "full", bg: colors.primary[500], transform: `translateX(${pill.left}px)`, transition: "transform 0.24s cubic-bezier(0.2, 0.8, 0.2, 1), width 0.24s cubic-bezier(0.2, 0.8, 0.2, 1)" })), options.map((option, index) => {
|
|
67
|
+
const isSelected = option.value === value;
|
|
68
|
+
return (_jsxs(Box, { as: "button", type: "button", ref: (node) => {
|
|
69
|
+
refs.current[index] = node;
|
|
70
|
+
}, role: "radio", "aria-checked": isSelected, disabled: option.isDisabled, tabIndex: index === tabIndex ? 0 : -1, onClick: () => onChange(option.value), position: "relative", zIndex: 1, display: "inline-flex", alignItems: "center", justifyContent: "center", gap: "0.375rem", flex: isFullWidth ? "1" : undefined, h: dims.h, px: dims.px, borderRadius: isUnderlineSlide ? "none" : "base", borderWidth: "1px", borderStyle: "solid", borderColor: hasIndicator
|
|
71
|
+
? "transparent"
|
|
72
|
+
: isSelected
|
|
73
|
+
? colors.primary.opacity[32]
|
|
74
|
+
: "transparent", bg: hasIndicator ? "transparent" : isSelected ? colors.backgroundColor.light : "transparent", boxShadow: hasIndicator ? "none" : isSelected ? "sm" : "none", color: isBrandSlide
|
|
75
|
+
? isSelected
|
|
76
|
+
? brandFill.color
|
|
77
|
+
: colors.text[600]
|
|
78
|
+
: isSelected
|
|
79
|
+
? colors.accentText
|
|
80
|
+
: colors.text[600], fontSize: dims.fontSize, fontWeight: isSelected ? 600 : 500, whiteSpace: "nowrap", opacity: option.isDisabled ? 0.5 : 1, cursor: option.isDisabled ? "not-allowed" : "pointer", transition: "color 0.2s ease", _hover: option.isDisabled || isSelected ? undefined : { color: colors.text[900] }, _focusVisible: {
|
|
81
|
+
outline: "none",
|
|
82
|
+
boxShadow: `0 0 0 2px ${colors.primary[300]}`,
|
|
83
|
+
}, children: [option.icon, option.label] }, option.value));
|
|
84
|
+
})] }));
|
|
55
85
|
}
|
|
@@ -1,43 +1,93 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useLayoutEffect, useRef, useState } from "react";
|
|
2
3
|
import { Box, IconButton } from "@chakra-ui/react";
|
|
3
4
|
import { Columns3, Table2 } from "lucide-react";
|
|
4
5
|
import { useCustomTheme } from "../../Theme/useCustomTheme.js";
|
|
5
6
|
import { AA_NON_TEXT, liftToContrast } from "../../Theme/tokens/builders/outlineRung.js";
|
|
7
|
+
import { resolveFilledSurface } from "../../Theme/tokens/builders/filledSurface.js";
|
|
6
8
|
import ToolTip from "../ToolTip/ToolTip.js";
|
|
7
|
-
const
|
|
9
|
+
const MODES = ["grid", "list"];
|
|
10
|
+
const TableToggle = ({ value, onChange, variant = "default" }) => {
|
|
8
11
|
const theme = useCustomTheme();
|
|
9
12
|
const { colors } = theme;
|
|
13
|
+
const isBrandSlide = variant === "brandSlide";
|
|
14
|
+
const isUnderlineSlide = variant === "underlineSlide";
|
|
15
|
+
const hasIndicator = isBrandSlide || isUnderlineSlide;
|
|
16
|
+
const brandFill = resolveFilledSurface(colors.primary, colors);
|
|
10
17
|
const restInk = liftToContrast(colors.background[800], colors.background[600], AA_NON_TEXT);
|
|
11
18
|
const hoverInk = liftToContrast(colors.background[900], colors.background[400], AA_NON_TEXT);
|
|
19
|
+
const trackRef = useRef(null);
|
|
20
|
+
const refs = useRef([]);
|
|
21
|
+
const [pill, setPill] = useState(null);
|
|
22
|
+
const selectedIndex = MODES.indexOf(value);
|
|
23
|
+
useLayoutEffect(() => {
|
|
24
|
+
if (!hasIndicator)
|
|
25
|
+
return;
|
|
26
|
+
const track = trackRef.current;
|
|
27
|
+
const node = refs.current[selectedIndex];
|
|
28
|
+
if (!track || !node) {
|
|
29
|
+
setPill(null);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const trackRect = track.getBoundingClientRect();
|
|
33
|
+
const nodeRect = node.getBoundingClientRect();
|
|
34
|
+
setPill({ left: nodeRect.left - trackRect.left, width: nodeRect.width });
|
|
35
|
+
}, [hasIndicator, selectedIndex]);
|
|
12
36
|
const styles = {
|
|
13
37
|
container: {
|
|
14
38
|
display: "flex",
|
|
15
39
|
alignItems: "center",
|
|
16
|
-
bg: colors.background[600],
|
|
17
|
-
p: "0.125rem",
|
|
18
|
-
borderRadius: "md",
|
|
40
|
+
bg: isBrandSlide ? colors.primary.opacity[8] : isUnderlineSlide ? "transparent" : colors.background[600],
|
|
41
|
+
p: isUnderlineSlide ? "0" : "0.125rem",
|
|
42
|
+
borderRadius: isUnderlineSlide ? "none" : "md",
|
|
43
|
+
borderBottom: isUnderlineSlide ? `0.0625rem solid ${colors.boxborder[200]}` : undefined,
|
|
19
44
|
height: "2rem",
|
|
20
|
-
gap: "0.125rem",
|
|
45
|
+
gap: isUnderlineSlide ? "0" : "0.125rem",
|
|
21
46
|
width: "6.3rem",
|
|
47
|
+
position: "relative",
|
|
22
48
|
},
|
|
23
49
|
button: (mode) => ({
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
50
|
+
position: "relative",
|
|
51
|
+
zIndex: 1,
|
|
52
|
+
bg: hasIndicator ? "transparent" : value === mode ? colors.backgroundColor.light : "transparent",
|
|
53
|
+
boxShadow: hasIndicator ? "none" : value === mode ? "md" : "none",
|
|
54
|
+
color: isBrandSlide
|
|
55
|
+
? value === mode
|
|
56
|
+
? brandFill.color
|
|
57
|
+
: restInk
|
|
58
|
+
: value === mode
|
|
59
|
+
? colors.accentText
|
|
60
|
+
: restInk,
|
|
61
|
+
borderRadius: isUnderlineSlide ? "none" : "base",
|
|
28
62
|
height: "1.75rem",
|
|
29
|
-
border: "0.031rem solid",
|
|
30
|
-
borderColor:
|
|
63
|
+
border: hasIndicator ? "none" : "0.031rem solid",
|
|
64
|
+
borderColor: hasIndicator
|
|
65
|
+
? "transparent"
|
|
66
|
+
: value === mode
|
|
67
|
+
? colors.primary.opacity[32]
|
|
68
|
+
: "transparent",
|
|
31
69
|
padding: "0.313rem 0.813rem",
|
|
32
70
|
_hover: {
|
|
33
|
-
bg:
|
|
34
|
-
?
|
|
35
|
-
:
|
|
36
|
-
|
|
71
|
+
bg: hasIndicator
|
|
72
|
+
? "transparent"
|
|
73
|
+
: value === mode
|
|
74
|
+
? colors.backgroundColor.light
|
|
75
|
+
: colors.background[400],
|
|
76
|
+
color: isBrandSlide
|
|
77
|
+
? value === mode
|
|
78
|
+
? brandFill.color
|
|
79
|
+
: hoverInk
|
|
80
|
+
: value === mode
|
|
81
|
+
? colors.accentText
|
|
82
|
+
: hoverInk,
|
|
37
83
|
},
|
|
38
|
-
transition: "
|
|
84
|
+
transition: "color 0.2s ease-in-out",
|
|
39
85
|
}),
|
|
40
86
|
};
|
|
41
|
-
return (_jsxs(Box, { ...styles.container, children: [_jsx(
|
|
87
|
+
return (_jsxs(Box, { ref: trackRef, ...styles.container, children: [isBrandSlide && pill && (_jsx(Box, { "aria-hidden": "true", position: "absolute", top: "0.125rem", left: 0, h: "1.75rem", w: `${pill.width}px`, borderRadius: "base", bg: brandFill.fill, boxShadow: `0 0.125rem 0.5rem ${colors.boxShadow.primary}`, transform: `translateX(${pill.left}px)`, transition: "transform 0.24s cubic-bezier(0.2, 0.8, 0.2, 1), width 0.24s cubic-bezier(0.2, 0.8, 0.2, 1)" })), isUnderlineSlide && pill && (_jsx(Box, { "aria-hidden": "true", position: "absolute", bottom: "-0.0625rem", left: 0, h: "0.125rem", w: `${pill.width}px`, borderRadius: "full", bg: colors.primary[500], transform: `translateX(${pill.left}px)`, transition: "transform 0.24s cubic-bezier(0.2, 0.8, 0.2, 1), width 0.24s cubic-bezier(0.2, 0.8, 0.2, 1)" })), _jsx(ToolTip, { label: "Table View", placement: "top", fontSize: 12, hasArrow: true, children: _jsx(IconButton, { ref: (node) => {
|
|
88
|
+
refs.current[0] = node;
|
|
89
|
+
}, "aria-label": "Grid View", icon: _jsx(Table2, { size: "1.25rem" }), variant: "ghost", onClick: () => onChange("grid"), ...styles.button("grid") }) }), _jsx(ToolTip, { label: "Kanban View", placement: "top", fontSize: 12, hasArrow: true, children: _jsx(IconButton, { ref: (node) => {
|
|
90
|
+
refs.current[1] = node;
|
|
91
|
+
}, "aria-label": "List View", icon: _jsx(Columns3, { size: "1.25rem" }), variant: "ghost", onClick: () => onChange("list"), ...styles.button("list") }) })] }));
|
|
42
92
|
};
|
|
43
93
|
export default TableToggle;
|
|
@@ -11,5 +11,5 @@ export default function ToggleSwitch({ isChecked, onChange, isDisabled = false,
|
|
|
11
11
|
return (_jsx(Box, { as: "button", type: "button", id: id, role: "switch", "aria-checked": isChecked, "aria-label": ariaLabel, "aria-describedby": ariaDescribedBy, disabled: isDisabled, onClick: () => onChange(!isChecked), position: "relative", display: "inline-flex", alignItems: "center", flexShrink: 0, w: dims.w, h: dims.h, p: 0, borderRadius: "full", borderWidth: "1px", borderStyle: "solid", borderColor: isChecked ? colors.primary[500] : colors.boxborder[500], bg: isChecked ? colors.primary[500] : colors.background[600], opacity: isDisabled ? 0.5 : 1, cursor: isDisabled ? "not-allowed" : "pointer", transition: "background-color 0.2s ease, border-color 0.2s ease", _focusVisible: {
|
|
12
12
|
outline: "none",
|
|
13
13
|
boxShadow: `0 0 0 2px ${colors.primary[300]}`,
|
|
14
|
-
}, children: _jsx(Box, { "aria-hidden": "true", position: "absolute", left: "0.125rem", w: dims.knob, h: dims.knob, borderRadius: "full", bg: colors.
|
|
14
|
+
}, children: _jsx(Box, { "aria-hidden": "true", position: "absolute", left: "0.125rem", w: dims.knob, h: dims.knob, borderRadius: "full", bg: colors.white, boxShadow: "sm", transition: "transform 0.2s ease", transform: isChecked ? `translateX(${dims.travel})` : "translateX(0)" }) }));
|
|
15
15
|
}
|
package/package.json
CHANGED