@datalayer/primer-addons 1.0.21 → 1.0.22
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/README.md +14 -0
- package/lib/components/appearance/AppearanceMenuWithStore.d.ts +26 -0
- package/lib/components/appearance/AppearanceMenuWithStore.js +21 -0
- package/lib/components/appearance/CollaboratorPalette.d.ts +65 -0
- package/lib/components/appearance/CollaboratorPalette.js +160 -0
- package/lib/components/appearance/index.d.ts +2 -0
- package/lib/components/appearance/index.js +2 -0
- package/lib/components/box/Box.d.ts +17 -5
- package/lib/components/box/Box.js +3 -5
- package/lib/components/calendar-picker/CalendarPicker.d.ts +35 -0
- package/lib/components/calendar-picker/CalendarPicker.js +146 -0
- package/lib/components/calendar-picker/index.d.ts +1 -0
- package/lib/components/calendar-picker/index.js +5 -0
- package/lib/components/card/Card.d.ts +6 -1
- package/lib/components/card/Card.js +1 -1
- package/lib/components/color/ColorSwatch.d.ts +1 -1
- package/lib/components/color-picker/ColorPicker.d.ts +97 -0
- package/lib/components/color-picker/ColorPicker.js +358 -0
- package/lib/components/color-picker/index.d.ts +1 -0
- package/lib/components/color-picker/index.js +5 -0
- package/lib/components/content-loader/ContentLoader.d.ts +1 -1
- package/lib/components/content-loader/ContentLoader.stories.d.ts +1 -1
- package/lib/components/date-picker/DatePicker.d.ts +46 -0
- package/lib/components/date-picker/DatePicker.js +98 -0
- package/lib/components/date-picker/index.d.ts +1 -0
- package/lib/components/date-picker/index.js +5 -0
- package/lib/components/icons/CircleIcon.d.ts +1 -1
- package/lib/components/icons/CircleIcon.stories.d.ts +1 -1
- package/lib/components/index.d.ts +5 -0
- package/lib/components/index.js +5 -0
- package/lib/components/logo/DatalayerText.d.ts +1 -1
- package/lib/components/logo/index.d.ts +0 -1
- package/lib/components/logo/index.js +0 -1
- package/lib/components/side-overlay/SideOverlay.d.ts +5 -5
- package/lib/components/side-overlay/SideOverlay.stories.d.ts +1 -1
- package/lib/components/slider/Slider.d.ts +1 -1
- package/lib/components/slider/Slider.stories.d.ts +1 -1
- package/lib/components/sliding-panel/SlidingPanel.d.ts +1 -1
- package/lib/components/sliding-panel/SlidingPanel.stories.d.ts +1 -1
- package/lib/components/toolbar/Toolbar.d.ts +2 -2
- package/lib/components/toolbar/Toolbar.js +0 -0
- package/lib/components/toolbar/ToolbarButton.d.ts +3 -1
- package/lib/components/toolbar/ToolbarButton.js +54 -22
- package/lib/components/toolbar/ToolbarDivider.d.ts +1 -1
- package/lib/components/toolbar/ToolbarDropdown.d.ts +3 -3
- package/lib/components/toolbar/ToolbarDropdown.js +66 -35
- package/lib/components/toolbar/ToolbarRenderer.d.ts +20 -3
- package/lib/components/toolbar/ToolbarRenderer.js +16 -16
- package/lib/components/toolbar/types.d.ts +8 -8
- package/lib/reactor/AppearancePlugin.d.ts +31 -0
- package/lib/reactor/AppearancePlugin.js +63 -0
- package/lib/reactor/ThemePlugin.d.ts +5 -0
- package/lib/reactor/ThemePlugin.js +98 -0
- package/lib/reactor/index.d.ts +30 -0
- package/lib/reactor/index.js +34 -0
- package/lib/reactor/page-layout/PageLayout.d.ts +148 -0
- package/lib/reactor/page-layout/PageLayout.js +310 -0
- package/lib/reactor/page-layout/PageLayoutPlugin.d.ts +59 -0
- package/lib/reactor/page-layout/PageLayoutPlugin.js +105 -0
- package/lib/reactor/page-layout/PagePanelToggle.d.ts +21 -0
- package/lib/reactor/page-layout/PagePanelToggle.js +29 -0
- package/lib/reactor/page-layout/SlotPageLayout.d.ts +36 -0
- package/lib/reactor/page-layout/SlotPageLayout.js +25 -0
- package/lib/reactor/page-layout/panelState.d.ts +28 -0
- package/lib/reactor/page-layout/panelState.js +49 -0
- package/lib/story-helpers.d.ts +2 -2
- package/lib/theme/DatalayerBrandThemeProvider.d.ts +1 -1
- package/lib/theme/DatalayerThemeProvider.d.ts +5 -5
- package/lib/theme/DatalayerThemeProvider.js +119 -58
- package/lib/theme/css/createThemeCSSVars.d.ts +1 -0
- package/lib/theme/css/createThemeCSSVars.js +9 -0
- package/lib/theme/fontStacks.d.ts +2 -0
- package/lib/theme/fontStacks.js +6 -0
- package/lib/theme/index.d.ts +2 -0
- package/lib/theme/index.js +2 -0
- package/lib/theme/palettes/ColorPalette.d.ts +3 -3
- package/lib/theme/portableTheme.d.ts +32 -0
- package/lib/theme/portableTheme.js +45 -0
- package/lib/utils/Helper.d.ts +2 -2
- package/lib/utils/Styles.d.ts +1 -1
- package/package.json +15 -14
- package/lib/components/logo/DI.d.ts +0 -19
- package/lib/components/logo/DI.js +0 -20
package/README.md
CHANGED
|
@@ -25,6 +25,20 @@ This project has been [shared in a discussion](https://github.com/primer/react/d
|
|
|
25
25
|
- [x] Slider
|
|
26
26
|
- [x] Toolbar
|
|
27
27
|
|
|
28
|
+
## Reactor Plugins
|
|
29
|
+
|
|
30
|
+
Three plugins for hosts built on [`@datalayer/reactor`](https://github.com/datalayer/reactor), imported by path so a page without a reactor never pulls it in:
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
import { AppearancePlugin, PageLayoutPlugin, ThemePlugin } from '@datalayer/primer-addons/lib/reactor';
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
- `ThemePlugin` keeps Primer's portals in the application's color mode and toggles that mode by command (`Mod+Alt+C`).
|
|
37
|
+
- `AppearancePlugin` puts the appearance menu — color mode, theme, the theme's description and a preview of it, the control the Datalayer header wears — in a slot, `header` by default, and brings `ThemePlugin` along as a dependency.
|
|
38
|
+
- `PageLayoutPlugin` arranges a host's slots as a page: what plugins put in `page` lies on a centred sheet over a quiet canvas, `page-band` is docked above it at the sheet's width (or floats over it as a card), `page-chips` sit under the band, and `page-panel` is a side panel opened from a toggle in the header. The layout (`PageLayout`), its toggle and its signals (`pageLayoutPanelOpen`, `openPagePanel()`…) are exported too, for a host that wires the parts itself — the Loop of `@datalayer/agent-runtimes` does, with the notebook as the page and the conversation as the panel.
|
|
39
|
+
|
|
40
|
+
The [gallery example](examples/gallery) is a Reactor app built on the three: `npm run gallery`.
|
|
41
|
+
|
|
28
42
|
<div align="center" style="text-align: center">
|
|
29
43
|
<img alt="Primer React Addons" src="https://assets.datalayer.tech/primer-addons-example.png" />
|
|
30
44
|
</div>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ReactElement } from 'react';
|
|
2
|
+
import type { StoreApi, UseBoundStore } from 'zustand';
|
|
3
|
+
import { type ThemeState } from '../../theme/useThemeStore';
|
|
4
|
+
import { type AppearanceMenuProps } from './AppearanceMenu';
|
|
5
|
+
export interface AppearanceMenuWithStoreProps extends Omit<AppearanceMenuProps, 'colorMode' | 'themeVariant' | 'onColorModeChange' | 'onThemeChange'> {
|
|
6
|
+
/** The store the menu reads and writes; the shared one by default. */
|
|
7
|
+
useStore?: UseBoundStore<StoreApi<ThemeState>>;
|
|
8
|
+
/**
|
|
9
|
+
* Whether choosing a theme also switches to that theme's default color
|
|
10
|
+
* mode. Off by default: a person who chose dark keeps dark while they try
|
|
11
|
+
* another theme, which is how the public Datalayer header behaves.
|
|
12
|
+
*/
|
|
13
|
+
applyThemeColorMode?: boolean;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* The {@link AppearanceMenu} bound to a theme store, so a header mounts it
|
|
17
|
+
* with no props at all.
|
|
18
|
+
*
|
|
19
|
+
* The menu itself is controlled: a page with its own idea of where the theme
|
|
20
|
+
* lives hands it values and callbacks. Most pages have no such idea — the
|
|
21
|
+
* theme lives in the store — and this reads it from there and writes the
|
|
22
|
+
* choices back. The same shape as {@link AppearanceControlsWithStore}, for
|
|
23
|
+
* the whole menu: the trigger, the overlay, the description, the preview.
|
|
24
|
+
*/
|
|
25
|
+
export declare function AppearanceMenuWithStore({ useStore, applyThemeColorMode, ...menu }: AppearanceMenuWithStoreProps): ReactElement;
|
|
26
|
+
export default AppearanceMenuWithStore;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useThemeStore } from '../../theme/useThemeStore';
|
|
3
|
+
import { AppearanceMenu } from './AppearanceMenu';
|
|
4
|
+
/**
|
|
5
|
+
* The {@link AppearanceMenu} bound to a theme store, so a header mounts it
|
|
6
|
+
* with no props at all.
|
|
7
|
+
*
|
|
8
|
+
* The menu itself is controlled: a page with its own idea of where the theme
|
|
9
|
+
* lives hands it values and callbacks. Most pages have no such idea — the
|
|
10
|
+
* theme lives in the store — and this reads it from there and writes the
|
|
11
|
+
* choices back. The same shape as {@link AppearanceControlsWithStore}, for
|
|
12
|
+
* the whole menu: the trigger, the overlay, the description, the preview.
|
|
13
|
+
*/
|
|
14
|
+
export function AppearanceMenuWithStore({ useStore = useThemeStore, applyThemeColorMode = false, ...menu }) {
|
|
15
|
+
const colorMode = useStore((state) => state.colorMode);
|
|
16
|
+
const themeVariant = useStore((state) => state.theme);
|
|
17
|
+
const setColorMode = useStore((state) => state.setColorMode);
|
|
18
|
+
const setTheme = useStore((state) => state.setTheme);
|
|
19
|
+
return (_jsx(AppearanceMenu, { colorMode: colorMode, themeVariant: themeVariant, onColorModeChange: setColorMode, onThemeChange: (theme) => setTheme(theme, applyThemeColorMode), ...menu }));
|
|
20
|
+
}
|
|
21
|
+
export default AppearanceMenuWithStore;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { type ThemeVariant } from "../../theme/themeRegistry";
|
|
2
|
+
/**
|
|
3
|
+
* Which of the bright palette's colours a collaborator can be given, in the
|
|
4
|
+
* order they are handed out.
|
|
5
|
+
*
|
|
6
|
+
* Interleaved on purpose: green, red, blue, yellow, cyan, orange, lime, so
|
|
7
|
+
* the second person in a room is never a near-neighbour of the first.
|
|
8
|
+
* `onGlow` is not here — it is a text colour, not a hue.
|
|
9
|
+
*/
|
|
10
|
+
export declare const COLLABORATOR_SLOTS: readonly ["glow", "blaze", "surge", "gold", "pop", "flame", "spark"];
|
|
11
|
+
/** How many colours a room can hand out before it repeats one. */
|
|
12
|
+
export declare const COLLABORATOR_COLOR_COUNT: 7;
|
|
13
|
+
/** The collaborator colours of one theme, in one colour mode. */
|
|
14
|
+
export declare function collaboratorColors(variant?: ThemeVariant, colorMode?: "light" | "dark" | "auto"): string[];
|
|
15
|
+
/**
|
|
16
|
+
* A number from a string, stable across machines and runs.
|
|
17
|
+
*
|
|
18
|
+
* djb2: small, and — unlike anything built on iteration order or `Math.random`
|
|
19
|
+
* — it gives every browser in the room the same answer for the same person,
|
|
20
|
+
* which is the whole point.
|
|
21
|
+
*/
|
|
22
|
+
export declare function collaboratorHash(seed: string): number;
|
|
23
|
+
/**
|
|
24
|
+
* The colour this person holds: the same one, everywhere, every session.
|
|
25
|
+
*
|
|
26
|
+
* `seed` should be whatever identifies them to the room — a user id where
|
|
27
|
+
* there is one, their handle otherwise.
|
|
28
|
+
*/
|
|
29
|
+
export declare function collaboratorColor(seed: string, variant?: ThemeVariant, colorMode?: "light" | "dark" | "auto"): string;
|
|
30
|
+
/**
|
|
31
|
+
* The palette as CSS custom properties.
|
|
32
|
+
*
|
|
33
|
+
* JupyterLab reads `--jp-collaborator-color1` through `7`; anything of ours
|
|
34
|
+
* can read `--dla-collaborator-color1` through `7`. Both names carry the same
|
|
35
|
+
* colours, so a JupyterLab surface and a Primer surface in the same page
|
|
36
|
+
* agree on who is who.
|
|
37
|
+
*/
|
|
38
|
+
export declare function collaboratorColorVars(variant?: ThemeVariant, colorMode?: "light" | "dark" | "auto"): Record<string, string>;
|
|
39
|
+
/** The collaborator colours of whichever theme is on, following the store. */
|
|
40
|
+
export declare function useCollaboratorColors(): string[];
|
|
41
|
+
/** One person's colour, in whichever theme is on. */
|
|
42
|
+
export declare function useCollaboratorColor(seed: string): string;
|
|
43
|
+
export interface CollaboratorPaletteProps {
|
|
44
|
+
/**
|
|
45
|
+
* People to colour, in the order they joined. Named, the swatch is theirs
|
|
46
|
+
* and is picked by hash; unnamed, the palette is shown slot by slot.
|
|
47
|
+
*/
|
|
48
|
+
people?: readonly string[];
|
|
49
|
+
/** The theme to read. The active one by default. */
|
|
50
|
+
variant?: ThemeVariant;
|
|
51
|
+
/** The mode to read. The active one by default. */
|
|
52
|
+
colorMode?: "light" | "dark" | "auto";
|
|
53
|
+
/** Show the hex value under each swatch. */
|
|
54
|
+
showValues?: boolean;
|
|
55
|
+
/** How tall each swatch stands, in pixels. */
|
|
56
|
+
height?: number;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* The palette, drawn.
|
|
60
|
+
*
|
|
61
|
+
* A row of swatches — one per collaborator colour, or one per named person —
|
|
62
|
+
* for a settings page, a room's roster, or the gallery.
|
|
63
|
+
*/
|
|
64
|
+
export declare function CollaboratorPalette({ people, variant, colorMode, showValues, height, }: CollaboratorPaletteProps): import("react").JSX.Element;
|
|
65
|
+
export default CollaboratorPalette;
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/*
|
|
3
|
+
* Copyright (c) 2023-2026 Datalayer, Inc.
|
|
4
|
+
* Distributed under the terms of the Modified BSD License.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Who is who, in colour.
|
|
8
|
+
*
|
|
9
|
+
* A shared document gives every person a colour: their cursor, their
|
|
10
|
+
* selection, the ring on their avatar. Those colours were fixed hex values
|
|
11
|
+
* in each application that needed them — JupyterLab's seven
|
|
12
|
+
* `--jp-collaborator-color*`, and a pair typed into an editor example — so a
|
|
13
|
+
* page in the Sand theme drew its collaborators in the same seven colours as
|
|
14
|
+
* a page in Matrix.
|
|
15
|
+
*
|
|
16
|
+
* They come from the active theme instead. Each theme already carries a
|
|
17
|
+
* **bright palette**: seven vivid, well-separated colours meant to sit on
|
|
18
|
+
* that theme's surfaces, in a light and a dark reading. Those are the
|
|
19
|
+
* collaborator colours, ordered here so that neighbouring people get
|
|
20
|
+
* different hue families rather than two greens.
|
|
21
|
+
*
|
|
22
|
+
* Two rules the colours have to keep, and this module keeps them:
|
|
23
|
+
*
|
|
24
|
+
* - **A person holds their colour.** {@link collaboratorColor} picks by a
|
|
25
|
+
* stable hash of who they are, so a name always lands on the same slot for
|
|
26
|
+
* everyone in the room, with no server handing numbers out.
|
|
27
|
+
* - **Nobody is invisible.** The bright palettes are vivid in both readings,
|
|
28
|
+
* so a cursor stays visible on a light page and on a dark one.
|
|
29
|
+
*
|
|
30
|
+
* @module components/appearance/CollaboratorPalette
|
|
31
|
+
*/
|
|
32
|
+
import { useMemo } from "react";
|
|
33
|
+
import { Box, Text } from "@primer/react";
|
|
34
|
+
import { getBrightPalette, } from "../../theme/themeRegistry";
|
|
35
|
+
import { useThemeStore } from "../../theme/useThemeStore";
|
|
36
|
+
import { useSystemColorMode } from "../../theme/useSystemColorMode";
|
|
37
|
+
/**
|
|
38
|
+
* Which of the bright palette's colours a collaborator can be given, in the
|
|
39
|
+
* order they are handed out.
|
|
40
|
+
*
|
|
41
|
+
* Interleaved on purpose: green, red, blue, yellow, cyan, orange, lime, so
|
|
42
|
+
* the second person in a room is never a near-neighbour of the first.
|
|
43
|
+
* `onGlow` is not here — it is a text colour, not a hue.
|
|
44
|
+
*/
|
|
45
|
+
export const COLLABORATOR_SLOTS = [
|
|
46
|
+
"glow",
|
|
47
|
+
"blaze",
|
|
48
|
+
"surge",
|
|
49
|
+
"gold",
|
|
50
|
+
"pop",
|
|
51
|
+
"flame",
|
|
52
|
+
"spark",
|
|
53
|
+
];
|
|
54
|
+
/** How many colours a room can hand out before it repeats one. */
|
|
55
|
+
export const COLLABORATOR_COLOR_COUNT = COLLABORATOR_SLOTS.length;
|
|
56
|
+
/** The collaborator colours of one theme, in one colour mode. */
|
|
57
|
+
export function collaboratorColors(variant = "datalayer", colorMode = "dark") {
|
|
58
|
+
const bright = getBrightPalette(variant, colorMode);
|
|
59
|
+
return COLLABORATOR_SLOTS.map((slot) => bright[slot]);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* A number from a string, stable across machines and runs.
|
|
63
|
+
*
|
|
64
|
+
* djb2: small, and — unlike anything built on iteration order or `Math.random`
|
|
65
|
+
* — it gives every browser in the room the same answer for the same person,
|
|
66
|
+
* which is the whole point.
|
|
67
|
+
*/
|
|
68
|
+
export function collaboratorHash(seed) {
|
|
69
|
+
let hash = 5381;
|
|
70
|
+
for (let index = 0; index < seed.length; index += 1) {
|
|
71
|
+
hash = ((hash << 5) + hash + seed.charCodeAt(index)) | 0;
|
|
72
|
+
}
|
|
73
|
+
return Math.abs(hash);
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* The colour this person holds: the same one, everywhere, every session.
|
|
77
|
+
*
|
|
78
|
+
* `seed` should be whatever identifies them to the room — a user id where
|
|
79
|
+
* there is one, their handle otherwise.
|
|
80
|
+
*/
|
|
81
|
+
export function collaboratorColor(seed, variant = "datalayer", colorMode = "dark") {
|
|
82
|
+
const colors = collaboratorColors(variant, colorMode);
|
|
83
|
+
return colors[collaboratorHash(seed) % colors.length];
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* The palette as CSS custom properties.
|
|
87
|
+
*
|
|
88
|
+
* JupyterLab reads `--jp-collaborator-color1` through `7`; anything of ours
|
|
89
|
+
* can read `--dla-collaborator-color1` through `7`. Both names carry the same
|
|
90
|
+
* colours, so a JupyterLab surface and a Primer surface in the same page
|
|
91
|
+
* agree on who is who.
|
|
92
|
+
*/
|
|
93
|
+
export function collaboratorColorVars(variant = "datalayer", colorMode = "dark") {
|
|
94
|
+
const colors = collaboratorColors(variant, colorMode);
|
|
95
|
+
const vars = {};
|
|
96
|
+
colors.forEach((color, index) => {
|
|
97
|
+
vars[`--jp-collaborator-color${index + 1}`] = color;
|
|
98
|
+
vars[`--dla-collaborator-color${index + 1}`] = color;
|
|
99
|
+
});
|
|
100
|
+
return vars;
|
|
101
|
+
}
|
|
102
|
+
/** The collaborator colours of whichever theme is on, following the store. */
|
|
103
|
+
export function useCollaboratorColors() {
|
|
104
|
+
const { theme, colorMode } = useThemeStore();
|
|
105
|
+
const systemMode = useSystemColorMode();
|
|
106
|
+
const resolved = colorMode === "auto" ? systemMode : colorMode;
|
|
107
|
+
return useMemo(() => collaboratorColors(theme, resolved), [theme, resolved]);
|
|
108
|
+
}
|
|
109
|
+
/** One person's colour, in whichever theme is on. */
|
|
110
|
+
export function useCollaboratorColor(seed) {
|
|
111
|
+
const colors = useCollaboratorColors();
|
|
112
|
+
return colors[collaboratorHash(seed) % colors.length];
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* The palette, drawn.
|
|
116
|
+
*
|
|
117
|
+
* A row of swatches — one per collaborator colour, or one per named person —
|
|
118
|
+
* for a settings page, a room's roster, or the gallery.
|
|
119
|
+
*/
|
|
120
|
+
export function CollaboratorPalette({ people, variant, colorMode, showValues = true, height = 44, }) {
|
|
121
|
+
const store = useThemeStore();
|
|
122
|
+
const systemMode = useSystemColorMode();
|
|
123
|
+
const activeMode = colorMode ?? store.colorMode;
|
|
124
|
+
const resolved = activeMode === "auto" ? systemMode : activeMode;
|
|
125
|
+
const colors = collaboratorColors(variant ?? store.theme, resolved);
|
|
126
|
+
const entries = people?.length
|
|
127
|
+
? people.map((person) => ({
|
|
128
|
+
key: person,
|
|
129
|
+
label: person,
|
|
130
|
+
color: colors[collaboratorHash(person) % colors.length],
|
|
131
|
+
}))
|
|
132
|
+
: colors.map((color, index) => ({
|
|
133
|
+
key: `${index}`,
|
|
134
|
+
label: `Collaborator ${index + 1}`,
|
|
135
|
+
color,
|
|
136
|
+
}));
|
|
137
|
+
return (_jsx(Box, { role: "list", "aria-label": "Collaborator colours", sx: {
|
|
138
|
+
display: "grid",
|
|
139
|
+
gridTemplateColumns: "repeat(auto-fill, minmax(96px, 1fr))",
|
|
140
|
+
gap: 2,
|
|
141
|
+
}, children: entries.map((entry) => (_jsxs(Box, { role: "listitem", sx: {
|
|
142
|
+
display: "grid",
|
|
143
|
+
gap: 1,
|
|
144
|
+
border: "1px solid",
|
|
145
|
+
borderColor: "var(--borderColor-default)",
|
|
146
|
+
borderRadius: 2,
|
|
147
|
+
overflow: "hidden",
|
|
148
|
+
}, children: [_jsx(Box, { style: { backgroundColor: entry.color, height } }), _jsxs(Box, { sx: { px: 2, pb: 2, display: "grid", gap: 1 }, children: [_jsx(Text, { sx: {
|
|
149
|
+
fontSize: 0,
|
|
150
|
+
color: "var(--fgColor-default)",
|
|
151
|
+
whiteSpace: "nowrap",
|
|
152
|
+
overflow: "hidden",
|
|
153
|
+
textOverflow: "ellipsis",
|
|
154
|
+
}, title: entry.label, children: entry.label }), showValues ? (_jsx(Text, { sx: {
|
|
155
|
+
fontSize: 0,
|
|
156
|
+
fontFamily: "mono",
|
|
157
|
+
color: "var(--fgColor-muted)",
|
|
158
|
+
}, children: entry.color })) : null] })] }, entry.key))) }));
|
|
159
|
+
}
|
|
160
|
+
export default CollaboratorPalette;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export * from './AppearanceControls';
|
|
2
2
|
export * from './AppearanceControlsWithStore';
|
|
3
3
|
export * from './AppearanceMenu';
|
|
4
|
+
export * from './AppearanceMenuWithStore';
|
|
5
|
+
export * from './CollaboratorPalette';
|
|
4
6
|
export * from './ColorModeCircle';
|
|
5
7
|
export * from './ThemePreviewCard';
|
|
6
8
|
export * from './ThemeCircle';
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
export * from './AppearanceControls';
|
|
6
6
|
export * from './AppearanceControlsWithStore';
|
|
7
7
|
export * from './AppearanceMenu';
|
|
8
|
+
export * from './AppearanceMenuWithStore';
|
|
9
|
+
export * from './CollaboratorPalette';
|
|
8
10
|
export * from './ColorModeCircle';
|
|
9
11
|
export * from './ThemePreviewCard';
|
|
10
12
|
export * from './ThemeCircle';
|
|
@@ -1,7 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
/**
|
|
2
|
+
* `Box`: Primer's, re-exported.
|
|
3
|
+
*
|
|
4
|
+
* This used to be a `styled.div` of our own, built on this package's own
|
|
5
|
+
* styled-components. That copy is v5 while `@primer/react` runs on v6, so
|
|
6
|
+
* wherever a bundler did not dedupe the two — webpack in the landing, Rsbuild
|
|
7
|
+
* in the decks app — our Box rendered under a runtime with no Primer theme:
|
|
8
|
+
* `sx` was never resolved and landed on the DOM as `sx="[object Object]"`,
|
|
9
|
+
* and a card that asked for `canvas.subtle` came out white on a dark page.
|
|
10
|
+
* agent-runtimes' Vite config carried a shim aliasing this module to Primer's
|
|
11
|
+
* Box for exactly that reason; making the module *be* that is the fix for
|
|
12
|
+
* every consumer at once. The system props (`p`, `bg`, `display`, …) and `sx`
|
|
13
|
+
* are the same API on Primer's Box.
|
|
14
|
+
*/
|
|
15
|
+
import type { ComponentProps } from 'react';
|
|
16
|
+
import { Box } from '@primer/react';
|
|
17
|
+
export { Box };
|
|
6
18
|
export type BoxProps = ComponentProps<typeof Box>;
|
|
7
19
|
export default Box;
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* Copyright (c)
|
|
2
|
+
* Copyright (c) 2023-2026 Datalayer, Inc.
|
|
3
3
|
*
|
|
4
4
|
* Datalayer License
|
|
5
5
|
*/
|
|
6
|
-
import
|
|
7
|
-
|
|
8
|
-
import sx from './sx';
|
|
9
|
-
export const Box = styled.div(space, color, typography, layout, flexbox, grid, background, border, position, shadow, sx);
|
|
6
|
+
import { Box } from '@primer/react';
|
|
7
|
+
export { Box };
|
|
10
8
|
export default Box;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every cell of the grid the month is drawn on: the days before the first
|
|
3
|
+
* fall on the previous month, the days after the last on the next, so the
|
|
4
|
+
* weeks stay whole and nothing jumps when the month changes length.
|
|
5
|
+
*/
|
|
6
|
+
export declare function monthGrid(month: Date, weekStartsOn?: number): Date[];
|
|
7
|
+
export interface CalendarPickerProps {
|
|
8
|
+
/** The day that reads as chosen. */
|
|
9
|
+
value?: Date | null;
|
|
10
|
+
/** The month on screen when the calendar has no value of its own. */
|
|
11
|
+
defaultMonth?: Date;
|
|
12
|
+
/** Told which day was picked. */
|
|
13
|
+
onChange?: (date: Date) => void;
|
|
14
|
+
/** Days before this one cannot be picked. */
|
|
15
|
+
min?: Date;
|
|
16
|
+
/** Days after this one cannot be picked. */
|
|
17
|
+
max?: Date;
|
|
18
|
+
/** Answers whether one more day is out of bounds, over `min` and `max`. */
|
|
19
|
+
isDisabled?: (date: Date) => boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Which day a week opens on: `0` Sunday (the default), `1` Monday. The
|
|
22
|
+
* headings follow it, so a Monday week reads M T W T F S S.
|
|
23
|
+
*/
|
|
24
|
+
weekStartsOn?: 0 | 1;
|
|
25
|
+
/** The locale the month name and the weekday initials are written in. */
|
|
26
|
+
locale?: string;
|
|
27
|
+
/** A label for the grid, for anyone reading it through a screen reader. */
|
|
28
|
+
"aria-label"?: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The month grid. Uncontrolled in the month it shows — arrows move it — and
|
|
32
|
+
* controlled in the day it marks, which is the caller's to hold.
|
|
33
|
+
*/
|
|
34
|
+
export declare function CalendarPicker({ value, defaultMonth, onChange, min, max, isDisabled, weekStartsOn, locale, "aria-label": ariaLabel, }: CalendarPickerProps): import("react").JSX.Element;
|
|
35
|
+
export default CalendarPicker;
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/*
|
|
3
|
+
* Copyright (c) 2023-2026 Datalayer, Inc.
|
|
4
|
+
* Distributed under the terms of the Modified BSD License.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* A month of days, to pick one from.
|
|
8
|
+
*
|
|
9
|
+
* The grid on its own, with no input and no popover: a page that already has
|
|
10
|
+
* somewhere to show a month — a sidebar, a scheduling panel, a form — renders
|
|
11
|
+
* this directly, and {@link DatePicker} puts the same grid in an overlay
|
|
12
|
+
* behind a field.
|
|
13
|
+
*
|
|
14
|
+
* Every colour is a Primer custom property rather than a token read from the
|
|
15
|
+
* theme object, so the calendar follows whichever theme the page is wearing.
|
|
16
|
+
*
|
|
17
|
+
* @module components/calendar-picker/CalendarPicker
|
|
18
|
+
*/
|
|
19
|
+
import { useMemo, useState } from "react";
|
|
20
|
+
import { Box, IconButton, Text } from "@primer/react";
|
|
21
|
+
import { ChevronLeftIcon, ChevronRightIcon } from "@primer/octicons-react";
|
|
22
|
+
/** Sunday-first, as a month grid is conventionally drawn. */
|
|
23
|
+
const WEEKDAY_ORDER = [0, 1, 2, 3, 4, 5, 6];
|
|
24
|
+
/** Midnight, so two days compare by their date alone. */
|
|
25
|
+
function startOfDay(date) {
|
|
26
|
+
return new Date(date.getFullYear(), date.getMonth(), date.getDate());
|
|
27
|
+
}
|
|
28
|
+
function sameDay(a, b) {
|
|
29
|
+
return (!!a &&
|
|
30
|
+
!!b &&
|
|
31
|
+
a.getFullYear() === b.getFullYear() &&
|
|
32
|
+
a.getMonth() === b.getMonth() &&
|
|
33
|
+
a.getDate() === b.getDate());
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Every cell of the grid the month is drawn on: the days before the first
|
|
37
|
+
* fall on the previous month, the days after the last on the next, so the
|
|
38
|
+
* weeks stay whole and nothing jumps when the month changes length.
|
|
39
|
+
*/
|
|
40
|
+
export function monthGrid(month, weekStartsOn = 0) {
|
|
41
|
+
const first = new Date(month.getFullYear(), month.getMonth(), 1);
|
|
42
|
+
const lead = (first.getDay() - weekStartsOn + 7) % 7;
|
|
43
|
+
const start = new Date(first);
|
|
44
|
+
start.setDate(first.getDate() - lead);
|
|
45
|
+
return Array.from({ length: 42 }, (_, index) => {
|
|
46
|
+
const day = new Date(start);
|
|
47
|
+
day.setDate(start.getDate() + index);
|
|
48
|
+
return day;
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* The month grid. Uncontrolled in the month it shows — arrows move it — and
|
|
53
|
+
* controlled in the day it marks, which is the caller's to hold.
|
|
54
|
+
*/
|
|
55
|
+
export function CalendarPicker({ value, defaultMonth, onChange, min, max, isDisabled, weekStartsOn = 0, locale, "aria-label": ariaLabel, }) {
|
|
56
|
+
const [month, setMonth] = useState(() => {
|
|
57
|
+
const anchor = value ?? defaultMonth ?? new Date();
|
|
58
|
+
return new Date(anchor.getFullYear(), anchor.getMonth(), 1);
|
|
59
|
+
});
|
|
60
|
+
const today = useMemo(() => startOfDay(new Date()), []);
|
|
61
|
+
const days = useMemo(() => monthGrid(month, weekStartsOn), [month, weekStartsOn]);
|
|
62
|
+
const monthName = useMemo(() => new Intl.DateTimeFormat(locale, {
|
|
63
|
+
month: "long",
|
|
64
|
+
year: "numeric",
|
|
65
|
+
}).format(month), [locale, month]);
|
|
66
|
+
const weekdayNames = useMemo(() => {
|
|
67
|
+
const format = new Intl.DateTimeFormat(locale, { weekday: "short" });
|
|
68
|
+
// Any week will do for the names; 2024-01-07 was a Sunday.
|
|
69
|
+
return WEEKDAY_ORDER.map((offset) => {
|
|
70
|
+
const day = new Date(2024, 0, 7 + ((offset + weekStartsOn) % 7));
|
|
71
|
+
return format.format(day);
|
|
72
|
+
});
|
|
73
|
+
}, [locale, weekStartsOn]);
|
|
74
|
+
const dayLabel = useMemo(() => new Intl.DateTimeFormat(locale, {
|
|
75
|
+
weekday: "long",
|
|
76
|
+
day: "numeric",
|
|
77
|
+
month: "long",
|
|
78
|
+
year: "numeric",
|
|
79
|
+
}), [locale]);
|
|
80
|
+
const blocked = (day) => {
|
|
81
|
+
if (min && startOfDay(day) < startOfDay(min)) {
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
if (max && startOfDay(day) > startOfDay(max)) {
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
return isDisabled?.(day) ?? false;
|
|
88
|
+
};
|
|
89
|
+
const step = (by) => setMonth(new Date(month.getFullYear(), month.getMonth() + by, 1));
|
|
90
|
+
return (_jsxs(Box, { role: "group", "aria-label": ariaLabel ?? "Choose a date", sx: { display: "grid", gap: 2, minWidth: 252 }, children: [_jsxs(Box, { sx: {
|
|
91
|
+
display: "flex",
|
|
92
|
+
alignItems: "center",
|
|
93
|
+
justifyContent: "space-between",
|
|
94
|
+
gap: 2,
|
|
95
|
+
}, children: [_jsx(IconButton, { icon: ChevronLeftIcon, "aria-label": "Previous month", variant: "invisible", size: "small", onClick: () => step(-1) }), _jsx(Text, { "aria-live": "polite", sx: {
|
|
96
|
+
fontSize: 1,
|
|
97
|
+
fontWeight: "semibold",
|
|
98
|
+
color: "var(--fgColor-default)",
|
|
99
|
+
}, children: monthName }), _jsx(IconButton, { icon: ChevronRightIcon, "aria-label": "Next month", variant: "invisible", size: "small", onClick: () => step(1) })] }), _jsxs(Box, { sx: {
|
|
100
|
+
display: "grid",
|
|
101
|
+
gridTemplateColumns: "repeat(7, 1fr)",
|
|
102
|
+
gap: 1,
|
|
103
|
+
}, children: [weekdayNames.map((name) => (_jsx(Text, { "aria-hidden": "true", sx: {
|
|
104
|
+
fontSize: 0,
|
|
105
|
+
textAlign: "center",
|
|
106
|
+
color: "var(--fgColor-muted)",
|
|
107
|
+
}, children: name.slice(0, 2) }, name))), days.map((day) => {
|
|
108
|
+
const outside = day.getMonth() !== month.getMonth();
|
|
109
|
+
const selected = sameDay(day, value ?? null);
|
|
110
|
+
const isToday = sameDay(day, today);
|
|
111
|
+
const disabled = blocked(day);
|
|
112
|
+
return (_jsx(Box, { as: "button", type: "button", "aria-label": dayLabel.format(day), "aria-pressed": selected, "aria-current": isToday ? "date" : undefined, disabled: disabled, onClick: () => !disabled && onChange?.(startOfDay(day)), sx: {
|
|
113
|
+
appearance: "none",
|
|
114
|
+
border: "1px solid",
|
|
115
|
+
borderColor: selected
|
|
116
|
+
? "var(--borderColor-accent-emphasis)"
|
|
117
|
+
: isToday
|
|
118
|
+
? "var(--borderColor-default)"
|
|
119
|
+
: "transparent",
|
|
120
|
+
borderRadius: 2,
|
|
121
|
+
height: 30,
|
|
122
|
+
cursor: disabled ? "not-allowed" : "pointer",
|
|
123
|
+
fontSize: 1,
|
|
124
|
+
lineHeight: 1,
|
|
125
|
+
bg: selected
|
|
126
|
+
? "var(--bgColor-accent-emphasis)"
|
|
127
|
+
: "transparent",
|
|
128
|
+
color: selected
|
|
129
|
+
? "var(--fgColor-onEmphasis)"
|
|
130
|
+
: outside || disabled
|
|
131
|
+
? "var(--fgColor-muted)"
|
|
132
|
+
: "var(--fgColor-default)",
|
|
133
|
+
opacity: disabled ? 0.5 : outside ? 0.6 : 1,
|
|
134
|
+
"&:hover:not(:disabled)": {
|
|
135
|
+
bg: selected
|
|
136
|
+
? "var(--bgColor-accent-emphasis)"
|
|
137
|
+
: "var(--bgColor-muted)",
|
|
138
|
+
},
|
|
139
|
+
"&:focus-visible": {
|
|
140
|
+
outline: "2px solid var(--borderColor-accent-emphasis)",
|
|
141
|
+
outlineOffset: "-1px",
|
|
142
|
+
},
|
|
143
|
+
}, children: day.getDate() }, day.toISOString()));
|
|
144
|
+
})] })] }));
|
|
145
|
+
}
|
|
146
|
+
export default CalendarPicker;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./CalendarPicker";
|
|
@@ -6,7 +6,12 @@ export type CardProps = Omit<BoxProps, 'border'> & {
|
|
|
6
6
|
shadow?: 'small' | 'medium' | 'large' | 'extraLarge';
|
|
7
7
|
};
|
|
8
8
|
export type CardHeaderProps = {
|
|
9
|
-
|
|
9
|
+
/**
|
|
10
|
+
* The card's title. A node rather than a string, so a header can carry an
|
|
11
|
+
* avatar, a label or a link beside its text — a card title is a place
|
|
12
|
+
* callers reasonably want to compose.
|
|
13
|
+
*/
|
|
14
|
+
title?: React.ReactNode;
|
|
10
15
|
description?: string;
|
|
11
16
|
leadingVisual?: React.ElementType;
|
|
12
17
|
action?: React.ReactNode;
|
|
@@ -35,7 +35,7 @@ export const Card = (props) => {
|
|
|
35
35
|
};
|
|
36
36
|
Card.Header = (props) => {
|
|
37
37
|
const { title, description, leadingVisual, action } = props;
|
|
38
|
-
return _jsxs(Box, { display: "flex", alignItems: "center", sx: { p: 3 }, children: [leadingVisual && _jsx(Box, { sx: { mr: 3 }, children: _jsx(IconButton, { size: "medium", icon: leadingVisual, "aria-label": "" }) }), _jsxs(Box, { sx: { flexGrow: 1 }, children: [_jsx(Text, { display: "block", children: title }), _jsx(Text, { display: "block", color: "fg.muted", children: description })] }), action && _jsx(Box, { children: action })] });
|
|
38
|
+
return _jsxs(Box, { display: "flex", alignItems: "center", sx: { p: 3 }, children: [leadingVisual && _jsx(Box, { sx: { mr: 3 }, children: _jsx(IconButton, { size: "medium", icon: leadingVisual, "aria-label": "" }) }), _jsxs(Box, { sx: { flexGrow: 1 }, children: [_jsx(Text, { as: "div", display: "block", children: title }), _jsx(Text, { display: "block", color: "fg.muted", children: description })] }), action && _jsx(Box, { children: action })] });
|
|
39
39
|
};
|
|
40
40
|
Card.Image = ({ url, image, svg, height }) => {
|
|
41
41
|
return (_jsxs(Box, { display: "block", width: "100%", height: `${height}px`, backgroundImage: `url(${url})`, sx: {
|
|
@@ -19,5 +19,5 @@ export interface ColorSwatchProps {
|
|
|
19
19
|
* The resolved value recomputes whenever the active theme or color mode
|
|
20
20
|
* changes, so swatches backed by CSS custom properties stay in sync.
|
|
21
21
|
*/
|
|
22
|
-
export declare function ColorSwatch({ color, label, showValue, height, }: ColorSwatchProps): import("react
|
|
22
|
+
export declare function ColorSwatch({ color, label, showValue, height, }: ColorSwatchProps): import("react").JSX.Element;
|
|
23
23
|
export default ColorSwatch;
|