pixelize-design-library 2.4.2-beta.3 → 2.4.2-beta.30
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/Accordion/Accordion.js +5 -3
- package/dist/Components/AppSwitcher/AppMark.d.ts +19 -0
- package/dist/Components/AppSwitcher/AppMark.js +62 -0
- package/dist/Components/AppSwitcher/AppSwitcher.d.ts +4 -0
- package/dist/Components/AppSwitcher/AppSwitcher.js +315 -0
- package/dist/Components/AppSwitcher/AppSwitcherProps.d.ts +64 -0
- package/dist/Components/AppSwitcher/AppSwitcherProps.js +86 -0
- package/dist/Components/AppSwitcher/useAppSwitcherShortcut.d.ts +3 -0
- package/dist/Components/AppSwitcher/useAppSwitcherShortcut.js +31 -0
- package/dist/Components/AppearanceSettings/AppearanceSettings.js +2 -1
- package/dist/Components/Button/Button.d.ts +2 -2
- package/dist/Components/Button/Button.js +4 -3
- package/dist/Components/Button/Button.styles.js +62 -15
- package/dist/Components/Button/ButtonProps.d.ts +6 -1
- package/dist/Components/ButtonGroupIcon/ButtonGroupIcon.js +6 -6
- package/dist/Components/Card/Card.d.ts +1 -1
- package/dist/Components/Card/Card.js +2 -2
- package/dist/Components/Card/CardProps.d.ts +0 -1
- package/dist/Components/Card/PaymentCard/PaymentCard.js +7 -7
- package/dist/Components/Checkbox/Checkbox.js +1 -1
- package/dist/Components/Checkbox/Checkbox.styles.js +12 -7
- package/dist/Components/Common/fieldStyles.js +1 -1
- package/dist/Components/CopyButton/CopyButton.js +6 -2
- package/dist/Components/CustomModulesTable/EditModuleModal.js +1 -1
- package/dist/Components/DatePicker/CalendarPanel.d.ts +5 -0
- package/dist/Components/DatePicker/CalendarPanel.js +24 -5
- package/dist/Components/DatePicker/DatePickerProps.d.ts +21 -0
- package/dist/Components/DatePicker/RangeDatePicker.js +4 -4
- package/dist/Components/DatePicker/SingleDatePicker.js +2 -2
- package/dist/Components/DatePicker/TimeOnlyPicker.js +1 -1
- package/dist/Components/DatePicker/dayStates.d.ts +56 -0
- package/dist/Components/DatePicker/dayStates.js +87 -0
- package/dist/Components/Dropdown/DropDown.js +7 -15
- package/dist/Components/EmailCards/VerifyEmailOtp/VerifyEmailOtp.js +1 -1
- package/dist/Components/EmptyState/EmptyState.js +2 -1
- package/dist/Components/FilePreview/FilePreview.js +1 -1
- package/dist/Components/FileUpload/FileUpload.js +5 -4
- package/dist/Components/FileUpload/FileUploadProps.d.ts +3 -1
- package/dist/Components/FileUpload/constants.js +1 -0
- package/dist/Components/FilterSidebar/FilterSidebar.js +4 -4
- package/dist/Components/FilterSidebar/filterHoverStyle.d.ts +0 -9
- package/dist/Components/FilterSidebar/filterHoverStyle.js +4 -10
- package/dist/Components/Form/FormWrapper.js +1 -1
- package/dist/Components/Header/components/mobileMenu.js +12 -3
- package/dist/Components/Input/TextInput.styles.js +6 -4
- package/dist/Components/InputTextArea/InputTextArea.style.js +5 -4
- package/dist/Components/KanbanBoard/KanbanBoard.js +5 -45
- package/dist/Components/LimitReachedModal/LimitReachedModal.js +4 -5
- package/dist/Components/Loading/Loading.js +15 -14
- package/dist/Components/MultiSelect/MultiSelect.js +1 -1
- package/dist/Components/NavigationBar/NavigationBar.js +3 -3
- package/dist/Components/NoteTextArea/NoteTextArea.js +9 -4
- package/dist/Components/NumberInput/NumberInput.js +3 -1
- package/dist/Components/NumberInput/NumberInput.styles.js +5 -4
- package/dist/Components/OrgSwitcher/OrgSwitcher.js +2 -2
- package/dist/Components/PdfViewer/PdfViewer.js +6 -1
- package/dist/Components/PinInput/PinInput.js +1 -1
- package/dist/Components/PlanPill/PlanPill.js +5 -5
- package/dist/Components/PlanPill/planPillGradient.d.ts +2 -14
- package/dist/Components/PlanPill/planPillGradient.js +3 -15
- package/dist/Components/Primitives/Primitives.d.ts +6 -0
- package/dist/Components/Primitives/Primitives.js +113 -0
- package/dist/Components/ProductCard/ProductPrice.js +1 -1
- package/dist/Components/ProductCard/ProductReview.js +6 -1
- package/dist/Components/ProductCard/ProductTags.js +1 -1
- package/dist/Components/ProfileCard/ProfileCard.js +1 -1
- package/dist/Components/ProfilePhotoViewer/ProfilePhotoViewer.js +1 -1
- package/dist/Components/RadioButton/RadioButton.js +3 -2
- package/dist/Components/Reorder/Reorder.js +1 -1
- package/dist/Components/ScrollToTop/ScrollToTop.js +2 -0
- package/dist/Components/ScrollbarBox/ScrollbarBox.d.ts +13 -0
- package/dist/Components/ScrollbarBox/ScrollbarBox.js +11 -0
- package/dist/Components/Search/Search.js +2 -2
- package/dist/Components/SearchSelect/SearchSelect.js +14 -9
- package/dist/Components/SegmentedControl/SegmentedControl.d.ts +2 -0
- package/dist/Components/SegmentedControl/SegmentedControl.js +58 -0
- package/dist/Components/SegmentedControl/SegmentedControlProps.d.ts +16 -0
- package/dist/Components/SegmentedControl/SegmentedControlProps.js +2 -0
- package/dist/Components/Select/Select.d.ts +1 -1
- package/dist/Components/Select/Select.js +4 -2
- package/dist/Components/Select/Select.styles.js +3 -2
- package/dist/Components/Select/SelectProps.d.ts +3 -0
- package/dist/Components/SelectSearch/SelectSearch.js +7 -4
- package/dist/Components/SelectV2/SelectV2.js +18 -14
- package/dist/Components/SideBar/SecondaryBar.js +1 -1
- package/dist/Components/SideBar/SideBar.js +1 -1
- package/dist/Components/SideBar/components/MenuPopoverContent.js +2 -2
- package/dist/Components/Slider/Slider.js +8 -1
- package/dist/Components/StageProgress/StageProgress.js +2 -9
- package/dist/Components/StageProgress/StepperStage.js +2 -2
- package/dist/Components/Table/Table.d.ts +1 -1
- package/dist/Components/Table/Table.js +17 -24
- package/dist/Components/Table/TableProps.d.ts +4 -0
- package/dist/Components/Table/components/Pagination.js +1 -1
- package/dist/Components/Table/components/TableActions.js +2 -2
- package/dist/Components/Table/components/TableBody.js +41 -12
- package/dist/Components/Table/components/TableGroupRow.js +1 -1
- package/dist/Components/Table/components/TableHeader.js +21 -8
- package/dist/Components/Table/components/TableSearch.js +1 -1
- package/dist/Components/Table/filters/ActiveFilters.js +2 -18
- package/dist/Components/Table/settings/ManageColumns.js +4 -17
- package/dist/Components/Table/settings/TableSettings.js +5 -11
- package/dist/Components/Table/variants/TableVariantContext.d.ts +4 -0
- package/dist/Components/Table/variants/TableVariantContext.js +17 -0
- package/dist/Components/Table/variants/classic.d.ts +6 -0
- package/dist/Components/Table/variants/classic.js +47 -0
- package/dist/Components/Table/variants/index.d.ts +7 -0
- package/dist/Components/Table/variants/index.js +27 -0
- package/dist/Components/Table/variants/pillInk.d.ts +6 -0
- package/dist/Components/Table/variants/pillInk.js +21 -0
- package/dist/Components/Table/variants/studio.d.ts +6 -0
- package/dist/Components/Table/variants/studio.js +50 -0
- package/dist/Components/Table/variants/tokens.d.ts +49 -0
- package/dist/Components/Table/variants/tokens.js +2 -0
- package/dist/Components/Tag/Tag.styles.js +3 -4
- package/dist/Components/Timeline/Timeline.js +2 -2
- package/dist/Components/Toaster/Toaster.js +1 -1
- package/dist/Components/Toggle/TableToggle.js +11 -7
- package/dist/Components/ToggleSwitch/ToggleSwitch.d.ts +2 -0
- package/dist/Components/ToggleSwitch/ToggleSwitch.js +18 -0
- package/dist/Components/ToggleSwitch/ToggleSwitchProps.d.ts +11 -0
- package/dist/Components/ToggleSwitch/ToggleSwitchProps.js +2 -0
- package/dist/Components/ToolTip/ToolTip.js +6 -6
- package/dist/Components/ToolTip/ToolTipProps.d.ts +3 -1
- package/dist/Components/Trail/TrailWorks.js +1 -1
- package/dist/Components/UpgradeButton/UpgradeButton.js +3 -3
- package/dist/Constants/Sidebar.js +3 -2
- package/dist/Theme/assertPalette.d.ts +1 -1
- package/dist/Theme/assertPalette.js +3 -2
- package/dist/Theme/chakra/Menu.styles.d.ts +3 -0
- package/dist/Theme/chakra/Menu.styles.js +5 -0
- package/dist/Theme/chakra/Popover.styles.d.ts +3 -0
- package/dist/Theme/chakra/Popover.styles.js +5 -1
- package/dist/Theme/chakra/componentStyles.d.ts +6 -0
- package/dist/Theme/chakra/createBrandTheme.js +10 -16
- package/dist/Theme/chakra/focusRing.styles.d.ts +52 -0
- package/dist/Theme/chakra/focusRing.styles.js +46 -0
- package/dist/Theme/provider/ThemeSwitcher.js +5 -2
- package/dist/Theme/tokens/brands/index.js +6 -1
- package/dist/Theme/tokens/brands/zinc/palette.dark.d.ts +1 -0
- package/dist/Theme/tokens/builders/buildDarkPalette.js +6 -1
- package/dist/Theme/tokens/builders/chartColorsFromTheme.js +1 -1
- package/dist/Theme/tokens/builders/focusRing.d.ts +52 -0
- package/dist/Theme/tokens/builders/focusRing.js +92 -0
- package/dist/Theme/tokens/builders/gradientInk.d.ts +22 -0
- package/dist/Theme/tokens/builders/gradientInk.js +28 -0
- package/dist/Theme/tokens/builders/index.d.ts +4 -0
- package/dist/Theme/tokens/builders/index.js +4 -0
- package/dist/Theme/tokens/builders/outlineRung.d.ts +46 -3
- package/dist/Theme/tokens/builders/outlineRung.js +101 -5
- package/dist/Theme/tokens/builders/placeholderInk.d.ts +19 -0
- package/dist/Theme/tokens/builders/placeholderInk.js +35 -0
- package/dist/Theme/tokens/builders/resolvedRungs.d.ts +5 -1
- package/dist/Theme/tokens/builders/resolvedRungs.js +7 -2
- package/dist/Theme/tokens/builders/rowStates.d.ts +13 -0
- package/dist/Theme/tokens/builders/rowStates.js +19 -0
- package/dist/Theme/tokens/builders/scrollbar.d.ts +12 -0
- package/dist/Theme/tokens/builders/scrollbar.js +57 -0
- package/dist/Theme/tokens/builders/surfaceRungs.d.ts +5 -0
- package/dist/Theme/tokens/builders/surfaceRungs.js +20 -5
- package/dist/Theme/tokens/types.d.ts +21 -1
- package/dist/esm/Components/Accordion/Accordion.js +5 -3
- package/dist/esm/Components/AppSwitcher/AppMark.js +56 -0
- package/dist/esm/Components/AppSwitcher/AppSwitcher.js +280 -0
- package/dist/esm/Components/AppSwitcher/AppSwitcherProps.js +78 -0
- package/dist/esm/Components/AppSwitcher/useAppSwitcherShortcut.js +27 -0
- package/dist/esm/Components/AppearanceSettings/AppearanceSettings.js +2 -1
- package/dist/esm/Components/Button/Button.js +5 -4
- package/dist/esm/Components/Button/Button.styles.js +63 -16
- package/dist/esm/Components/ButtonGroupIcon/ButtonGroupIcon.js +6 -6
- package/dist/esm/Components/Card/Card.js +2 -2
- package/dist/esm/Components/Card/PaymentCard/PaymentCard.js +7 -7
- package/dist/esm/Components/Checkbox/Checkbox.js +1 -1
- package/dist/esm/Components/Checkbox/Checkbox.styles.js +13 -8
- package/dist/esm/Components/Common/fieldStyles.js +1 -1
- package/dist/esm/Components/CopyButton/CopyButton.js +7 -3
- package/dist/esm/Components/CustomModulesTable/EditModuleModal.js +1 -1
- package/dist/esm/Components/DatePicker/CalendarPanel.js +24 -5
- package/dist/esm/Components/DatePicker/RangeDatePicker.js +4 -4
- package/dist/esm/Components/DatePicker/SingleDatePicker.js +2 -2
- package/dist/esm/Components/DatePicker/TimeOnlyPicker.js +1 -1
- package/dist/esm/Components/DatePicker/dayStates.js +79 -0
- package/dist/esm/Components/Dropdown/DropDown.js +7 -15
- package/dist/esm/Components/EmailCards/VerifyEmailOtp/VerifyEmailOtp.js +1 -1
- package/dist/esm/Components/EmptyState/EmptyState.js +2 -1
- package/dist/esm/Components/FilePreview/FilePreview.js +1 -1
- package/dist/esm/Components/FileUpload/FileUpload.js +6 -5
- package/dist/esm/Components/FileUpload/constants.js +1 -0
- package/dist/esm/Components/FilterSidebar/FilterSidebar.js +4 -4
- package/dist/esm/Components/FilterSidebar/filterHoverStyle.js +4 -10
- package/dist/esm/Components/Form/FormWrapper.js +1 -1
- package/dist/esm/Components/Header/components/mobileMenu.js +12 -3
- package/dist/esm/Components/Input/TextInput.styles.js +6 -4
- package/dist/esm/Components/InputTextArea/InputTextArea.style.js +5 -4
- package/dist/esm/Components/KanbanBoard/KanbanBoard.js +5 -45
- package/dist/esm/Components/LimitReachedModal/LimitReachedModal.js +4 -5
- package/dist/esm/Components/Loading/Loading.js +12 -11
- package/dist/esm/Components/MultiSelect/MultiSelect.js +1 -1
- package/dist/esm/Components/NavigationBar/NavigationBar.js +3 -3
- package/dist/esm/Components/NoteTextArea/NoteTextArea.js +9 -4
- package/dist/esm/Components/NumberInput/NumberInput.js +4 -2
- package/dist/esm/Components/NumberInput/NumberInput.styles.js +5 -4
- package/dist/esm/Components/OrgSwitcher/OrgSwitcher.js +2 -2
- package/dist/esm/Components/PdfViewer/PdfViewer.js +6 -1
- package/dist/esm/Components/PinInput/PinInput.js +1 -1
- package/dist/esm/Components/PlanPill/PlanPill.js +6 -6
- package/dist/esm/Components/PlanPill/planPillGradient.js +3 -15
- package/dist/esm/Components/Primitives/Primitives.js +6 -0
- package/dist/esm/Components/ProductCard/ProductPrice.js +1 -1
- package/dist/esm/Components/ProductCard/ProductReview.js +6 -1
- package/dist/esm/Components/ProductCard/ProductTags.js +1 -1
- package/dist/esm/Components/ProfileCard/ProfileCard.js +1 -1
- package/dist/esm/Components/ProfilePhotoViewer/ProfilePhotoViewer.js +1 -1
- package/dist/esm/Components/RadioButton/RadioButton.js +3 -2
- package/dist/esm/Components/Reorder/Reorder.js +1 -1
- package/dist/esm/Components/ScrollToTop/ScrollToTop.js +2 -0
- package/dist/esm/Components/ScrollbarBox/ScrollbarBox.js +9 -0
- package/dist/esm/Components/Search/Search.js +2 -2
- package/dist/esm/Components/SearchSelect/SearchSelect.js +14 -9
- package/dist/esm/Components/SegmentedControl/SegmentedControl.js +55 -0
- package/dist/esm/Components/SegmentedControl/SegmentedControlProps.js +1 -0
- package/dist/esm/Components/Select/Select.js +5 -3
- package/dist/esm/Components/Select/Select.styles.js +3 -2
- package/dist/esm/Components/SelectSearch/SelectSearch.js +7 -4
- package/dist/esm/Components/SelectV2/SelectV2.js +18 -14
- package/dist/esm/Components/SideBar/SecondaryBar.js +1 -1
- package/dist/esm/Components/SideBar/SideBar.js +1 -1
- package/dist/esm/Components/SideBar/components/MenuPopoverContent.js +2 -2
- package/dist/esm/Components/Slider/Slider.js +8 -1
- package/dist/esm/Components/StageProgress/StageProgress.js +2 -9
- package/dist/esm/Components/StageProgress/StepperStage.js +2 -2
- package/dist/esm/Components/Table/Table.js +17 -24
- package/dist/esm/Components/Table/components/Pagination.js +1 -1
- package/dist/esm/Components/Table/components/TableActions.js +2 -2
- package/dist/esm/Components/Table/components/TableBody.js +42 -13
- package/dist/esm/Components/Table/components/TableGroupRow.js +1 -1
- package/dist/esm/Components/Table/components/TableHeader.js +21 -8
- package/dist/esm/Components/Table/components/TableSearch.js +1 -1
- package/dist/esm/Components/Table/filters/ActiveFilters.js +2 -18
- package/dist/esm/Components/Table/settings/ManageColumns.js +4 -17
- package/dist/esm/Components/Table/settings/TableSettings.js +5 -11
- package/dist/esm/Components/Table/variants/TableVariantContext.js +13 -0
- package/dist/esm/Components/Table/variants/classic.js +43 -0
- package/dist/esm/Components/Table/variants/index.js +9 -0
- package/dist/esm/Components/Table/variants/pillInk.js +18 -0
- package/dist/esm/Components/Table/variants/studio.js +46 -0
- package/dist/esm/Components/Table/variants/tokens.js +1 -0
- package/dist/esm/Components/Tag/Tag.styles.js +3 -4
- package/dist/esm/Components/Timeline/Timeline.js +2 -2
- package/dist/esm/Components/Toaster/Toaster.js +1 -1
- package/dist/esm/Components/Toggle/TableToggle.js +11 -7
- package/dist/esm/Components/ToggleSwitch/ToggleSwitch.js +15 -0
- package/dist/esm/Components/ToggleSwitch/ToggleSwitchProps.js +1 -0
- package/dist/esm/Components/ToolTip/ToolTip.js +6 -6
- package/dist/esm/Components/Trail/TrailWorks.js +1 -1
- package/dist/esm/Components/UpgradeButton/UpgradeButton.js +3 -3
- package/dist/esm/Constants/Sidebar.js +3 -2
- package/dist/esm/Theme/assertPalette.js +3 -2
- package/dist/esm/Theme/chakra/Menu.styles.js +5 -0
- package/dist/esm/Theme/chakra/Popover.styles.js +5 -1
- package/dist/esm/Theme/chakra/createBrandTheme.js +10 -16
- package/dist/esm/Theme/chakra/focusRing.styles.js +40 -0
- package/dist/esm/Theme/provider/ThemeSwitcher.js +5 -2
- package/dist/esm/Theme/tokens/brands/index.js +6 -1
- package/dist/esm/Theme/tokens/builders/buildDarkPalette.js +6 -1
- package/dist/esm/Theme/tokens/builders/chartColorsFromTheme.js +1 -1
- package/dist/esm/Theme/tokens/builders/focusRing.js +85 -0
- package/dist/esm/Theme/tokens/builders/gradientInk.js +22 -0
- package/dist/esm/Theme/tokens/builders/index.js +4 -0
- package/dist/esm/Theme/tokens/builders/outlineRung.js +96 -4
- package/dist/esm/Theme/tokens/builders/placeholderInk.js +29 -0
- package/dist/esm/Theme/tokens/builders/resolvedRungs.js +7 -2
- package/dist/esm/Theme/tokens/builders/rowStates.js +15 -0
- package/dist/esm/Theme/tokens/builders/scrollbar.js +52 -0
- package/dist/esm/Theme/tokens/builders/surfaceRungs.js +20 -5
- package/dist/esm/index.js +24 -1
- package/dist/index.d.ts +27 -3
- package/dist/index.js +64 -4
- package/package.json +8 -2
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SCROLLBAR_WIDTH = void 0;
|
|
4
|
+
exports.scrollbarStyles = scrollbarStyles;
|
|
5
|
+
exports.globalScrollbarStyles = globalScrollbarStyles;
|
|
6
|
+
/** One width everywhere. The 3px, 4px, 5px and 10px bars in use were all accidents. */
|
|
7
|
+
exports.SCROLLBAR_WIDTH = "6px";
|
|
8
|
+
const RADIUS = "6px";
|
|
9
|
+
/**
|
|
10
|
+
* The single source of scrollbar chrome. `overlay` drops the track for bars that float over a
|
|
11
|
+
* card; `hover` keeps the thumb invisible until the region is hovered.
|
|
12
|
+
*/
|
|
13
|
+
function scrollbarStyles(palette, variant = "default") {
|
|
14
|
+
const { gray } = palette;
|
|
15
|
+
const track = variant === "default" ? gray[100] : "transparent";
|
|
16
|
+
const thumb = variant === "hover" ? "transparent" : gray[400];
|
|
17
|
+
return {
|
|
18
|
+
scrollbarWidth: "thin",
|
|
19
|
+
scrollbarColor: `${thumb} ${track}`,
|
|
20
|
+
"&::-webkit-scrollbar": {
|
|
21
|
+
width: exports.SCROLLBAR_WIDTH,
|
|
22
|
+
height: exports.SCROLLBAR_WIDTH,
|
|
23
|
+
},
|
|
24
|
+
"&::-webkit-scrollbar-track": {
|
|
25
|
+
background: track,
|
|
26
|
+
borderRadius: RADIUS,
|
|
27
|
+
},
|
|
28
|
+
"&::-webkit-scrollbar-thumb": {
|
|
29
|
+
background: thumb,
|
|
30
|
+
borderRadius: RADIUS,
|
|
31
|
+
transition: "background 0.2s ease",
|
|
32
|
+
},
|
|
33
|
+
"&::-webkit-scrollbar-thumb:hover": {
|
|
34
|
+
background: gray[500],
|
|
35
|
+
},
|
|
36
|
+
"&::-webkit-scrollbar-corner": {
|
|
37
|
+
background: track,
|
|
38
|
+
},
|
|
39
|
+
...(variant === "hover"
|
|
40
|
+
? {
|
|
41
|
+
"&:hover": { scrollbarColor: `${gray[400]} ${track}` },
|
|
42
|
+
"&:hover::-webkit-scrollbar-thumb": { background: gray[400] },
|
|
43
|
+
}
|
|
44
|
+
: {}),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/** The same chrome for the document itself, where `&` selectors do not apply. */
|
|
48
|
+
function globalScrollbarStyles(palette) {
|
|
49
|
+
const { gray } = palette;
|
|
50
|
+
return {
|
|
51
|
+
"::-webkit-scrollbar": { width: exports.SCROLLBAR_WIDTH, height: exports.SCROLLBAR_WIDTH },
|
|
52
|
+
"::-webkit-scrollbar-track": { background: gray[100] },
|
|
53
|
+
"::-webkit-scrollbar-thumb": { background: gray[400], borderRadius: RADIUS },
|
|
54
|
+
"::-webkit-scrollbar-thumb:hover": { background: gray[500] },
|
|
55
|
+
"::-webkit-scrollbar-corner": { background: gray[100] },
|
|
56
|
+
};
|
|
57
|
+
}
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import type { PaletteProps } from "../types";
|
|
2
|
+
/**
|
|
3
|
+
* A hint is held to the non-text floor, not the body-text one. Deliberate: at 4.5 an empty field
|
|
4
|
+
* reads as one already typed into. See CHROME-CONSISTENCY.md.
|
|
5
|
+
*/
|
|
6
|
+
export declare const PLACEHOLDER_TEXT = 3;
|
|
2
7
|
export declare const namedSurfaces: (palette: PaletteProps) => string[];
|
|
3
8
|
/** The surface an ink is closest to — clearing it clears every other one. */
|
|
4
9
|
export declare const hardestSurface: (ink: string, surfaces: string[]) => string;
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.hardestSurface = exports.namedSurfaces = void 0;
|
|
3
|
+
exports.hardestSurface = exports.namedSurfaces = exports.PLACEHOLDER_TEXT = void 0;
|
|
4
4
|
exports.withSurfaceRungs = withSurfaceRungs;
|
|
5
5
|
const color_1 = require("./color");
|
|
6
6
|
const outlineRung_1 = require("./outlineRung");
|
|
7
7
|
const accentText_1 = require("./accentText");
|
|
8
8
|
const semanticText_1 = require("./semanticText");
|
|
9
|
+
/**
|
|
10
|
+
* A hint is held to the non-text floor, not the body-text one. Deliberate: at 4.5 an empty field
|
|
11
|
+
* reads as one already typed into. See CHROME-CONSISTENCY.md.
|
|
12
|
+
*/
|
|
13
|
+
exports.PLACEHOLDER_TEXT = outlineRung_1.AA_NON_TEXT;
|
|
9
14
|
const namedSurfaces = (palette) => [
|
|
10
15
|
...Object.values(palette.backgroundColor),
|
|
11
16
|
palette.background[50],
|
|
@@ -19,6 +24,8 @@ exports.hardestSurface = hardestSurface;
|
|
|
19
24
|
function withSurfaceRungs(palette) {
|
|
20
25
|
const surfaces = (0, exports.namedSurfaces)(palette);
|
|
21
26
|
const lift = (ink, target) => (0, outlineRung_1.liftToContrast)(ink, (0, exports.hardestSurface)(ink, surfaces), target);
|
|
27
|
+
const settle = (ink, target) => (0, outlineRung_1.settleAcross)(ink, surfaces, target);
|
|
28
|
+
const rule = (ink) => (0, outlineRung_1.ruleInk)(ink, [palette.backgroundColor.main, palette.background[50]], outlineRung_1.CHROME_RULE);
|
|
22
29
|
const semanticText = semanticText_1.SEMANTIC_TONES.reduce((acc, tone) => {
|
|
23
30
|
acc[tone] = lift(palette.semanticText[tone], accentText_1.AA_NORMAL_TEXT);
|
|
24
31
|
return acc;
|
|
@@ -30,10 +37,18 @@ function withSurfaceRungs(palette) {
|
|
|
30
37
|
semanticText,
|
|
31
38
|
text: { ...palette.text, 500: lift(palette.text[500], accentText_1.AA_NORMAL_TEXT) },
|
|
32
39
|
header: { ...palette.header, 500: lift(palette.header[500], accentText_1.AA_NORMAL_TEXT) },
|
|
33
|
-
placeholder:
|
|
34
|
-
|
|
35
|
-
|
|
40
|
+
// `placeholder` is deliberately absent: the brand authors that rung and the published
|
|
41
|
+
// library ships it untouched. Its floor is enforced in `placeholderInk`, against the
|
|
42
|
+
// surfaces a FIELD renders on — not `table.hover`, which no field sits on.
|
|
43
|
+
// Settled, not lifted: this rung already passed through `withOutlineRung`, and lifting it
|
|
44
|
+
// again here to AA_NON_TEXT is what shipped 3.85-4.95:1 outlines. See CHROME_BORDER.
|
|
45
|
+
boxborder: {
|
|
46
|
+
...palette.boxborder,
|
|
47
|
+
200: rule(palette.boxborder[200]),
|
|
48
|
+
500: settle(palette.boxborder[500], outlineRung_1.CHROME_BORDER),
|
|
36
49
|
},
|
|
37
|
-
|
|
50
|
+
// The 200 rung of both ramps is the same job under two names; settle them together or an
|
|
51
|
+
// app picking `border` gets a heavier rule than one picking `boxborder`.
|
|
52
|
+
border: { ...palette.border, 200: rule(palette.border[200]) },
|
|
38
53
|
};
|
|
39
54
|
}
|
|
@@ -65,6 +65,24 @@ export type SidebarTokens = {
|
|
|
65
65
|
/** Status ink resolved against the RAIL. `semanticText` is canvas-relative, not rail. */
|
|
66
66
|
semanticText: SemanticTextTokens;
|
|
67
67
|
};
|
|
68
|
+
/**
|
|
69
|
+
* The two-tone focus indicator. `ring` is the brand-toned band that carries WCAG 1.4.11
|
|
70
|
+
* against whatever is BEHIND the control; `halo` is the inner band drawn between the ring
|
|
71
|
+
* and the control's own fill, so the indicator stays visible on a filled button too.
|
|
72
|
+
*
|
|
73
|
+
* Two tones because one band cannot own both adjacencies. `ring` is resolved against three
|
|
74
|
+
* backdrops (canvas, card, `primary[500]`) and clears all three on every brand — but a button
|
|
75
|
+
* can be filled by any `colorScheme`, and widening the ring to cover the mid-tone named surfaces
|
|
76
|
+
* as well leaves `skyline` and `storefront` with no solution at any hue (contrast is luminance
|
|
77
|
+
* alone, and their bands come out empty). `halo` takes the fill-side adjacency instead, so the
|
|
78
|
+
* pair holds whatever the button is filled with and whatever it sits on.
|
|
79
|
+
*/
|
|
80
|
+
export type FocusRingTokens = {
|
|
81
|
+
/** Outer band. >=3:1 against the canvas, a card surface, and the control's own fill. */
|
|
82
|
+
ring: string;
|
|
83
|
+
/** Inner band, in the surface tone. >=3:1 against the fill it hugs and against `ring`. */
|
|
84
|
+
halo: string;
|
|
85
|
+
};
|
|
68
86
|
export type BoxShadowTokens = {
|
|
69
87
|
primary: string;
|
|
70
88
|
error: string;
|
|
@@ -102,6 +120,8 @@ export type PaletteProps = {
|
|
|
102
120
|
header: ColorScale;
|
|
103
121
|
placeholder: ColorScale;
|
|
104
122
|
boxShadow: BoxShadowTokens;
|
|
123
|
+
/** Two-tone `:focus-visible` indicator, resolved to 3:1 (WCAG 1.4.11). Never hand-written. */
|
|
124
|
+
focusRing: FocusRingTokens;
|
|
105
125
|
sidebar: SidebarTokens;
|
|
106
126
|
boxborder: ColorScale;
|
|
107
127
|
border: ColorScale;
|
|
@@ -125,7 +145,7 @@ export type PaletteProps = {
|
|
|
125
145
|
};
|
|
126
146
|
/** What a brand writes by hand; every token required, so a gap is a compile error. */
|
|
127
147
|
/** Derived tokens are excluded — `finalizeLightPalette` resolves them per brand. */
|
|
128
|
-
export type BrandPaletteSource = Omit<PaletteProps, "accentText" | "textMuted" | "semanticText" | "sidebar" | "onPrimary" | "onSemantic"> & {
|
|
148
|
+
export type BrandPaletteSource = Omit<PaletteProps, "accentText" | "textMuted" | "semanticText" | "sidebar" | "onPrimary" | "onSemantic" | "focusRing"> & {
|
|
129
149
|
sidebar: {
|
|
130
150
|
background: ColorScale;
|
|
131
151
|
};
|
|
@@ -4,21 +4,23 @@ import { Accordion as ChakraAccordian, AccordionItem, AccordionButton, Accordion
|
|
|
4
4
|
import { ChevronDown } from "lucide-react";
|
|
5
5
|
import { useCustomTheme } from "../../Theme/useCustomTheme.js";
|
|
6
6
|
import { isDarkCanvas } from "../../Theme/tokens/builders/accentText.js";
|
|
7
|
+
import { liftToContrast, AA_NON_TEXT } from "../../Theme/tokens/builders/outlineRung.js";
|
|
7
8
|
import { getAccordionHoverColors } from "./accordionColors.js";
|
|
8
9
|
const Accordion = ({ allowMultiple, allowToggle, index, onChange, reduceMotion, items, ...rest }) => {
|
|
9
10
|
const { colors } = useCustomTheme();
|
|
10
11
|
const isDark = isDarkCanvas(colors.backgroundColor.main);
|
|
11
|
-
const border = colors.boxborder[
|
|
12
|
+
const border = colors.boxborder[200];
|
|
12
13
|
const headingColor = colors.text[800];
|
|
13
14
|
const bodyColor = colors.text[600];
|
|
14
15
|
const { expandedBg, softHover } = useMemo(() => getAccordionHoverColors(colors, isDark), [colors, isDark]);
|
|
16
|
+
const focusRing = useMemo(() => liftToContrast(colors.primary[300], colors.background[50], AA_NON_TEXT), [colors]);
|
|
15
17
|
return (_jsx(ChakraAccordian, { allowMultiple: allowMultiple, allowToggle: allowToggle, index: index, onChange: onChange, reduceMotion: reduceMotion, ...rest, children: items.map((item, idx) => {
|
|
16
18
|
var _a;
|
|
17
|
-
return (_jsx(AccordionItem, { border: `0.063rem solid ${border}`, borderRadius: "xl", bg: colors.background[50], mb: 3, overflow: "hidden", transition: "border-color 0.15s ease, box-shadow 0.15s ease", _last: { mb: 0 }, _hover: { borderColor: colors.boxborder[
|
|
19
|
+
return (_jsx(AccordionItem, { border: `0.063rem solid ${border}`, borderRadius: "xl", bg: colors.background[50], mb: 3, overflow: "hidden", transition: "border-color 0.15s ease, box-shadow 0.15s ease", _last: { mb: 0 }, _hover: { borderColor: colors.boxborder[200] }, ...item.itemProps, sx: (_a = item.customStyles) === null || _a === void 0 ? void 0 : _a.itemStyle, children: ({ isExpanded }) => {
|
|
18
20
|
var _a, _b, _c;
|
|
19
21
|
return (_jsxs(_Fragment, { children: [_jsx("h2", { children: _jsxs(AccordionButton, { px: 4, py: 3, gap: 3, fontSize: "0.875rem", fontWeight: 600, color: headingColor, bg: isExpanded ? expandedBg : "transparent", transition: "background 0.15s ease", _hover: { bg: isExpanded ? expandedBg : softHover }, _focusVisible: {
|
|
20
22
|
boxShadow: "none",
|
|
21
|
-
outline: `0.125rem solid ${
|
|
23
|
+
outline: `0.125rem solid ${focusRing}`,
|
|
22
24
|
outlineOffset: "-0.125rem",
|
|
23
25
|
}, ...item.buttonProps, sx: {
|
|
24
26
|
...(_a = item.customStyles) === null || _a === void 0 ? void 0 : _a.buttonStyle,
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { isValidElement } from "react";
|
|
3
|
+
import { Box, Flex, Image } from "@chakra-ui/react";
|
|
4
|
+
import { useCustomTheme } from "../../Theme/useCustomTheme.js";
|
|
5
|
+
import { mix } from "../../Theme/tokens/builders/color.js";
|
|
6
|
+
import { onFilled } from "../../Theme/tokens/builders/accentText.js";
|
|
7
|
+
import { liftToContrast } from "../../Theme/tokens/builders/outlineRung.js";
|
|
8
|
+
import { AA_NORMAL_TEXT } from "../../Theme/tokens/builders/accentText.js";
|
|
9
|
+
import { CATEGORICAL_SERIES } from "../../Theme/tokens/builders/chartColorsFromTheme.js";
|
|
10
|
+
import { resolveAvatarFill } from "../../Theme/tokens/builders/statusTone.js";
|
|
11
|
+
import { avatarColor } from "../OrgSwitcher/OrgSwitcherProps.js";
|
|
12
|
+
import { appInitials } from "./AppSwitcherProps.js";
|
|
13
|
+
// The app's own hue at 85% strength — the solid mark, softened just off full saturation.
|
|
14
|
+
const FILL_STRENGTH = 0.85;
|
|
15
|
+
/**
|
|
16
|
+
* One hue per app. `avatarColor` hashes into an 8-hue series, so six apps collide more often than
|
|
17
|
+
* not — Billing and Account shipped identical marks. Assigned over the WHOLE list, never the
|
|
18
|
+
* filtered one, so searching cannot repaint a mark.
|
|
19
|
+
*/
|
|
20
|
+
export const assignAppHues = (apps, mode) => {
|
|
21
|
+
const ramp = CATEGORICAL_SERIES[mode].map(resolveAvatarFill);
|
|
22
|
+
const taken = new Set(apps.flatMap((a) => (a.color ? [a.color] : [])));
|
|
23
|
+
return apps.map((app) => {
|
|
24
|
+
if (app.color)
|
|
25
|
+
return app.color;
|
|
26
|
+
const seed = avatarColor(app.id || app.name, mode);
|
|
27
|
+
const start = Math.max(0, ramp.indexOf(seed));
|
|
28
|
+
for (let i = 0; i < ramp.length; i += 1) {
|
|
29
|
+
const next = ramp[(start + i) % ramp.length];
|
|
30
|
+
if (!taken.has(next)) {
|
|
31
|
+
taken.add(next);
|
|
32
|
+
return next;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
// More apps than the series has hues; past this point a repeat is unavoidable.
|
|
36
|
+
return seed;
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
// Softening costs the fill contrast, so it is walked back until a WHITE glyph clears AA — every
|
|
40
|
+
// mark keeps the same white icon instead of some flipping to black.
|
|
41
|
+
export const appTint = (hue, surface, white) => liftToContrast(mix(hue, surface, 1 - FILL_STRENGTH), white, AA_NORMAL_TEXT);
|
|
42
|
+
export const appTintInk = (fill, white, black) => onFilled(fill, white, black);
|
|
43
|
+
const AppMark = ({ app, hue, boxSize, iconSize, muted = false, }) => {
|
|
44
|
+
const { colors } = useCustomTheme();
|
|
45
|
+
if (app.iconUrl) {
|
|
46
|
+
return (_jsx(Image, { src: app.iconUrl, alt: "", boxSize: boxSize, borderRadius: "lg", objectFit: "cover", flexShrink: 0, opacity: muted ? 0.55 : 1 }));
|
|
47
|
+
}
|
|
48
|
+
const surface = colors.backgroundColor.light;
|
|
49
|
+
const fill = muted ? colors.backgroundColor.quaternary : appTint(hue, surface, colors.white);
|
|
50
|
+
const ink = muted ? colors.textMuted : appTintInk(fill, colors.white, colors.black);
|
|
51
|
+
// lucide icons are forwardRef objects, not functions — a typeof check silently drops them.
|
|
52
|
+
const isElement = isValidElement(app.icon);
|
|
53
|
+
const Icon = !isElement && app.icon ? app.icon : null;
|
|
54
|
+
return (_jsx(Flex, { align: "center", justify: "center", boxSize: boxSize, borderRadius: "lg", flexShrink: 0, bg: fill, color: ink, fontSize: `${Math.round(iconSize * 0.72)}px`, fontWeight: 700, "aria-hidden": true, children: Icon ? (_jsx(Icon, { size: iconSize, color: ink })) : isValidElement(app.icon) ? (_jsx(Box, { display: "flex", color: ink, children: app.icon })) : (appInitials(app.name)) }));
|
|
55
|
+
};
|
|
56
|
+
export default AppMark;
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
3
|
+
import { Box, Flex, Input, InputGroup, InputLeftElement, Popover, PopoverBody, PopoverContent, PopoverTrigger, Skeleton, Text, useDisclosure, } from "@chakra-ui/react";
|
|
4
|
+
import { Grip, Lock, Search } from "lucide-react";
|
|
5
|
+
import { useCustomTheme } from "../../Theme/useCustomTheme.js";
|
|
6
|
+
import { chartMode } from "../../Theme/tokens/builders/chartColorsFromTheme.js";
|
|
7
|
+
import { AA_NORMAL_TEXT } from "../../Theme/tokens/builders/accentText.js";
|
|
8
|
+
import { liftToContrast, neutralStep, resolveHoverTint, ruleInk, AA_NON_TEXT, CHROME_RULE, MIN_PERCEPTIBLE_STEP, } from "../../Theme/tokens/builders/outlineRung.js";
|
|
9
|
+
import AppMark, { assignAppHues } from "./AppMark.js";
|
|
10
|
+
import { filterApps, formatShortcut, } from "./AppSwitcherProps.js";
|
|
11
|
+
import { useAppSwitcherShortcut } from "./useAppSwitcherShortcut.js";
|
|
12
|
+
const COLUMNS = 2;
|
|
13
|
+
const HEADING = "Your apps";
|
|
14
|
+
const s = { trigger: "36px", mark: "32px", icon: 17, name: "0.875rem", sub: "0.75rem", row: "52px" };
|
|
15
|
+
const warned = new Set();
|
|
16
|
+
const warnOnce = (key, message) => {
|
|
17
|
+
if (process.env.NODE_ENV === "production" || warned.has(key))
|
|
18
|
+
return;
|
|
19
|
+
warned.add(key);
|
|
20
|
+
console.error(`[AppSwitcher] ${message}`);
|
|
21
|
+
};
|
|
22
|
+
const validate = (apps) => {
|
|
23
|
+
const seen = new Set();
|
|
24
|
+
apps.forEach((app, i) => {
|
|
25
|
+
var _a;
|
|
26
|
+
if (!app.id)
|
|
27
|
+
warnOnce(`id-${i}`, `apps[${i}] ("${(_a = app.name) !== null && _a !== void 0 ? _a : "unnamed"}") has no id.`);
|
|
28
|
+
else if (seen.has(app.id))
|
|
29
|
+
warnOnce(`dup-${app.id}`, `duplicate app id "${app.id}".`);
|
|
30
|
+
else
|
|
31
|
+
seen.add(app.id);
|
|
32
|
+
if (!app.url && !app.locked)
|
|
33
|
+
warnOnce(`url-${app.id || i}`, `app "${app.name}" has no url; it cannot be opened.`);
|
|
34
|
+
});
|
|
35
|
+
};
|
|
36
|
+
// Past a pointer's pause threshold, so crossing the launcher on the way elsewhere does not open it,
|
|
37
|
+
// but short enough that deliberately resting on it feels instant.
|
|
38
|
+
const HOVER_OPEN_DELAY = 450;
|
|
39
|
+
// Long enough to travel the gap between the launcher and the panel below it.
|
|
40
|
+
const HOVER_CLOSE_GRACE = 250;
|
|
41
|
+
// Flip back to true to restore `searchThreshold` opening the field on its own.
|
|
42
|
+
const SEARCH_OPENS_BY_COUNT = false;
|
|
43
|
+
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, }) => {
|
|
44
|
+
const theme = useCustomTheme();
|
|
45
|
+
const { isOpen, onOpen, onClose } = useDisclosure();
|
|
46
|
+
const [query, setQuery] = useState("");
|
|
47
|
+
const [activeIndex, setActiveIndex] = useState(0);
|
|
48
|
+
const searchRef = useRef(null);
|
|
49
|
+
const triggerRef = useRef(null);
|
|
50
|
+
const panelRef = useRef(null);
|
|
51
|
+
const itemRefs = useRef([]);
|
|
52
|
+
const typeahead = useRef({ buffer: "", at: 0 });
|
|
53
|
+
const hoverTimer = useRef();
|
|
54
|
+
const openedByHover = useRef(false);
|
|
55
|
+
useEffect(() => validate(apps), [apps]);
|
|
56
|
+
// Search is off while the panel's design settles: the app count alone no longer opens it. An
|
|
57
|
+
// explicit `isSearchable` still wins, so nothing about the prop changed.
|
|
58
|
+
const showSearch = isSearchable !== null && isSearchable !== void 0 ? isSearchable : (SEARCH_OPENS_BY_COUNT && apps.length >= searchThreshold);
|
|
59
|
+
const hues = useMemo(() => assignAppHues(apps, chartMode(theme.colors)), [apps, theme.colors]);
|
|
60
|
+
const visible = useMemo(() => filterApps(apps, query), [apps, query]);
|
|
61
|
+
const setOpen = useCallback((next) => {
|
|
62
|
+
if (next)
|
|
63
|
+
onOpen();
|
|
64
|
+
else
|
|
65
|
+
onClose();
|
|
66
|
+
onOpenChange === null || onOpenChange === void 0 ? void 0 : onOpenChange(next);
|
|
67
|
+
}, [onOpen, onClose, onOpenChange]);
|
|
68
|
+
const cancelHoverTimer = useCallback(() => {
|
|
69
|
+
if (hoverTimer.current)
|
|
70
|
+
clearTimeout(hoverTimer.current);
|
|
71
|
+
hoverTimer.current = undefined;
|
|
72
|
+
}, []);
|
|
73
|
+
useEffect(() => cancelHoverTimer, [cancelHoverTimer]);
|
|
74
|
+
// Touch and stylus report no hover; a timer there would open the panel on a tap-and-hold.
|
|
75
|
+
const pointerHovers = () => {
|
|
76
|
+
var _a;
|
|
77
|
+
return typeof window !== "undefined" &&
|
|
78
|
+
((_a = window.matchMedia) === null || _a === void 0 ? void 0 : _a.call(window, "(hover: hover) and (pointer: fine)").matches);
|
|
79
|
+
};
|
|
80
|
+
const handleHoverStart = useCallback(() => {
|
|
81
|
+
if (isOpen || !pointerHovers())
|
|
82
|
+
return;
|
|
83
|
+
cancelHoverTimer();
|
|
84
|
+
hoverTimer.current = setTimeout(() => {
|
|
85
|
+
openedByHover.current = true;
|
|
86
|
+
setOpen(true);
|
|
87
|
+
}, HOVER_OPEN_DELAY);
|
|
88
|
+
}, [isOpen, cancelHoverTimer, setOpen]);
|
|
89
|
+
// A click-opened panel is sticky, and a keyboard user inside it is not "hovering away".
|
|
90
|
+
const handleHoverEnd = useCallback(() => {
|
|
91
|
+
cancelHoverTimer();
|
|
92
|
+
if (!isOpen || !openedByHover.current)
|
|
93
|
+
return;
|
|
94
|
+
hoverTimer.current = setTimeout(() => {
|
|
95
|
+
var _a;
|
|
96
|
+
if ((_a = panelRef.current) === null || _a === void 0 ? void 0 : _a.contains(document.activeElement))
|
|
97
|
+
return;
|
|
98
|
+
openedByHover.current = false;
|
|
99
|
+
setOpen(false);
|
|
100
|
+
setQuery("");
|
|
101
|
+
}, HOVER_CLOSE_GRACE);
|
|
102
|
+
}, [isOpen, cancelHoverTimer, setOpen]);
|
|
103
|
+
const closeAndRefocus = useCallback(() => {
|
|
104
|
+
var _a;
|
|
105
|
+
setOpen(false);
|
|
106
|
+
setQuery("");
|
|
107
|
+
(_a = triggerRef.current) === null || _a === void 0 ? void 0 : _a.focus();
|
|
108
|
+
}, [setOpen]);
|
|
109
|
+
useAppSwitcherShortcut(shortcut, () => setOpen(true), !isOpen);
|
|
110
|
+
useEffect(() => {
|
|
111
|
+
if (!isOpen)
|
|
112
|
+
setQuery("");
|
|
113
|
+
setActiveIndex(0);
|
|
114
|
+
}, [isOpen]);
|
|
115
|
+
useEffect(() => setActiveIndex(0), [query]);
|
|
116
|
+
const focusItem = useCallback((next) => {
|
|
117
|
+
var _a;
|
|
118
|
+
setActiveIndex(next);
|
|
119
|
+
(_a = itemRefs.current[next]) === null || _a === void 0 ? void 0 : _a.focus();
|
|
120
|
+
}, []);
|
|
121
|
+
// Read the focused row, not state: two keydowns in one React batch would both compute from a
|
|
122
|
+
// stale activeIndex, so holding an arrow key silently dropped moves.
|
|
123
|
+
const currentIndex = useCallback(() => {
|
|
124
|
+
const focused = itemRefs.current.findIndex((el) => el === document.activeElement);
|
|
125
|
+
return focused === -1 ? activeIndex : focused;
|
|
126
|
+
}, [activeIndex]);
|
|
127
|
+
const step = useCallback((delta) => {
|
|
128
|
+
const count = visible.length;
|
|
129
|
+
if (count === 0)
|
|
130
|
+
return;
|
|
131
|
+
focusItem(((currentIndex() + delta) % count + count) % count);
|
|
132
|
+
}, [currentIndex, visible.length, focusItem]);
|
|
133
|
+
const jumpToLetter = useCallback((char) => {
|
|
134
|
+
const now = Date.now();
|
|
135
|
+
const buffer = now - typeahead.current.at < 600 ? typeahead.current.buffer + char : char;
|
|
136
|
+
typeahead.current = { buffer, at: now };
|
|
137
|
+
const at = currentIndex();
|
|
138
|
+
const from = buffer.length > 1 ? at : at + 1;
|
|
139
|
+
const order = visible.map((_, i) => (from + i) % visible.length);
|
|
140
|
+
const hit = order.find((i) => visible[i].name.toLowerCase().startsWith(buffer));
|
|
141
|
+
if (hit !== undefined)
|
|
142
|
+
focusItem(hit);
|
|
143
|
+
}, [visible, currentIndex, focusItem]);
|
|
144
|
+
// Chakra parks focus on the panel itself, where no arrow handler lives; route the first press in.
|
|
145
|
+
const handlePanelKeyDown = useCallback((e) => {
|
|
146
|
+
if (itemRefs.current.includes(document.activeElement))
|
|
147
|
+
return;
|
|
148
|
+
if (visible.length === 0)
|
|
149
|
+
return;
|
|
150
|
+
if (["ArrowDown", "ArrowRight", "Home"].includes(e.key)) {
|
|
151
|
+
e.preventDefault();
|
|
152
|
+
focusItem(0);
|
|
153
|
+
}
|
|
154
|
+
else if (["ArrowUp", "ArrowLeft", "End"].includes(e.key)) {
|
|
155
|
+
e.preventDefault();
|
|
156
|
+
focusItem(visible.length - 1);
|
|
157
|
+
}
|
|
158
|
+
else if (e.key === "Escape") {
|
|
159
|
+
e.preventDefault();
|
|
160
|
+
closeAndRefocus();
|
|
161
|
+
}
|
|
162
|
+
}, [visible.length, focusItem, closeAndRefocus]);
|
|
163
|
+
const handleItemKeyDown = useCallback((e, app) => {
|
|
164
|
+
if (e.key === "ArrowDown") {
|
|
165
|
+
e.preventDefault();
|
|
166
|
+
step(COLUMNS);
|
|
167
|
+
}
|
|
168
|
+
else if (e.key === "ArrowUp") {
|
|
169
|
+
e.preventDefault();
|
|
170
|
+
step(-COLUMNS);
|
|
171
|
+
}
|
|
172
|
+
else if (e.key === "ArrowRight") {
|
|
173
|
+
e.preventDefault();
|
|
174
|
+
step(1);
|
|
175
|
+
}
|
|
176
|
+
else if (e.key === "ArrowLeft") {
|
|
177
|
+
e.preventDefault();
|
|
178
|
+
step(-1);
|
|
179
|
+
}
|
|
180
|
+
else if (e.key === "Home") {
|
|
181
|
+
e.preventDefault();
|
|
182
|
+
focusItem(0);
|
|
183
|
+
}
|
|
184
|
+
else if (e.key === "End") {
|
|
185
|
+
e.preventDefault();
|
|
186
|
+
focusItem(visible.length - 1);
|
|
187
|
+
}
|
|
188
|
+
else if (e.key === "Escape") {
|
|
189
|
+
e.preventDefault();
|
|
190
|
+
closeAndRefocus();
|
|
191
|
+
}
|
|
192
|
+
else if (e.key === " " && !app.locked) {
|
|
193
|
+
e.preventDefault();
|
|
194
|
+
e.currentTarget.click();
|
|
195
|
+
}
|
|
196
|
+
else if (e.key.length === 1 && !e.metaKey && !e.ctrlKey && !e.altKey)
|
|
197
|
+
jumpToLetter(e.key.toLowerCase());
|
|
198
|
+
}, [step, focusItem, visible.length, closeAndRefocus, jumpToLetter]);
|
|
199
|
+
const handleSelect = (app, e) => {
|
|
200
|
+
if (app.locked) {
|
|
201
|
+
e.preventDefault();
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
onSelect === null || onSelect === void 0 ? void 0 : onSelect(app, e);
|
|
205
|
+
if (!e.defaultPrevented)
|
|
206
|
+
setOpen(false);
|
|
207
|
+
};
|
|
208
|
+
const nothingToSwitchTo = apps.length === 0 || (hideWhenAlone && apps.every((a) => a.id === currentAppId));
|
|
209
|
+
if (nothingToSwitchTo)
|
|
210
|
+
return null;
|
|
211
|
+
const rowHoverBg = resolveHoverTint(theme.colors.gray[50], theme.colors.backgroundColor.light);
|
|
212
|
+
const footerRule = ruleInk(theme.colors.boxborder[200], [theme.colors.backgroundColor.subtle], CHROME_RULE);
|
|
213
|
+
// Neutral, not `backgroundColor.tertiary`: that rung is brand-tinted in the hand-authored light
|
|
214
|
+
// palettes, so lifting it painted the row a saturated block. The rail identifies the row, so the
|
|
215
|
+
// fill only has to be perceptible — pushed further it is a mid-grey slab that reads disabled.
|
|
216
|
+
const currentBg = neutralStep(theme.colors.backgroundColor.light, MIN_PERCEPTIBLE_STEP);
|
|
217
|
+
// The brand rail is the whole marker — no ring, no badge. `accentText` already clears AA as ink
|
|
218
|
+
// on the canvas; lifting it to AA_NON_TEXT here guarantees it on the row's own fill too, for a
|
|
219
|
+
// brand whose accent is pale.
|
|
220
|
+
const currentBar = liftToContrast(theme.colors.accentText, currentBg, AA_NON_TEXT);
|
|
221
|
+
// A derived surface is outside what `withSurfaceRungs` settled, so its ink is resolved here.
|
|
222
|
+
const currentName = liftToContrast(theme.colors.text[900], currentBg, AA_NORMAL_TEXT);
|
|
223
|
+
const currentSub = liftToContrast(theme.colors.textMuted, currentBg, AA_NORMAL_TEXT);
|
|
224
|
+
const width = panelWidth !== null && panelWidth !== void 0 ? panelWidth : "28rem";
|
|
225
|
+
const hint = shortcut && showShortcutHint ? formatShortcut(shortcut) : null;
|
|
226
|
+
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] }, onMouseEnter: handleHoverStart, onMouseLeave: handleHoverEnd, onClick: () => { openedByHover.current = false; cancelHoverTimer(); }, onKeyDown: (e) => {
|
|
227
|
+
if (!isOpen) {
|
|
228
|
+
if (e.key === "ArrowDown") {
|
|
229
|
+
e.preventDefault();
|
|
230
|
+
setOpen(true);
|
|
231
|
+
}
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
handlePanelKeyDown(e);
|
|
235
|
+
}, children: _jsx(Grip, { size: s.icon + 1 }) }) }), _jsx(PopoverContent, { ref: panelRef, onMouseEnter: cancelHoverTimer, onMouseLeave: handleHoverEnd, 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) => {
|
|
236
|
+
if (e.key === "ArrowDown" && visible.length > 0) {
|
|
237
|
+
e.preventDefault();
|
|
238
|
+
focusItem(0);
|
|
239
|
+
}
|
|
240
|
+
else if (e.key === "Escape") {
|
|
241
|
+
e.preventDefault();
|
|
242
|
+
if (query)
|
|
243
|
+
setQuery("");
|
|
244
|
+
else
|
|
245
|
+
closeAndRefocus();
|
|
246
|
+
}
|
|
247
|
+
} })] }) })), _jsxs(Box, { role: "menu", "aria-label": HEADING, maxH: maxHeight, overflowY: "auto", px: 2, pb: 2, display: "grid", gridTemplateColumns: `repeat(${COLUMNS}, minmax(0, 1fr))`, gap: "2px", children: [isLoading
|
|
248
|
+
? [0, 1, 2, 3].map((i) => (_jsxs(Flex, { align: "center", gap: 3, px: 2, py: 2, minH: s.row, children: [_jsx(Skeleton, { boxSize: s.mark, borderRadius: "lg" }), _jsx(Skeleton, { h: "0.75rem", flex: "1", borderRadius: "base" })] }, i)))
|
|
249
|
+
: visible.map((app, idx) => {
|
|
250
|
+
var _a;
|
|
251
|
+
const isCurrent = app.id === currentAppId;
|
|
252
|
+
const interactive = !isCurrent && !app.locked;
|
|
253
|
+
return (_jsxs(Flex, { as: interactive ? "a" : "div", role: "menuitem", ref: (el) => { itemRefs.current[idx] = el; }, tabIndex: idx === activeIndex ? 0 : -1, "aria-current": isCurrent ? "true" : undefined, "aria-disabled": app.locked ? true : undefined, title: app.locked ? ((_a = app.lockedReason) !== null && _a !== void 0 ? _a : `${app.name} is not available on your plan`) : undefined, ...(interactive
|
|
254
|
+
? {
|
|
255
|
+
href: app.url,
|
|
256
|
+
target: "_blank",
|
|
257
|
+
rel: "noopener noreferrer",
|
|
258
|
+
}
|
|
259
|
+
: {}), align: "center", gap: 3, minH: s.row, px: 2, py: 2, borderRadius: "lg", textDecoration: "none", cursor: interactive ? "pointer" : "default", bg: isCurrent ? currentBg : "transparent", position: "relative",
|
|
260
|
+
// Full height, taking the row's own `lg` radius on its outer corners so the
|
|
261
|
+
// rail follows the row's curve instead of cutting across it.
|
|
262
|
+
_before: isCurrent
|
|
263
|
+
? {
|
|
264
|
+
content: '""',
|
|
265
|
+
position: "absolute",
|
|
266
|
+
insetInlineStart: 0,
|
|
267
|
+
insetBlock: 0,
|
|
268
|
+
width: "3px",
|
|
269
|
+
borderStartStartRadius: "lg",
|
|
270
|
+
borderEndStartRadius: "lg",
|
|
271
|
+
bg: currentBar,
|
|
272
|
+
}
|
|
273
|
+
: undefined, transition: "background 0.12s ease", _hover: interactive ? { bg: rowHoverBg } : undefined, onClick: (e) => handleSelect(app, e), onKeyDown: (e) => handleItemKeyDown(e, app), children: [_jsx(AppMark, { app: app, hue: hues[apps.indexOf(app)], boxSize: s.mark, iconSize: s.icon, muted: app.locked }), _jsxs(Box, { flex: "1", minW: 0, children: [_jsxs(Flex, { align: "center", gap: 1.5, children: [_jsx(Text, { fontSize: s.name, fontWeight: 600, color: app.locked
|
|
274
|
+
? theme.colors.textMuted
|
|
275
|
+
: isCurrent
|
|
276
|
+
? currentName
|
|
277
|
+
: theme.colors.text[900], overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", children: app.name }), app.badge !== undefined && !isCurrent && (_jsx(Text, { fontSize: "0.625rem", fontWeight: 600, color: theme.colors.semanticText.info, flexShrink: 0, children: app.badge }))] }), app.description && (_jsx(Text, { fontSize: s.sub, color: isCurrent ? currentSub : theme.colors.textMuted, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", children: app.description }))] }), app.locked && (_jsx(Box, { as: Lock, size: 14, color: theme.colors.textMuted, flexShrink: 0 }))] }, app.id || app.name));
|
|
278
|
+
}), !isLoading && visible.length === 0 && (_jsx(Box, { gridColumn: `span ${COLUMNS}`, px: 2, py: 6, textAlign: "center", children: _jsx(Text, { fontSize: s.name, color: theme.colors.textMuted, children: emptyMessage }) }))] }), footer && (_jsx(Flex, { align: "center", gap: 1.5, px: 3, py: 2.5, borderTop: "1px solid", borderTopColor: footerRule, bg: theme.colors.backgroundColor.subtle, children: footer }))] }) })] }));
|
|
279
|
+
};
|
|
280
|
+
export default AppSwitcher;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
const NON_WORD = /[^\p{L}\p{N}]+/u;
|
|
2
|
+
/** Up to two initials, so a mark stays legible without an icon. */
|
|
3
|
+
export const appInitials = (name) => {
|
|
4
|
+
const words = name.trim().split(NON_WORD).filter(Boolean);
|
|
5
|
+
if (words.length === 0)
|
|
6
|
+
return "?";
|
|
7
|
+
if (words.length === 1)
|
|
8
|
+
return words[0].slice(0, 2).toUpperCase();
|
|
9
|
+
return (words[0][0] + words[1][0]).toUpperCase();
|
|
10
|
+
};
|
|
11
|
+
export const filterApps = (apps, query) => {
|
|
12
|
+
const q = query.trim().toLowerCase();
|
|
13
|
+
if (!q)
|
|
14
|
+
return apps;
|
|
15
|
+
return apps.filter((app) => {
|
|
16
|
+
var _a, _b;
|
|
17
|
+
return [app.name, (_a = app.description) !== null && _a !== void 0 ? _a : "", ...((_b = app.keywords) !== null && _b !== void 0 ? _b : [])]
|
|
18
|
+
.join(" ")
|
|
19
|
+
.toLowerCase()
|
|
20
|
+
.includes(q);
|
|
21
|
+
});
|
|
22
|
+
};
|
|
23
|
+
/** "mod" is Cmd on Apple platforms and Ctrl everywhere else. */
|
|
24
|
+
export const matchesShortcut = (event, shortcut, isApple = typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.platform)) => {
|
|
25
|
+
const parts = shortcut.toLowerCase().split("+").map((p) => p.trim()).filter(Boolean);
|
|
26
|
+
if (parts.length === 0)
|
|
27
|
+
return false;
|
|
28
|
+
const key = parts[parts.length - 1];
|
|
29
|
+
const mods = new Set(parts.slice(0, -1));
|
|
30
|
+
const wantsMod = mods.has("mod");
|
|
31
|
+
const wantsCtrl = mods.has("ctrl") || (wantsMod && !isApple);
|
|
32
|
+
const wantsMeta = mods.has("cmd") || mods.has("meta") || (wantsMod && isApple);
|
|
33
|
+
if (event.ctrlKey !== wantsCtrl)
|
|
34
|
+
return false;
|
|
35
|
+
if (event.metaKey !== wantsMeta)
|
|
36
|
+
return false;
|
|
37
|
+
if (event.shiftKey !== mods.has("shift"))
|
|
38
|
+
return false;
|
|
39
|
+
if (event.altKey !== mods.has("alt"))
|
|
40
|
+
return false;
|
|
41
|
+
return event.key.toLowerCase() === key;
|
|
42
|
+
};
|
|
43
|
+
const APPLE_SYMBOLS = { mod: "⌘", cmd: "⌘", meta: "⌘", ctrl: "⌃", shift: "⇧", alt: "⌥" };
|
|
44
|
+
const OTHER_LABELS = { mod: "Ctrl", cmd: "Win", meta: "Win", ctrl: "Ctrl", shift: "Shift", alt: "Alt" };
|
|
45
|
+
export const formatShortcut = (shortcut, isApple = typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.platform)) => {
|
|
46
|
+
const parts = shortcut.toLowerCase().split("+").map((p) => p.trim()).filter(Boolean);
|
|
47
|
+
const table = isApple ? APPLE_SYMBOLS : OTHER_LABELS;
|
|
48
|
+
const rendered = parts.map((p, i) => { var _a; return i === parts.length - 1 ? p.toUpperCase() : ((_a = table[p]) !== null && _a !== void 0 ? _a : p.toUpperCase()); });
|
|
49
|
+
return isApple ? rendered.join("") : rendered.join("+");
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Derives every app's origin from the host the consumer is already served on, so one deployment
|
|
53
|
+
* variable keeps dev, test and prod in step. An app may still carry its own `url`, which wins.
|
|
54
|
+
*/
|
|
55
|
+
export const buildSuiteApps = (apps, { baseUrl, baseSubdomain }) => {
|
|
56
|
+
let base;
|
|
57
|
+
try {
|
|
58
|
+
base = new URL(baseUrl.trim());
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
throw new Error(`[AppSwitcher] buildSuiteApps: baseUrl ${JSON.stringify(baseUrl)} is not an absolute URL.`);
|
|
62
|
+
}
|
|
63
|
+
const labels = base.hostname.split(".");
|
|
64
|
+
const at = labels.indexOf(baseSubdomain);
|
|
65
|
+
return apps.map(({ subdomain, url, ...app }) => {
|
|
66
|
+
if (url)
|
|
67
|
+
return { ...app, url: url.trim() };
|
|
68
|
+
// Without the label there is nothing to swap, and every app would silently share one origin.
|
|
69
|
+
if (at === -1) {
|
|
70
|
+
throw new Error(`[AppSwitcher] buildSuiteApps: ${JSON.stringify(baseSubdomain)} is not a label in ` +
|
|
71
|
+
`"${base.hostname}", so ${JSON.stringify(app.name)} has no derivable url. ` +
|
|
72
|
+
`Give that app an explicit url, or pass a baseUrl that contains the label.`);
|
|
73
|
+
}
|
|
74
|
+
const next = new URL(base.toString());
|
|
75
|
+
next.hostname = labels.map((l, i) => (i === at ? subdomain : l)).join(".");
|
|
76
|
+
return { ...app, url: next.origin };
|
|
77
|
+
});
|
|
78
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { useEffect } from "react";
|
|
2
|
+
import { matchesShortcut } from "./AppSwitcherProps.js";
|
|
3
|
+
const isTypingTarget = (target) => {
|
|
4
|
+
if (!(target instanceof HTMLElement))
|
|
5
|
+
return false;
|
|
6
|
+
if (target.isContentEditable)
|
|
7
|
+
return true;
|
|
8
|
+
return ["INPUT", "TEXTAREA", "SELECT"].includes(target.tagName);
|
|
9
|
+
};
|
|
10
|
+
/** Fires `onTrigger` on the shortcut. Inert when `shortcut` is null, and never in SSR. */
|
|
11
|
+
export const useAppSwitcherShortcut = (shortcut, onTrigger, enabled = true) => {
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
if (!shortcut || !enabled || typeof window === "undefined")
|
|
14
|
+
return undefined;
|
|
15
|
+
const handler = (event) => {
|
|
16
|
+
if (event.defaultPrevented || isTypingTarget(event.target))
|
|
17
|
+
return;
|
|
18
|
+
if (!matchesShortcut(event, shortcut))
|
|
19
|
+
return;
|
|
20
|
+
event.preventDefault();
|
|
21
|
+
onTrigger();
|
|
22
|
+
};
|
|
23
|
+
window.addEventListener("keydown", handler);
|
|
24
|
+
return () => window.removeEventListener("keydown", handler);
|
|
25
|
+
}, [shortcut, enabled, onTrigger]);
|
|
26
|
+
};
|
|
27
|
+
export default useAppSwitcherShortcut;
|