pixelize-design-library 2.4.2-beta.58 → 2.4.2-beta.59

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.
Files changed (123) hide show
  1. package/CHANGELOG.md +75 -0
  2. package/COMPONENT-GOTCHAS.md +88 -2
  3. package/DESIGN-GUIDE.md +26 -3
  4. package/dist/Components/AppSwitcher/AppSwitcher.js +5 -1
  5. package/dist/Components/AppSwitcher/AppSwitcherProps.d.ts +2 -0
  6. package/dist/Components/AppSwitcher/AppSwitcherProps.js +7 -4
  7. package/dist/Components/Breadcrumbs/Breadcrumbs.d.ts +1 -1
  8. package/dist/Components/Breadcrumbs/Breadcrumbs.js +33 -10
  9. package/dist/Components/Breadcrumbs/BreadcrumbsProps.d.ts +16 -2
  10. package/dist/Components/Card/Card.d.ts +3 -1
  11. package/dist/Components/Card/Card.js +12 -2
  12. package/dist/Components/Card/CardProps.d.ts +5 -1
  13. package/dist/Components/Editor/Editor.js +50 -2
  14. package/dist/Components/Editor/EditorFieldBar.d.ts +15 -0
  15. package/dist/Components/Editor/EditorFieldBar.js +19 -0
  16. package/dist/Components/Editor/EditorProps.d.ts +74 -1
  17. package/dist/Components/Editor/MakeDynamicDialog.d.ts +12 -0
  18. package/dist/Components/Editor/MakeDynamicDialog.js +59 -0
  19. package/dist/Components/Editor/editorLabels.d.ts +4 -0
  20. package/dist/Components/Editor/editorLabels.js +24 -0
  21. package/dist/Components/Editor/editorTokenStyles.d.ts +28 -0
  22. package/dist/Components/Editor/editorTokenStyles.js +30 -0
  23. package/dist/Components/Editor/editorTokens.d.ts +26 -0
  24. package/dist/Components/Editor/editorTokens.js +134 -0
  25. package/dist/Components/Editor/useEditorBridge.d.ts +40 -0
  26. package/dist/Components/Editor/useEditorBridge.js +141 -0
  27. package/dist/Components/Kbd/Kbd.d.ts +6 -0
  28. package/dist/Components/Kbd/Kbd.js +33 -0
  29. package/dist/Components/Kbd/KbdProps.d.ts +17 -0
  30. package/dist/Components/Kbd/KbdProps.js +2 -0
  31. package/dist/Components/LauncherTrigger/LauncherTrigger.d.ts +4 -0
  32. package/dist/Components/LauncherTrigger/LauncherTrigger.js +41 -0
  33. package/dist/Components/LauncherTrigger/LauncherTriggerProps.d.ts +16 -0
  34. package/dist/Components/LauncherTrigger/LauncherTriggerProps.js +2 -0
  35. package/dist/Components/NavLauncher/NavLauncher.d.ts +3 -0
  36. package/dist/Components/NavLauncher/NavLauncher.js +395 -0
  37. package/dist/Components/NavLauncher/NavLauncherProps.d.ts +187 -0
  38. package/dist/Components/NavLauncher/NavLauncherProps.js +2 -0
  39. package/dist/Components/NavLauncher/NewTabLink.d.ts +3 -0
  40. package/dist/Components/NavLauncher/NewTabLink.js +14 -0
  41. package/dist/Components/NavLauncher/PinToggle.d.ts +3 -0
  42. package/dist/Components/NavLauncher/PinToggle.js +21 -0
  43. package/dist/Components/NavLauncher/launcherParts.d.ts +93 -0
  44. package/dist/Components/NavLauncher/launcherParts.js +130 -0
  45. package/dist/Components/NavLauncher/match.d.ts +53 -0
  46. package/dist/Components/NavLauncher/match.js +178 -0
  47. package/dist/Components/NavLauncher/revealStyles.d.ts +4 -0
  48. package/dist/Components/NavLauncher/revealStyles.js +19 -0
  49. package/dist/Components/NavigationBar/NavigationBar.d.ts +1 -1
  50. package/dist/Components/NavigationBar/NavigationBar.js +2 -2
  51. package/dist/Components/NavigationBar/NavigationBarProps.d.ts +2 -0
  52. package/dist/Components/PageHeader/PageHeader.d.ts +1 -1
  53. package/dist/Components/PageHeader/PageHeader.js +9 -2
  54. package/dist/Components/PageHeader/PageHeaderProps.d.ts +8 -0
  55. package/dist/Components/PdfViewer/PdfViewer.d.ts +21 -4
  56. package/dist/Components/PdfViewer/PdfViewer.js +124 -3
  57. package/dist/Components/Search/Search.js +2 -1
  58. package/dist/Components/SideBar/SecondaryBar.js +4 -3
  59. package/dist/Components/SideBar/SideBar.d.ts +1 -1
  60. package/dist/Components/SideBar/SideBar.js +30 -9
  61. package/dist/Components/SideBar/SideBarProps.d.ts +32 -0
  62. package/dist/Components/SideBar/components/MenuItemBox.d.ts +6 -0
  63. package/dist/Components/SideBar/components/MenuItemBox.js +8 -4
  64. package/dist/Components/SideBar/components/MenuPopoverContent.js +3 -2
  65. package/dist/Components/SideBar/components/OtherApps.js +2 -1
  66. package/dist/Components/SideBar/components/PinnedSection.d.ts +17 -0
  67. package/dist/Components/SideBar/components/PinnedSection.js +98 -0
  68. package/dist/Components/SideBar/sidebarFocus.d.ts +23 -0
  69. package/dist/Components/SideBar/sidebarFocus.js +20 -0
  70. package/dist/Components/StageProgress/StageProgress.js +2 -2
  71. package/dist/Components/StageProgress/StageProgressProps.d.ts +2 -0
  72. package/dist/Components/StageProgress/StepperStage.js +5 -2
  73. package/dist/Components/WizardDialog/WizardDialog.d.ts +9 -0
  74. package/dist/Components/WizardDialog/WizardDialog.js +102 -0
  75. package/dist/Components/WizardDialog/WizardDialogProps.d.ts +48 -0
  76. package/dist/Components/WizardDialog/WizardDialogProps.js +2 -0
  77. package/dist/Components/WizardDialog/useWizardFlow.d.ts +22 -0
  78. package/dist/Components/WizardDialog/useWizardFlow.js +47 -0
  79. package/dist/Components/WizardDialog/wizardSteps.d.ts +31 -0
  80. package/dist/Components/WizardDialog/wizardSteps.js +40 -0
  81. package/dist/esm/Components/AppSwitcher/AppSwitcher.js +2 -1
  82. package/dist/esm/Components/AppSwitcher/AppSwitcherProps.js +5 -3
  83. package/dist/esm/Components/Breadcrumbs/Breadcrumbs.js +35 -12
  84. package/dist/esm/Components/Card/Card.js +12 -2
  85. package/dist/esm/Components/Editor/Editor.js +48 -3
  86. package/dist/esm/Components/Editor/EditorFieldBar.js +13 -0
  87. package/dist/esm/Components/Editor/MakeDynamicDialog.js +20 -0
  88. package/dist/esm/Components/Editor/editorLabels.js +20 -0
  89. package/dist/esm/Components/Editor/editorTokenStyles.js +25 -0
  90. package/dist/esm/Components/Editor/editorTokens.js +121 -0
  91. package/dist/esm/Components/Editor/useEditorBridge.js +138 -0
  92. package/dist/esm/Components/Kbd/Kbd.js +28 -0
  93. package/dist/esm/Components/Kbd/KbdProps.js +1 -0
  94. package/dist/esm/Components/LauncherTrigger/LauncherTrigger.js +37 -0
  95. package/dist/esm/Components/LauncherTrigger/LauncherTriggerProps.js +1 -0
  96. package/dist/esm/Components/NavLauncher/NavLauncher.js +356 -0
  97. package/dist/esm/Components/NavLauncher/NavLauncherProps.js +1 -0
  98. package/dist/esm/Components/NavLauncher/NewTabLink.js +11 -0
  99. package/dist/esm/Components/NavLauncher/PinToggle.js +18 -0
  100. package/dist/esm/Components/NavLauncher/launcherParts.js +110 -0
  101. package/dist/esm/Components/NavLauncher/match.js +172 -0
  102. package/dist/esm/Components/NavLauncher/revealStyles.js +15 -0
  103. package/dist/esm/Components/NavigationBar/NavigationBar.js +3 -3
  104. package/dist/esm/Components/PageHeader/PageHeader.js +9 -2
  105. package/dist/esm/Components/PdfViewer/PdfViewer.js +123 -2
  106. package/dist/esm/Components/Search/Search.js +3 -2
  107. package/dist/esm/Components/SideBar/SecondaryBar.js +4 -3
  108. package/dist/esm/Components/SideBar/SideBar.js +31 -10
  109. package/dist/esm/Components/SideBar/components/MenuItemBox.js +4 -2
  110. package/dist/esm/Components/SideBar/components/MenuPopoverContent.js +3 -2
  111. package/dist/esm/Components/SideBar/components/OtherApps.js +2 -1
  112. package/dist/esm/Components/SideBar/components/PinnedSection.js +92 -0
  113. package/dist/esm/Components/SideBar/sidebarFocus.js +15 -0
  114. package/dist/esm/Components/StageProgress/StageProgress.js +2 -2
  115. package/dist/esm/Components/StageProgress/StepperStage.js +5 -2
  116. package/dist/esm/Components/WizardDialog/WizardDialog.js +58 -0
  117. package/dist/esm/Components/WizardDialog/WizardDialogProps.js +1 -0
  118. package/dist/esm/Components/WizardDialog/useWizardFlow.js +44 -0
  119. package/dist/esm/Components/WizardDialog/wizardSteps.js +35 -0
  120. package/dist/esm/index.js +7 -0
  121. package/dist/index.d.ts +19 -4
  122. package/dist/index.js +21 -1
  123. package/package.json +1 -1
@@ -1,19 +1,42 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { Box, Breadcrumb, BreadcrumbItem, BreadcrumbLink, useTheme } from "@chakra-ui/react";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Breadcrumb, BreadcrumbItem, BreadcrumbLink, Menu, MenuButton, MenuItem, MenuList, useTheme, } from "@chakra-ui/react";
3
3
  import { useCustomTheme } from "../../Theme/useCustomTheme.js";
4
+ import { focusVisibleStyles } from "../../Theme/chakra/focusRing.styles.js";
4
5
  import { breadcrumbInk } from "./breadcrumbInk.js";
5
- export default function Breadcrumbs({ spacing, separator, fontWeight, fontSize, isCurrentPage, handleClick, items, }) {
6
+ const DEFAULT_MAX_ITEMS = 3;
7
+ const MIN_MAX_ITEMS = 2;
8
+ const OVERFLOW_SX = {
9
+ nowrap: undefined,
10
+ wrap: { "& ol": { flexWrap: "wrap", rowGap: "4px" }, "& li": { maxWidth: "100%" } },
11
+ truncate: { "& ol": { flexWrap: "nowrap" }, "& li": { minWidth: 0 } },
12
+ };
13
+ const collapse = (entries, maxItems) => {
14
+ const keep = Math.max(MIN_MAX_ITEMS, Math.floor(maxItems));
15
+ if (entries.length <= keep)
16
+ return { head: entries, hidden: [], tail: [] };
17
+ return { head: entries.slice(0, 1), hidden: entries.slice(1, entries.length - (keep - 1)), tail: entries.slice(entries.length - (keep - 1)) };
18
+ };
19
+ export default function Breadcrumbs({ spacing, separator, fontWeight, fontSize, isCurrentPage, handleClick, items, overflow = "nowrap", maxItems = DEFAULT_MAX_ITEMS, labels, "data-testid": testId, }) {
6
20
  const theme = useTheme();
7
21
  const { colors } = useCustomTheme();
8
22
  const ink = breadcrumbInk(colors);
9
23
  const effectiveSeparator = separator !== null && separator !== void 0 ? separator : (_jsx(Box, { as: "span", color: ink.separator, children: "/" }));
10
- return (_jsx(Breadcrumb, { spacing: spacing, separator: effectiveSeparator, fontWeight: fontWeight, fontSize: fontSize, fontFamily: theme.fonts.body, children: items.map((item, index) => {
11
- const isLast = isCurrentPage && index === items.length - 1;
12
- return (_jsx(BreadcrumbItem, { isCurrentPage: isLast, children: _jsx(BreadcrumbLink, { href: item.path, color: isLast ? ink.current : ink.link, fontWeight: isLast ? 600 : fontWeight, sx: {
13
- "&:hover, &:active": { textDecoration: "none", color: isLast ? ink.current : ink.linkHover },
14
- }, onClick: (event) => {
15
- event.preventDefault();
16
- handleClick(item.path);
17
- }, children: item.label }) }, index));
18
- }) }));
24
+ const clip = overflow !== "nowrap";
25
+ const clipProps = clip ? { noOfLines: 1 } : {};
26
+ const renderCrumb = ({ crumb, index }) => {
27
+ const isLast = Boolean(isCurrentPage) && index === items.length - 1;
28
+ const color = isLast ? ink.current : ink.link;
29
+ const common = { ...clipProps, title: clip ? crumb.label : undefined, color, fontWeight: isLast ? 600 : fontWeight };
30
+ return (_jsx(BreadcrumbItem, { isCurrentPage: isLast, children: crumb.path === undefined ? (_jsx(Box, { as: "span", "aria-current": isLast ? "page" : undefined, ...common, children: crumb.label })) : (_jsx(BreadcrumbLink, { href: crumb.path, ...common, sx: { "&:hover, &:active": { textDecoration: "none", color: isLast ? ink.current : ink.linkHover } }, onClick: (event) => {
31
+ event.preventDefault();
32
+ handleClick(crumb.path);
33
+ }, children: crumb.label })) }, index));
34
+ };
35
+ const renderMenu = (hidden) => {
36
+ var _a;
37
+ return (_jsx(BreadcrumbItem, { sx: { flexShrink: 0 }, children: _jsxs(Menu, { isLazy: true, placement: "bottom-start", children: [_jsx(MenuButton, { type: "button", "aria-label": ((_a = labels === null || labels === void 0 ? void 0 : labels.showMore) !== null && _a !== void 0 ? _a : ((n) => `Show ${n} more`))(hidden.length), color: ink.link, borderRadius: "md", px: 1, _hover: { color: ink.linkHover }, sx: focusVisibleStyles(colors), children: "\u2026" }), _jsx(MenuList, { children: hidden.map(({ crumb, index }) => (_jsx(MenuItem, { isDisabled: crumb.path === undefined, onClick: () => crumb.path !== undefined && handleClick(crumb.path), children: crumb.label }, index))) })] }) }, "overflow"));
38
+ };
39
+ const entries = items.map((crumb, index) => ({ crumb, index }));
40
+ const { head, hidden, tail } = overflow === "truncate" ? collapse(entries, maxItems) : { head: entries, hidden: [], tail: [] };
41
+ return (_jsxs(Breadcrumb, { spacing: spacing, separator: effectiveSeparator, fontWeight: fontWeight, fontSize: fontSize, fontFamily: theme.fonts.body, sx: OVERFLOW_SX[overflow], "data-testid": testId, children: [head.map(renderCrumb), hidden.length > 0 && renderMenu(hidden), tail.map(renderCrumb)] }));
19
42
  }
@@ -1,11 +1,21 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { forwardRef } from "react";
3
3
  import { Card as ChakraCard, CardBody, CardFooter, CardHeader, } from "@chakra-ui/react";
4
- const Card = forwardRef(function Card({ header, footer, children, color, colorScheme, direction = "column", align, justify, bodyProps, ...rest }, ref) {
4
+ const HEADER_PADDING_WHEN_FLUSH = "0.75rem";
5
+ const PADDING_VALUES = {
6
+ none: "0",
7
+ "2xs": "0.375rem",
8
+ xs: "0.75rem",
9
+ sm: "0.75rem 1rem",
10
+ md: "1rem 1.25rem",
11
+ lg: "1.25rem 1.5rem",
12
+ };
13
+ const Card = forwardRef(function Card({ header, footer, children, color, colorScheme, direction = "column", align, justify, bodyProps, padding, ...rest }, ref) {
5
14
  const cardProps = { ...rest };
6
15
  delete cardProps.dividercolor;
7
16
  delete cardProps.dividersize;
8
17
  delete cardProps.dividervariant;
9
- return (_jsxs(ChakraCard, { ref: ref, colorScheme: colorScheme !== null && colorScheme !== void 0 ? colorScheme : color, ...cardProps, children: [header && _jsx(CardHeader, { children: header }), _jsx(CardBody, { display: "flex", flexDirection: direction, alignItems: align, justifyContent: justify, ...bodyProps, children: children }), footer && _jsx(CardFooter, { children: footer })] }));
18
+ const sx = padding ? { "--card-padding": PADDING_VALUES[padding], ...cardProps.sx } : cardProps.sx;
19
+ return (_jsxs(ChakraCard, { ref: ref, colorScheme: colorScheme !== null && colorScheme !== void 0 ? colorScheme : color, ...cardProps, sx: sx, children: [header && (_jsx(CardHeader, { sx: padding === "none" ? { "--card-padding": HEADER_PADDING_WHEN_FLUSH } : undefined, children: header })), _jsx(CardBody, { display: "flex", flexDirection: direction, alignItems: align, justifyContent: justify, ...bodyProps, children: children }), footer && _jsx(CardFooter, { children: footer })] }));
10
20
  });
11
21
  export default Card;
@@ -1,6 +1,16 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import React, { Suspense, useEffect, useMemo, useState } from "react";
3
3
  import { Box } from "@chakra-ui/react";
4
+ import FieldMessage from "../Common/FieldMessage.js";
5
+ import ErrorMessage from "../Common/ErrorMessage.js";
6
+ import { fieldLabelStyles } from "../Common/FormLabel.js";
7
+ import { useFieldIds } from "../Common/useFieldIds.js";
8
+ import EditorFieldBar from "./EditorFieldBar.js";
9
+ import MakeDynamicDialog from "./MakeDynamicDialog.js";
10
+ import { DEFAULT_TOKEN_SYNTAX, fieldOptions } from "./editorTokens.js";
11
+ import { chipStyles } from "./editorTokenStyles.js";
12
+ import { resolveEditorLabels } from "./editorLabels.js";
13
+ import { useEditorBridge } from "./useEditorBridge.js";
4
14
  import { useCustomTheme } from "../../Theme/useCustomTheme.js";
5
15
  import { isDarkCanvas } from "../../Theme/tokens/builders/accentText.js";
6
16
  import { getJoditThemeVars } from "./joditThemeVars.js";
@@ -13,8 +23,25 @@ const JoditEditor = React.lazy(async () => {
13
23
  return { default: ((_a = mod.default) !== null && _a !== void 0 ? _a : mod) };
14
24
  });
15
25
  /** Lazy-loaded Jodit wrapper — a `config.theme` or `config.popupRoot` you pass overrides this library's own dark-mode/popup fix; omit both keys. */
16
- const Editor = React.memo(function Editor({ value, key, config, editorRef, className, onChange, onBlur, }) {
26
+ const Editor = React.memo(function Editor({ value, key, config, editorRef, className, onChange, onBlur, label, isRequired, helperText, error, errorMessage, mergeFields, tokenSyntax = DEFAULT_TOKEN_SYNTAX, showMakeDynamic, toolbarButtons, onUnknownTokens, apiRef, onReady, labels, "data-testid": testId, }) {
17
27
  const theme = useCustomTheme();
28
+ const text = useMemo(() => resolveEditorLabels(labels), [labels]);
29
+ const { joditValue, attachEditor, api, instance, unknown } = useEditorBridge({
30
+ value,
31
+ onChange,
32
+ onBlur,
33
+ mergeFields,
34
+ syntax: tokenSyntax,
35
+ chipLabel: text.fieldChip,
36
+ apiRef,
37
+ onReady,
38
+ onUnknownTokens,
39
+ });
40
+ const hasFields = mergeFields !== undefined;
41
+ const options = useMemo(() => fieldOptions(mergeFields !== null && mergeFields !== void 0 ? mergeFields : [], tokenSyntax),
42
+ // eslint-disable-next-line react-hooks/exhaustive-deps
43
+ [JSON.stringify(mergeFields), tokenSyntax.open, tokenSyntax.close]);
44
+ const [dynamicText, setDynamicText] = useState(null);
18
45
  // Jodit portals dialogs/toolbar-dropdown popups (search & replace, image/link properties,
19
46
  // font-size list, ...) to `document.body` by default (`Dialog.destination` /
20
47
  // `getPopupViewRoot`), outside this wrapper's DOM subtree — so the CSS custom properties
@@ -49,6 +76,24 @@ const Editor = React.memo(function Editor({ value, key, config, editorRef, class
49
76
  observer.observe(popupRoot, { childList: true, subtree: true });
50
77
  return () => observer.disconnect();
51
78
  }, [popupRoot]);
52
- return (_jsx(Box, { ref: setPopupRoot, sx: themeVars, children: _jsx(Suspense, { fallback: null, children: _jsx(JoditEditor, { config: stableConfig, value: value, ref: editorRef, className: className, onChange: onChange, onBlur: onBlur }, key) }) }));
79
+ const unknownActive = hasFields && unknown.length > 0 && !(error && errorMessage);
80
+ const ids = useFieldIds(undefined, { error, errorMessage, helperText });
81
+ const describedBy = unknownActive ? ids.errorId : ids.describedBy;
82
+ const invalid = Boolean(error) || unknownActive;
83
+ const isReadOnly = Boolean(stableConfig === null || stableConfig === void 0 ? void 0 : stableConfig.readonly);
84
+ useEffect(() => {
85
+ if (!instance)
86
+ return;
87
+ const el = instance.editor;
88
+ const set = (name, val) => val === undefined ? el.removeAttribute(name) : el.setAttribute(name, val);
89
+ set("role", "textbox");
90
+ set("aria-multiline", "true");
91
+ set("aria-labelledby", label ? ids.labelId : undefined);
92
+ set("aria-describedby", describedBy);
93
+ set("aria-invalid", invalid ? "true" : undefined);
94
+ set("aria-required", isRequired ? "true" : undefined);
95
+ }, [instance, label, ids.labelId, describedBy, invalid, isRequired]);
96
+ const showFieldBar = hasFields || Boolean(toolbarButtons === null || toolbarButtons === void 0 ? void 0 : toolbarButtons.length);
97
+ return (_jsxs(Box, { "data-testid": testId, children: [label && (_jsxs(Box, { as: "label", id: ids.labelId, display: "block", onClick: () => api.focus(), ...fieldLabelStyles(theme.colors), children: [label, isRequired && (_jsx("span", { "aria-hidden": true, style: { color: theme.colors.semanticText.error, marginLeft: "0.125rem" }, children: "*" }))] })), showFieldBar && (_jsx(EditorFieldBar, { options: options, labels: text, api: api, isDisabled: !instance || isReadOnly, showFieldMenu: hasFields, showMakeDynamic: hasFields && (showMakeDynamic !== null && showMakeDynamic !== void 0 ? showMakeDynamic : true), buttons: toolbarButtons !== null && toolbarButtons !== void 0 ? toolbarButtons : [], onMakeDynamic: () => setDynamicText(api.getSelectionText()) })), _jsx(Box, { ref: setPopupRoot, sx: { ...themeVars, ...chipStyles(theme.colors) }, children: _jsx(Suspense, { fallback: null, children: popupRoot && (_jsx(JoditEditor, { config: stableConfig, value: joditValue, ref: editorRef, editorRef: attachEditor, className: className }, key)) }) }), unknownActive ? (_jsx(ErrorMessage, { id: ids.errorId, errorMessage: text.unknownTokens(unknown.join(", ")) })) : (_jsx(FieldMessage, { error: error, errorMessage: errorMessage, helperText: helperText, helperId: ids.helperId, errorId: ids.errorId })), hasFields && (_jsx(MakeDynamicDialog, { selectedText: dynamicText, options: options, labels: text, countFor: api.countOccurrences, onReplace: api.replaceAll, onClose: () => setDynamicText(null) }))] }));
53
98
  });
54
99
  export default Editor;
@@ -0,0 +1,13 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Flex, Icon } from "@chakra-ui/react";
3
+ import { WandSparkles } from "lucide-react";
4
+ import Button from "../Button/Button.js";
5
+ import Select from "../Select/Select.js";
6
+ export default function EditorFieldBar({ options, labels, api, isDisabled, showFieldMenu, showMakeDynamic, buttons, onMakeDynamic, }) {
7
+ return (_jsxs(Flex, { role: "toolbar", "aria-label": labels.toolbar, gap: 2, wrap: "wrap", align: "center", mb: 2, children: [showFieldMenu && (_jsx(Box, { width: { base: "100%", sm: "16rem" }, children: _jsx(Select, { native: false, "aria-label": labels.insertField, placeholder: labels.insertField, options: options, value: "", noOptionsMessage: labels.noFields, isDisabled: isDisabled, onChange: (option) => {
8
+ if (!option)
9
+ return;
10
+ api.insertField(String(option.id));
11
+ window.setTimeout(api.focus, 0);
12
+ }, "data-testid": "editor-insert-field" }) })), showMakeDynamic && (_jsx(Button, { variant: "outline", colorScheme: "gray", leftIcon: _jsx(WandSparkles, { size: 16, "aria-hidden": true }), label: labels.makeDynamic, onClick: onMakeDynamic, isDisabled: isDisabled, "data-testid": "editor-make-dynamic" })), buttons.map((button) => (_jsx(Button, { variant: "ghost", colorScheme: "gray", leftIcon: button.icon ? _jsx(Icon, { as: button.icon, boxSize: 4, "aria-hidden": true }) : undefined, label: button.label, onClick: () => button.onClick(api), isDisabled: isDisabled || button.isDisabled, "data-testid": `editor-button-${button.id}` }, button.id)))] }));
13
+ }
@@ -0,0 +1,20 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from "react";
3
+ import { Box, Flex, Text } from "@chakra-ui/react";
4
+ import Button from "../Button/Button.js";
5
+ import Modal, { ModalBody, ModalFooter } from "../Modal/Modal.js";
6
+ import Select from "../Select/Select.js";
7
+ import { useCustomTheme } from "../../Theme/useCustomTheme.js";
8
+ export default function MakeDynamicDialog({ selectedText, options, labels, countFor, onReplace, onClose, }) {
9
+ const { colors } = useCustomTheme();
10
+ const [path, setPath] = useState("");
11
+ const count = selectedText ? countFor(selectedText) : 0;
12
+ const close = () => {
13
+ setPath("");
14
+ onClose();
15
+ };
16
+ return (_jsxs(Modal, { isOpen: selectedText !== null, onClose: close, size: "md", isCentered: true, title: labels.dynamicTitle, children: [_jsx(ModalBody, { children: selectedText ? (_jsxs(Flex, { direction: "column", gap: 4, "data-testid": "editor-dynamic-dialog", children: [_jsxs(Box, { children: [_jsx(Text, { fontSize: "xs", color: colors.textMuted, children: labels.dynamicSelected }), _jsxs(Text, { fontSize: "sm", fontWeight: 500, color: colors.text[800], noOfLines: 3, children: ["\u201C", selectedText, "\u201D"] })] }), _jsx(Select, { native: false, label: labels.dynamicField, placeholder: labels.dynamicFieldPlaceholder, options: options, value: path, onChange: (option) => setPath(option ? String(option.id) : ""), "data-testid": "editor-dynamic-field" }), _jsx(Text, { fontSize: "sm", color: colors.textMuted, "aria-live": "polite", children: labels.dynamicCount(count) })] })) : (_jsx(Text, { fontSize: "sm", color: colors.text[800], "data-testid": "editor-dynamic-empty", children: labels.dynamicNoSelection })) }), _jsx(ModalFooter, { children: _jsxs(Flex, { gap: 2, justify: "flex-end", w: "100%", children: [_jsx(Button, { variant: "ghost", colorScheme: "gray", label: labels.cancel, onClick: close }), selectedText ? (_jsx(Button, { variant: "solid", colorScheme: "primary", label: labels.dynamicReplace, onClick: () => {
17
+ onReplace(selectedText, path);
18
+ close();
19
+ }, isDisabled: !path || count === 0, "data-testid": "editor-dynamic-apply" })) : null] }) })] }));
20
+ }
@@ -0,0 +1,20 @@
1
+ export const DEFAULT_EDITOR_LABELS = {
2
+ insertField: "Insert field",
3
+ noFields: "No fields",
4
+ makeDynamic: "Make dynamic",
5
+ dynamicTitle: "Make dynamic",
6
+ dynamicField: "Field",
7
+ dynamicFieldPlaceholder: "Choose a field",
8
+ dynamicCount: (n) => `${n} ${n === 1 ? "occurrence" : "occurrences"} will be replaced`,
9
+ dynamicReplace: "Replace",
10
+ dynamicNoSelection: "Select some text in the editor first.",
11
+ unknownTokens: (list) => `Unknown fields: ${list}`,
12
+ cancel: "Cancel",
13
+ dynamicSelected: "Selected text",
14
+ toolbar: "Template fields",
15
+ fieldChip: (label) => `Field: ${label}`,
16
+ };
17
+ export const resolveEditorLabels = (labels) => ({
18
+ ...DEFAULT_EDITOR_LABELS,
19
+ ...labels,
20
+ });
@@ -0,0 +1,25 @@
1
+ import { CHIP_CLASS } from "./editorTokens.js";
2
+ export const chipColors = (colors) => ({
3
+ known: { bg: colors.primary.opacity[8], ink: colors.accentText },
4
+ unknown: { bg: "transparent", ink: colors.semanticText.error },
5
+ });
6
+ export const chipStyles = (colors) => {
7
+ const { known, unknown } = chipColors(colors);
8
+ return {
9
+ [`.${CHIP_CLASS}`]: {
10
+ display: "inline-block",
11
+ px: "0.25rem",
12
+ borderRadius: "sm",
13
+ border: "1px solid transparent",
14
+ bg: known.bg,
15
+ color: known.ink,
16
+ cursor: "default",
17
+ },
18
+ [`.${CHIP_CLASS}[data-known="false"]`]: {
19
+ bg: unknown.bg,
20
+ color: unknown.ink,
21
+ border: "1px dashed",
22
+ borderColor: unknown.ink,
23
+ },
24
+ };
25
+ };
@@ -0,0 +1,121 @@
1
+ export const DEFAULT_TOKEN_SYNTAX = { open: "${", close: "}" };
2
+ export const CHIP_CLASS = "pxl-token";
3
+ const PATH_CHARS = "[A-Za-z0-9_.]+";
4
+ const TAG_SPLIT = /(<[^>]*>)/;
5
+ const CHIP_PATTERN = /<span\b([^>]*\bdata-field=(["'])([^"']*)\2[^>]*)>[^<]*<\/span>/g;
6
+ const escapeRegExp = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
7
+ const escapeHtml = (text) => text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
8
+ const escapeAttr = (text) => escapeHtml(text).replace(/"/g, "&quot;");
9
+ const patternsFor = ({ open, close }) => {
10
+ const o = escapeRegExp(open);
11
+ const c = escapeRegExp(close);
12
+ return {
13
+ match: new RegExp(`${o}(${PATH_CHARS})${c}`, "g"),
14
+ split: new RegExp(`(${o}(?:(?!${c})[\\s\\S])*${c})`),
15
+ };
16
+ };
17
+ export const fieldToken = (path, syntax = DEFAULT_TOKEN_SYNTAX) => `${syntax.open}${path}${syntax.close}`;
18
+ export function fieldOptions(fields, syntax = DEFAULT_TOKEN_SYNTAX) {
19
+ const groups = [...new Set(fields.map((field) => field.group))];
20
+ return groups.flatMap((group) => fields
21
+ .filter((field) => field.group === group)
22
+ .map((field) => ({
23
+ id: field.path,
24
+ label: field.label,
25
+ group,
26
+ description: fieldToken(field.path, syntax),
27
+ })));
28
+ }
29
+ export function extractTokens(html, syntax = DEFAULT_TOKEN_SYNTAX) {
30
+ const { match } = patternsFor(syntax);
31
+ return [...new Set([...html.matchAll(match)].map((m) => m[1]))];
32
+ }
33
+ export function unknownTokens(html, fields, syntax = DEFAULT_TOKEN_SYNTAX) {
34
+ const known = new Set(fields.map((field) => field.path));
35
+ return extractTokens(html, syntax).filter((path) => !known.has(path));
36
+ }
37
+ function needlePattern(text) {
38
+ const words = text
39
+ .trim()
40
+ .split(/\s+/)
41
+ .map((word) => escapeRegExp(escapeHtml(word)));
42
+ return new RegExp(words.join("(?:\\s|&nbsp;|\\u00a0)+"), "g");
43
+ }
44
+ function mapTextOutsideTokens(html, syntax, mapText) {
45
+ const { split } = patternsFor(syntax);
46
+ return html
47
+ .split(TAG_SPLIT)
48
+ .map((segment) => segment.startsWith("<")
49
+ ? segment
50
+ : segment
51
+ .split(split)
52
+ .map((part, i) => (i % 2 === 1 ? part : mapText(part)))
53
+ .join(""))
54
+ .join("");
55
+ }
56
+ /** Replaces every whitespace-insensitive occurrence of `selectedText` outside tags and tokens. */
57
+ export function makeDynamic(html, selectedText, path, syntax = DEFAULT_TOKEN_SYNTAX) {
58
+ if (!selectedText.trim())
59
+ return { html, count: 0 };
60
+ const pattern = needlePattern(selectedText);
61
+ let count = 0;
62
+ const next = mapTextOutsideTokens(html, syntax, (text) => text.replace(pattern, () => {
63
+ count += 1;
64
+ return fieldToken(path, syntax);
65
+ }));
66
+ return { html: next, count };
67
+ }
68
+ export function countOccurrences(html, selectedText, syntax = DEFAULT_TOKEN_SYNTAX) {
69
+ return makeDynamic(html, selectedText, "_", syntax).count;
70
+ }
71
+ export function chipHtml(path, { fields, syntax, chipLabel }) {
72
+ const field = fields.find((f) => f.path === path);
73
+ const text = field ? field.label : fieldToken(path, syntax);
74
+ const title = (field === null || field === void 0 ? void 0 : field.sample) ? ` title="${escapeAttr(field.sample)}"` : "";
75
+ return (`<span class="${CHIP_CLASS}" data-field="${escapeAttr(path)}" data-known="${field ? "true" : "false"}"` +
76
+ ` contenteditable="false" role="img" aria-label="${escapeAttr(chipLabel(text))}"${title}>${escapeHtml(text)}</span>`);
77
+ }
78
+ /** Raw `${path}` tokens in text nodes become non-editable chip spans; tags are left alone. */
79
+ export function chipify(html, context) {
80
+ const { match } = patternsFor(context.syntax);
81
+ return html
82
+ .split(TAG_SPLIT)
83
+ .map((segment) => segment.startsWith("<") ? segment : segment.replace(match, (_t, path) => chipHtml(path, context)))
84
+ .join("");
85
+ }
86
+ /** Inverse of `chipify`: every chip span collapses back to its raw token. */
87
+ export function serializeChips(html, syntax = DEFAULT_TOKEN_SYNTAX) {
88
+ return html.replace(CHIP_PATTERN, (_m, _attrs, _q, path) => fieldToken(path, syntax));
89
+ }
90
+ const isChip = (node) => !!node && node.nodeType === 1 && node.hasAttribute("data-field");
91
+ const INVISIBLE = /[\u200b\ufeff]/g;
92
+ const isBlankText = (node) => !node.data.replace(INVISIBLE, "");
93
+ const skipEmptyText = (node, step) => {
94
+ let current = node;
95
+ while (current && current.nodeType === 3 && isBlankText(current))
96
+ current = current[step];
97
+ return current;
98
+ };
99
+ /** The chip a Backspace (backward) or Delete (forward) at a collapsed caret would eat, if any. */
100
+ export function adjacentChip(range, direction) {
101
+ var _a;
102
+ if (!range.collapsed)
103
+ return null;
104
+ const node = range.startContainer;
105
+ const offset = range.startOffset;
106
+ const step = direction === "backward" ? "previousSibling" : "nextSibling";
107
+ let candidate;
108
+ if (node.nodeType === 3) {
109
+ const text = node.data;
110
+ const beyond = direction === "backward" ? text.slice(0, offset) : text.slice(offset);
111
+ const atEdge = !beyond.replace(INVISIBLE, "");
112
+ if (!atEdge)
113
+ return null;
114
+ candidate = node[step];
115
+ }
116
+ else {
117
+ candidate = (_a = node.childNodes[direction === "backward" ? offset - 1 : offset]) !== null && _a !== void 0 ? _a : null;
118
+ }
119
+ const found = skipEmptyText(candidate, step);
120
+ return isChip(found) ? found : null;
121
+ }
@@ -0,0 +1,138 @@
1
+ import { useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react";
2
+ import { adjacentChip, chipHtml, chipify, countOccurrences, fieldToken, makeDynamic, serializeChips, unknownTokens, } from "./editorTokens.js";
3
+ export function useEditorBridge({ value, onChange, onBlur, mergeFields, syntax, chipLabel, apiRef, onReady, onUnknownTokens, }) {
4
+ const enabled = mergeFields !== undefined;
5
+ const fieldsKey = JSON.stringify(mergeFields);
6
+ const ctx = useMemo(() => ({ fields: mergeFields !== null && mergeFields !== void 0 ? mergeFields : [], syntax, chipLabel }),
7
+ // eslint-disable-next-line react-hooks/exhaustive-deps
8
+ [fieldsKey, syntax.open, syntax.close, chipLabel]);
9
+ const joditRef = useRef(null);
10
+ const rangeRef = useRef(null);
11
+ const lastRef = useRef(null);
12
+ const appliedFieldsKey = useRef(fieldsKey);
13
+ const live = useRef({ enabled, ctx, onChange, onBlur, onReady, fieldsKey });
14
+ live.current = { enabled, ctx, onChange, onBlur, onReady, fieldsKey };
15
+ const [instance, setInstance] = useState(null);
16
+ const [raw, setRaw] = useState(value !== null && value !== void 0 ? value : "");
17
+ useEffect(() => {
18
+ if (value !== undefined)
19
+ setRaw(value);
20
+ }, [value]);
21
+ const handleChange = useCallback((html) => {
22
+ const { enabled: on, ctx: c, onChange: emit } = live.current;
23
+ const next = on ? serializeChips(html, c.syntax) : html;
24
+ lastRef.current = { raw: next, jodit: html };
25
+ setRaw(next);
26
+ emit === null || emit === void 0 ? void 0 : emit(next);
27
+ }, []);
28
+ const handleBlur = useCallback((html, event) => {
29
+ const { enabled: on, ctx: c, onBlur: emit } = live.current;
30
+ emit === null || emit === void 0 ? void 0 : emit(on ? serializeChips(html, c.syntax) : html, event);
31
+ }, []);
32
+ const attachEditor = useCallback((jodit) => {
33
+ joditRef.current = jodit;
34
+ rangeRef.current = null;
35
+ appliedFieldsKey.current = live.current.fieldsKey;
36
+ const remember = () => {
37
+ if (jodit.s.isInsideArea)
38
+ rangeRef.current = jodit.s.range.cloneRange();
39
+ };
40
+ jodit.events.on("keyup mouseup touchend", remember);
41
+ jodit.events.on("change", handleChange);
42
+ jodit.events.on("blur", (event) => handleBlur(jodit.value, event));
43
+ const deleteChip = (event) => {
44
+ if (!live.current.enabled)
45
+ return false;
46
+ if (event.key !== "Backspace" && event.key !== "Delete")
47
+ return false;
48
+ const chip = adjacentChip(jodit.s.range, event.key === "Backspace" ? "backward" : "forward");
49
+ if (!chip)
50
+ return false;
51
+ chip.remove();
52
+ jodit.synchronizeValues();
53
+ event.preventDefault();
54
+ return true;
55
+ };
56
+ jodit.events.on("keydown", ((event) => (deleteChip(event) ? false : undefined)));
57
+ jodit.editor.addEventListener("keydown", (event) => {
58
+ if (deleteChip(event))
59
+ event.stopImmediatePropagation();
60
+ }, true);
61
+ jodit.waitForReady().then(() => {
62
+ var _a, _b;
63
+ if (joditRef.current !== jodit)
64
+ return;
65
+ setInstance(jodit);
66
+ (_b = (_a = live.current).onReady) === null || _b === void 0 ? void 0 : _b.call(_a);
67
+ });
68
+ }, [handleChange, handleBlur]);
69
+ useEffect(() => {
70
+ if (!instance || !enabled || appliedFieldsKey.current === fieldsKey)
71
+ return;
72
+ appliedFieldsKey.current = fieldsKey;
73
+ const html = chipify(serializeChips(instance.value, ctx.syntax), ctx);
74
+ instance.value = html;
75
+ lastRef.current = { raw: serializeChips(html, ctx.syntax), jodit: instance.value };
76
+ }, [instance, enabled, fieldsKey, ctx]);
77
+ const api = useMemo(() => {
78
+ const current = () => joditRef.current;
79
+ const currentRaw = (jodit) => live.current.enabled ? serializeChips(jodit.value, live.current.ctx.syntax) : jodit.value;
80
+ const insertHTML = (html) => {
81
+ const jodit = current();
82
+ if (!jodit)
83
+ return;
84
+ if (rangeRef.current)
85
+ jodit.s.selectRange(rangeRef.current, true);
86
+ jodit.s.insertHTML(html);
87
+ rangeRef.current = jodit.s.range.cloneRange();
88
+ };
89
+ return {
90
+ insertHTML,
91
+ insertField: (path) => {
92
+ const { enabled: on, ctx: c } = live.current;
93
+ insertHTML(on ? chipHtml(path, c) : fieldToken(path, c.syntax));
94
+ },
95
+ getSelectionText: () => (rangeRef.current ? rangeRef.current.toString().trim() : ""),
96
+ replaceAll: (text, path) => {
97
+ const jodit = current();
98
+ if (!jodit)
99
+ return 0;
100
+ const { enabled: on, ctx: c, onChange: emit } = live.current;
101
+ const result = makeDynamic(currentRaw(jodit), text, path, c.syntax);
102
+ if (result.count === 0)
103
+ return 0;
104
+ jodit.value = on ? chipify(result.html, c) : result.html;
105
+ lastRef.current = { raw: result.html, jodit: jodit.value };
106
+ rangeRef.current = null;
107
+ setRaw(result.html);
108
+ emit === null || emit === void 0 ? void 0 : emit(result.html);
109
+ return result.count;
110
+ },
111
+ countOccurrences: (text) => {
112
+ const jodit = current();
113
+ return jodit ? countOccurrences(currentRaw(jodit), text, live.current.ctx.syntax) : 0;
114
+ },
115
+ focus: () => {
116
+ var _a;
117
+ (_a = current()) === null || _a === void 0 ? void 0 : _a.s.focus();
118
+ },
119
+ };
120
+ }, []);
121
+ useImperativeHandle(apiRef, () => api, [api]);
122
+ const unknown = useMemo(() => (enabled ? unknownTokens(raw, ctx.fields, syntax) : []), [enabled, raw, ctx, syntax]);
123
+ const unknownKey = unknown.join("\n");
124
+ const reported = useRef("");
125
+ useEffect(() => {
126
+ if (!enabled || reported.current === unknownKey)
127
+ return;
128
+ reported.current = unknownKey;
129
+ onUnknownTokens === null || onUnknownTokens === void 0 ? void 0 : onUnknownTokens(unknown);
130
+ // eslint-disable-next-line react-hooks/exhaustive-deps
131
+ }, [enabled, unknownKey]);
132
+ const last = lastRef.current;
133
+ let joditValue = value;
134
+ if (enabled && value !== undefined) {
135
+ joditValue = last && last.raw === value ? last.jodit : chipify(value, ctx);
136
+ }
137
+ return { joditValue, attachEditor, api, instance, raw, unknown, ctx };
138
+ }
@@ -0,0 +1,28 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Box } from "@chakra-ui/react";
3
+ import { useCustomTheme } from "../../Theme/useCustomTheme.js";
4
+ import { shortcutKeys } from "../AppSwitcher/AppSwitcherProps.js";
5
+ const HEIGHT = { xs: "1rem", sm: "1.25rem" };
6
+ const SPOKEN = {
7
+ mod: "Command",
8
+ cmd: "Command",
9
+ ctrl: "Control",
10
+ alt: "Option",
11
+ shift: "Shift",
12
+ enter: "Enter",
13
+ esc: "Escape",
14
+ };
15
+ export const detectApple = () => typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.platform);
16
+ /** One keyboard key cap; decorative hint rows should wrap it in `aria-hidden`. */
17
+ const Kbd = ({ children, size = "sm", "data-testid": testId }) => {
18
+ const { colors } = useCustomTheme();
19
+ return (_jsx(Box, { as: "kbd", "data-testid": testId, display: "inline-flex", alignItems: "center", justifyContent: "center", minW: HEIGHT[size], h: HEIGHT[size], px: 1.5, fontFamily: "inherit", fontSize: "0.6875rem", fontWeight: 600, lineHeight: 1, whiteSpace: "nowrap", borderWidth: "1px", borderColor: colors.boxborder[200], borderRadius: "base", bg: colors.backgroundColor.subtle, color: colors.textMuted, children: children }));
20
+ };
21
+ export const KbdShortcut = ({ shortcut, size, isApple, "data-testid": testId }) => {
22
+ const parts = shortcut.split("+").map((p) => p.trim()).filter(Boolean);
23
+ const apple = isApple !== null && isApple !== void 0 ? isApple : detectApple();
24
+ const spoken = parts.map((p) => { var _a; return (_a = SPOKEN[p.toLowerCase()]) !== null && _a !== void 0 ? _a : p.toUpperCase(); }).join(" ");
25
+ const keys = shortcutKeys(shortcut, apple);
26
+ return (_jsx(Box, { as: "span", display: "inline-flex", gap: 1, role: "img", "aria-label": spoken, "data-testid": testId, children: keys.map((key, i) => (_jsx(Kbd, { size: size, children: key }, `${key}-${i}`))) }));
27
+ };
28
+ export default Kbd;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,37 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box } from "@chakra-ui/react";
3
+ import { Compass } from "lucide-react";
4
+ import { useCustomTheme } from "../../Theme/useCustomTheme.js";
5
+ import { reducedMotionSx } from "../../Theme/chakra/motion.js";
6
+ import { FOCUS_VISIBLE_OVER_STATE, focusVisibleRing } from "../../Theme/chakra/focusRing.styles.js";
7
+ import { transitionFor } from "../../Theme/tokens/primitives/motion.js";
8
+ import { useMatchMedia } from "../../Hooks/useMatchMedia.js";
9
+ import { detectApple } from "../Kbd/Kbd.js";
10
+ import { formatShortcut } from "../AppSwitcher/AppSwitcherProps.js";
11
+ const MD_QUERY = "(min-width: 48em)";
12
+ const KEY_NAMES = {
13
+ mod: ["Meta", "Control"],
14
+ cmd: ["Meta"],
15
+ ctrl: ["Control"],
16
+ alt: ["Alt"],
17
+ shift: ["Shift"],
18
+ esc: ["Escape"],
19
+ };
20
+ export const keyShortcutsFor = (shortcut) => {
21
+ const options = shortcut
22
+ .split("+")
23
+ .map((p) => p.trim())
24
+ .filter(Boolean)
25
+ .map((p) => { var _a; return (_a = KEY_NAMES[p.toLowerCase()]) !== null && _a !== void 0 ? _a : [p.length === 1 ? p.toUpperCase() : p]; });
26
+ const variants = Math.max(1, ...options.map((o) => o.length));
27
+ const chords = Array.from({ length: variants }, (_, v) => options.map((o) => o[Math.min(v, o.length - 1)]).join("+"));
28
+ return Array.from(new Set(chords)).join(" ");
29
+ };
30
+ const LauncherTrigger = ({ onClick, isOpen, label = "Menu", shortcut = "mod+k", icon: Icon = Compass, "aria-label": ariaLabel, "data-testid": testId, }) => {
31
+ const { colors } = useCustomTheme();
32
+ const showHint = useMatchMedia(MD_QUERY, true);
33
+ const ring = focusVisibleRing(colors);
34
+ const hint = shortcut ? formatShortcut(shortcut, detectApple()) : "";
35
+ return (_jsxs(Box, { as: "button", type: "button", "data-testid": testId, onClick: onClick, "aria-haspopup": "dialog", "aria-expanded": isOpen, "aria-label": ariaLabel, "aria-keyshortcuts": shortcut ? keyShortcutsFor(shortcut) : undefined, display: "inline-flex", alignItems: "center", gap: 2, flexShrink: 0, h: "2.25rem", pl: "0.625rem", pr: "0.375rem", borderRadius: "10px", borderWidth: "1px", borderColor: colors.primary[100], bg: colors.primary[50], color: colors.accentText, fontSize: "0.84375rem", fontWeight: 600, lineHeight: 1, cursor: "pointer", transition: transitionFor(["background-color", "border-color", "box-shadow"]), _hover: { borderColor: colors.primary[300] }, sx: { _focusVisible: ring, [FOCUS_VISIBLE_OVER_STATE]: ring, ...reducedMotionSx() }, children: [_jsxs(Box, { as: "span", display: "inline-flex", alignItems: "center", gap: 2, "aria-hidden": ariaLabel ? true : undefined, children: [_jsx(Icon, { size: 17, "aria-hidden": "true" }), _jsx("span", { children: label })] }), hint && showHint && (_jsx(Box, { as: "kbd", "aria-hidden": "true", fontFamily: "inherit", fontSize: "0.6875rem", fontWeight: 600, lineHeight: "normal", px: "6px", py: "2px", borderRadius: "6px", borderWidth: "1px", borderColor: colors.primary[100], bg: colors.surfaceRaised, children: hint }))] }));
36
+ };
37
+ export default LauncherTrigger;