@datalayer/primer-addons 1.0.22 → 1.0.23
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/lib/components/box/Box.js +1 -2
- package/lib/components/box/sx.js +1 -2
- package/lib/components/closeable-flash/CloseableFlash.d.ts +23 -1
- package/lib/components/closeable-flash/CloseableFlash.js +52 -4
- package/lib/components/content-loader/ContentLoader.js +4 -0
- package/lib/components/date-picker/DatePicker.d.ts +36 -1
- package/lib/components/date-picker/DatePicker.js +122 -16
- package/lib/components/date-picker/TimeColumns.d.ts +23 -0
- package/lib/components/date-picker/TimeColumns.js +88 -0
- package/lib/components/icons/CircleIcon.js +4 -0
- package/lib/components/index.js +4 -0
- package/lib/components/logo/index.js +4 -0
- package/lib/components/side-overlay/SideOverlay.js +4 -0
- package/lib/components/side-overlay/SideOverlay.stories.js +4 -0
- package/lib/components/slider/Slider.js +4 -0
- package/lib/components/slider/Slider.stories.js +4 -0
- package/lib/components/sliding-panel/SlidingPanel.js +17 -1
- package/lib/components/sliding-panel/SlidingPanel.stories.js +4 -0
- package/lib/index.js +4 -0
- package/lib/reactor/page-layout/PageLayout.js +68 -52
- package/lib/story-helpers.d.ts +1 -1
- package/lib/theme/palettes/ColorPalette.js +1 -2
- package/lib/theme/palettes/index.js +1 -2
- package/lib/utils/Portals.js +3 -1
- package/package.json +9 -3
package/lib/components/box/sx.js
CHANGED
|
@@ -1,7 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `CloseableFlash`: a Primer `Flash` the reader can dismiss.
|
|
3
|
+
*
|
|
4
|
+
* Primer's own variants are `default`, `success`, `warning` and `danger`. Two
|
|
5
|
+
* more are added here, for the levels a platform announce is written at:
|
|
6
|
+
* `info`, which is the neutral blue Primer already draws for `default` and is
|
|
7
|
+
* worth having under the name people say, and `critical`, which is `danger`
|
|
8
|
+
* wearing its emphasis colour — an incident has to look different from a
|
|
9
|
+
* validation error, and a reader who sees everything red stops reading red.
|
|
10
|
+
*
|
|
11
|
+
* @module components/closeable-flash/CloseableFlash
|
|
12
|
+
*/
|
|
1
13
|
import { FC } from "react";
|
|
2
14
|
import { FlashProps } from "@primer/react";
|
|
3
|
-
|
|
15
|
+
/** What a flash may be, Primer's own plus the announce levels. */
|
|
16
|
+
export type CloseableFlashVariant = NonNullable<FlashProps['variant']> | 'info' | 'critical';
|
|
17
|
+
export type CloseableFlashProps = Omit<FlashProps, 'variant'> & {
|
|
18
|
+
variant?: CloseableFlashVariant;
|
|
4
19
|
leadingVisual?: React.ComponentType;
|
|
5
20
|
onClose?: () => void;
|
|
21
|
+
/**
|
|
22
|
+
* Whether the reader may close it. `false` draws the same flash without the
|
|
23
|
+
* close button, which is what a preview of one wants: an editor showing an
|
|
24
|
+
* announce as it will look should not offer to dismiss something that is
|
|
25
|
+
* not being shown to anybody yet.
|
|
26
|
+
*/
|
|
27
|
+
dismissible?: boolean;
|
|
6
28
|
};
|
|
7
29
|
export declare const CloseableFlash: FC<CloseableFlashProps>;
|
|
@@ -1,7 +1,40 @@
|
|
|
1
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
|
+
* `CloseableFlash`: a Primer `Flash` the reader can dismiss.
|
|
8
|
+
*
|
|
9
|
+
* Primer's own variants are `default`, `success`, `warning` and `danger`. Two
|
|
10
|
+
* more are added here, for the levels a platform announce is written at:
|
|
11
|
+
* `info`, which is the neutral blue Primer already draws for `default` and is
|
|
12
|
+
* worth having under the name people say, and `critical`, which is `danger`
|
|
13
|
+
* wearing its emphasis colour — an incident has to look different from a
|
|
14
|
+
* validation error, and a reader who sees everything red stops reading red.
|
|
15
|
+
*
|
|
16
|
+
* @module components/closeable-flash/CloseableFlash
|
|
17
|
+
*/
|
|
2
18
|
import { useState } from "react";
|
|
3
19
|
import { Box, Flash, CircleOcticon, Text } from "@primer/react";
|
|
4
20
|
import { XIcon } from "@primer/octicons-react";
|
|
21
|
+
/**
|
|
22
|
+
* The Primer variant that draws a given level.
|
|
23
|
+
*
|
|
24
|
+
* `info` is Primer's `default` — already the accent blue — and `critical` is
|
|
25
|
+
* `danger`; what sets the two dangers apart is the emphasis below, not the
|
|
26
|
+
* variant, because Primer has no louder red to ask for.
|
|
27
|
+
*/
|
|
28
|
+
const flashVariantForVariant = (variant) => {
|
|
29
|
+
switch (variant) {
|
|
30
|
+
case 'info':
|
|
31
|
+
return 'default';
|
|
32
|
+
case 'critical':
|
|
33
|
+
return 'danger';
|
|
34
|
+
default:
|
|
35
|
+
return variant;
|
|
36
|
+
}
|
|
37
|
+
};
|
|
5
38
|
const closeButtonToneForVariant = (variant) => {
|
|
6
39
|
switch (variant) {
|
|
7
40
|
case 'success':
|
|
@@ -9,7 +42,10 @@ const closeButtonToneForVariant = (variant) => {
|
|
|
9
42
|
case 'warning':
|
|
10
43
|
return { fg: 'attention.fg', hoverBg: 'rgba(154, 103, 0, 0.16)' };
|
|
11
44
|
case 'danger':
|
|
45
|
+
case 'critical':
|
|
12
46
|
return { fg: 'danger.fg', hoverBg: 'rgba(207, 34, 46, 0.14)' };
|
|
47
|
+
case 'info':
|
|
48
|
+
return { fg: 'accent.fg', hoverBg: 'rgba(9, 105, 218, 0.12)' };
|
|
13
49
|
default:
|
|
14
50
|
return { fg: 'fg.default', hoverBg: 'rgba(31, 35, 40, 0.12)' };
|
|
15
51
|
}
|
|
@@ -21,16 +57,21 @@ const textColorForVariant = (variant) => {
|
|
|
21
57
|
case 'warning':
|
|
22
58
|
return 'attention.fg';
|
|
23
59
|
case 'danger':
|
|
60
|
+
case 'critical':
|
|
24
61
|
return 'danger.fg';
|
|
62
|
+
case 'info':
|
|
63
|
+
return 'accent.fg';
|
|
25
64
|
default:
|
|
26
65
|
return 'fg.default';
|
|
27
66
|
}
|
|
28
67
|
};
|
|
29
68
|
export const CloseableFlash = (props) => {
|
|
30
|
-
const { leadingVisual, variant, onClose, ...otherProps } = props;
|
|
69
|
+
const { leadingVisual, variant, onClose, sx, dismissible = true, ...otherProps } = props;
|
|
31
70
|
const resolvedVariant = variant ?? 'default';
|
|
32
71
|
const closeTone = closeButtonToneForVariant(resolvedVariant);
|
|
33
|
-
const
|
|
72
|
+
const primerVariant = flashVariantForVariant(resolvedVariant);
|
|
73
|
+
const flashVariant = primerVariant === 'default' ? undefined : primerVariant;
|
|
74
|
+
const isCritical = resolvedVariant === 'critical';
|
|
34
75
|
const [isVisible, setIsVisible] = useState(true);
|
|
35
76
|
const handleClose = () => {
|
|
36
77
|
if (onClose) {
|
|
@@ -41,7 +82,14 @@ export const CloseableFlash = (props) => {
|
|
|
41
82
|
if (!isVisible) {
|
|
42
83
|
return null;
|
|
43
84
|
}
|
|
44
|
-
return (_jsx(Flash, { variant: flashVariant,
|
|
85
|
+
return (_jsx(Flash, { variant: flashVariant, sx: {
|
|
86
|
+
// A critical flash is the one a reader must not scroll past: the
|
|
87
|
+
// border carries the emphasis colour rather than the muted one.
|
|
88
|
+
...(isCritical
|
|
89
|
+
? { borderColor: 'danger.emphasis', borderWidth: 2, borderStyle: 'solid' }
|
|
90
|
+
: {}),
|
|
91
|
+
...sx,
|
|
92
|
+
}, ...otherProps, children: _jsxs(Box, { display: "flex", justifyContent: "space-between", alignItems: "center", children: [_jsxs(Box, { sx: { flexGrow: 1 }, children: [leadingVisual && _jsx(CircleOcticon, { icon: leadingVisual }), _jsx(Text, { color: textColorForVariant(resolvedVariant), sx: isCritical ? { fontWeight: 'bold' } : undefined, children: props.children })] }), dismissible && (_jsx(Box, { as: "button", type: "button", "aria-label": "Close", onClick: handleClose, sx: {
|
|
45
93
|
flex: '0 0 auto',
|
|
46
94
|
p: 0,
|
|
47
95
|
m: 0,
|
|
@@ -71,5 +119,5 @@ export const CloseableFlash = (props) => {
|
|
|
71
119
|
'& svg': {
|
|
72
120
|
display: 'block',
|
|
73
121
|
},
|
|
74
|
-
}, children: _jsx(Box, { as: "span", "aria-hidden": true, children: _jsx(XIcon, { size: 16 }) }) })] }) }));
|
|
122
|
+
}, children: _jsx(Box, { as: "span", "aria-hidden": true, children: _jsx(XIcon, { size: 16 }) }) }))] }) }));
|
|
75
123
|
};
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/*
|
|
3
|
+
* Copyright (c) 2021-2026 Datalayer, Inc.
|
|
4
|
+
* Distributed under the terms of the Modified BSD License.
|
|
5
|
+
*/
|
|
2
6
|
import { Box, useTheme } from "@primer/react";
|
|
3
7
|
import Skeleton, { SkeletonTheme } from "react-loading-skeleton";
|
|
4
8
|
import 'react-loading-skeleton/dist/skeleton.css';
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { type CalendarPickerProps } from "../calendar-picker/CalendarPicker";
|
|
2
2
|
/** `YYYY-MM-DD`, the one format that means the same thing everywhere. */
|
|
3
3
|
export declare function formatISODate(date: Date): string;
|
|
4
|
+
/** `YYYY-MM-DD HH:mm`, the same date with the minute it holds. */
|
|
5
|
+
export declare function formatISODateTime(date: Date): string;
|
|
6
|
+
/** `HH:mm` of a date, which is what a `type="time"` field holds. */
|
|
7
|
+
export declare function formatTime(date: Date): string;
|
|
4
8
|
/**
|
|
5
9
|
* Reads `YYYY-MM-DD` as a local date.
|
|
6
10
|
*
|
|
@@ -9,6 +13,16 @@ export declare function formatISODate(date: Date): string;
|
|
|
9
13
|
* means the same day in every zone.
|
|
10
14
|
*/
|
|
11
15
|
export declare function parseISODate(text: string): Date | null;
|
|
16
|
+
/**
|
|
17
|
+
* Reads `YYYY-MM-DD HH:mm` — or the same with a `T`, or with seconds, or with
|
|
18
|
+
* no time at all — as a local date.
|
|
19
|
+
*
|
|
20
|
+
* Lenient on the way in and strict on the way out: somebody typing a date into
|
|
21
|
+
* a field has a shape in their fingers, and rejecting `2026-01-31T09:00`
|
|
22
|
+
* because the separator is a `T` teaches them nothing. What the field *writes*
|
|
23
|
+
* is always one shape.
|
|
24
|
+
*/
|
|
25
|
+
export declare function parseISODateTime(text: string): Date | null;
|
|
12
26
|
export interface DatePickerProps extends Pick<CalendarPickerProps, "min" | "max" | "isDisabled" | "weekStartsOn" | "locale"> {
|
|
13
27
|
/** The chosen date, or nothing chosen. */
|
|
14
28
|
value?: Date | null;
|
|
@@ -35,6 +49,27 @@ export interface DatePickerProps extends Pick<CalendarPickerProps, "min" | "max"
|
|
|
35
49
|
format?: (date: Date) => string;
|
|
36
50
|
/** An id for the field, for a label that names it from outside. */
|
|
37
51
|
id?: string;
|
|
52
|
+
/**
|
|
53
|
+
* Whether the field holds a time as well as a day.
|
|
54
|
+
*
|
|
55
|
+
* With it, the field reads and writes `YYYY-MM-DD HH:mm`, and the overlay
|
|
56
|
+
* carries a time beneath the calendar. Without it — the default — nothing
|
|
57
|
+
* about the field changes, because most dates are days.
|
|
58
|
+
*/
|
|
59
|
+
withTime?: boolean;
|
|
60
|
+
/**
|
|
61
|
+
* Minutes between the options the minute column offers. Default: five.
|
|
62
|
+
*
|
|
63
|
+
* A minute the step does not land on — one read back from a stored value —
|
|
64
|
+
* is still offered, so choosing an hour never quietly moves it.
|
|
65
|
+
*/
|
|
66
|
+
timeStep?: number;
|
|
67
|
+
/**
|
|
68
|
+
* The time a day picked from the calendar takes when the field held nothing
|
|
69
|
+
* yet. `"00:00"` by default — a window that starts "on the 3rd" starts at
|
|
70
|
+
* the beginning of the 3rd.
|
|
71
|
+
*/
|
|
72
|
+
defaultTime?: string;
|
|
38
73
|
}
|
|
39
74
|
/**
|
|
40
75
|
* A date field with a calendar behind it.
|
|
@@ -42,5 +77,5 @@ export interface DatePickerProps extends Pick<CalendarPickerProps, "min" | "max"
|
|
|
42
77
|
* Controlled: `value` is the caller's, and every pick and every parsed entry
|
|
43
78
|
* arrives through `onChange`.
|
|
44
79
|
*/
|
|
45
|
-
export declare function DatePicker({ value, onChange, placeholder, "aria-label": ariaLabel, disabled, size, validationStatus, format, id, min, max, isDisabled, weekStartsOn, locale, }: DatePickerProps): import("react").JSX.Element;
|
|
80
|
+
export declare function DatePicker({ value, onChange, placeholder, "aria-label": ariaLabel, disabled, size, validationStatus, format, id, min, max, isDisabled, weekStartsOn, locale, withTime, timeStep, defaultTime, }: DatePickerProps): import("react").JSX.Element;
|
|
46
81
|
export default DatePicker;
|
|
@@ -1,14 +1,27 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useMemo, useRef, useState } from "react";
|
|
3
|
-
import { AnchoredOverlay, Box, TextInput } from "@primer/react";
|
|
3
|
+
import { AnchoredOverlay, Box, Button, TextInput } from "@primer/react";
|
|
4
4
|
import { CalendarIcon } from "@primer/octicons-react";
|
|
5
5
|
import { CalendarPicker } from "../calendar-picker/CalendarPicker";
|
|
6
|
+
import { TimeColumns } from "./TimeColumns";
|
|
6
7
|
/** `YYYY-MM-DD`, the one format that means the same thing everywhere. */
|
|
7
8
|
export function formatISODate(date) {
|
|
8
9
|
const month = `${date.getMonth() + 1}`.padStart(2, "0");
|
|
9
10
|
const day = `${date.getDate()}`.padStart(2, "0");
|
|
10
11
|
return `${date.getFullYear()}-${month}-${day}`;
|
|
11
12
|
}
|
|
13
|
+
/** `YYYY-MM-DD HH:mm`, the same date with the minute it holds. */
|
|
14
|
+
export function formatISODateTime(date) {
|
|
15
|
+
const hours = `${date.getHours()}`.padStart(2, "0");
|
|
16
|
+
const minutes = `${date.getMinutes()}`.padStart(2, "0");
|
|
17
|
+
return `${formatISODate(date)} ${hours}:${minutes}`;
|
|
18
|
+
}
|
|
19
|
+
/** `HH:mm` of a date, which is what a `type="time"` field holds. */
|
|
20
|
+
export function formatTime(date) {
|
|
21
|
+
const hours = `${date.getHours()}`.padStart(2, "0");
|
|
22
|
+
const minutes = `${date.getMinutes()}`.padStart(2, "0");
|
|
23
|
+
return `${hours}:${minutes}`;
|
|
24
|
+
}
|
|
12
25
|
/**
|
|
13
26
|
* Reads `YYYY-MM-DD` as a local date.
|
|
14
27
|
*
|
|
@@ -23,7 +36,44 @@ export function parseISODate(text) {
|
|
|
23
36
|
}
|
|
24
37
|
const [, year, month, day] = match;
|
|
25
38
|
const date = new Date(Number(year), Number(month) - 1, Number(day));
|
|
26
|
-
return
|
|
39
|
+
return isExactly(date, Number(year), Number(month), Number(day)) ? date : null;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Whether `date` holds exactly the fields it was built from.
|
|
43
|
+
*
|
|
44
|
+
* The `Date` constructor rolls an out-of-range field into the next one —
|
|
45
|
+
* `2026-02-31` becomes the 3rd of March, `24:00` the next day — instead of
|
|
46
|
+
* failing, so a typo would be committed as some other moment. A field that
|
|
47
|
+
* does not come back as written was not a real one.
|
|
48
|
+
*/
|
|
49
|
+
function isExactly(date, year, month, day, hours = 0, minutes = 0, seconds = 0) {
|
|
50
|
+
return (!Number.isNaN(date.getTime()) &&
|
|
51
|
+
date.getFullYear() === year &&
|
|
52
|
+
date.getMonth() === month - 1 &&
|
|
53
|
+
date.getDate() === day &&
|
|
54
|
+
date.getHours() === hours &&
|
|
55
|
+
date.getMinutes() === minutes &&
|
|
56
|
+
date.getSeconds() === seconds);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Reads `YYYY-MM-DD HH:mm` — or the same with a `T`, or with seconds, or with
|
|
60
|
+
* no time at all — as a local date.
|
|
61
|
+
*
|
|
62
|
+
* Lenient on the way in and strict on the way out: somebody typing a date into
|
|
63
|
+
* a field has a shape in their fingers, and rejecting `2026-01-31T09:00`
|
|
64
|
+
* because the separator is a `T` teaches them nothing. What the field *writes*
|
|
65
|
+
* is always one shape.
|
|
66
|
+
*/
|
|
67
|
+
export function parseISODateTime(text) {
|
|
68
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})(?:[T ](\d{1,2}):(\d{2})(?::(\d{2}))?)?$/.exec(text.trim());
|
|
69
|
+
if (!match) {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
const [, year, month, day, hours, minutes, seconds] = match;
|
|
73
|
+
const date = new Date(Number(year), Number(month) - 1, Number(day), Number(hours ?? 0), Number(minutes ?? 0), Number(seconds ?? 0));
|
|
74
|
+
return isExactly(date, Number(year), Number(month), Number(day), Number(hours ?? 0), Number(minutes ?? 0), Number(seconds ?? 0))
|
|
75
|
+
? date
|
|
76
|
+
: null;
|
|
27
77
|
}
|
|
28
78
|
/**
|
|
29
79
|
* A date field with a calendar behind it.
|
|
@@ -31,18 +81,23 @@ export function parseISODate(text) {
|
|
|
31
81
|
* Controlled: `value` is the caller's, and every pick and every parsed entry
|
|
32
82
|
* arrives through `onChange`.
|
|
33
83
|
*/
|
|
34
|
-
export function DatePicker({ value = null, onChange, placeholder
|
|
84
|
+
export function DatePicker({ value = null, onChange, placeholder, "aria-label": ariaLabel = "Date", disabled = false, size = "medium", validationStatus, format, id, min, max, isDisabled, weekStartsOn, locale, withTime = false, timeStep = 5, defaultTime = "00:00", }) {
|
|
85
|
+
// The two shapes, chosen once: a caller that passed a `format` keeps it,
|
|
86
|
+
// and a caller that asked for a time gets the one that carries a time.
|
|
87
|
+
const writeDate = format ?? (withTime ? formatISODateTime : formatISODate);
|
|
88
|
+
const readDate = withTime ? parseISODateTime : parseISODate;
|
|
89
|
+
const emptyText = placeholder ?? (withTime ? "YYYY-MM-DD HH:mm" : "YYYY-MM-DD");
|
|
35
90
|
const [open, setOpen] = useState(false);
|
|
36
|
-
const [text, setText] = useState(() => (value ?
|
|
91
|
+
const [text, setText] = useState(() => (value ? writeDate(value) : ""));
|
|
37
92
|
const anchorRef = useRef(null);
|
|
38
93
|
// The field follows the value, except while it is being typed into: a
|
|
39
94
|
// controlled rewrite mid-word moves the caret to the end.
|
|
40
95
|
const typing = useRef(false);
|
|
41
96
|
useEffect(() => {
|
|
42
97
|
if (!typing.current) {
|
|
43
|
-
setText(value ?
|
|
98
|
+
setText(value ? writeDate(value) : "");
|
|
44
99
|
}
|
|
45
|
-
}, [value,
|
|
100
|
+
}, [value, writeDate]);
|
|
46
101
|
const commit = (next) => {
|
|
47
102
|
typing.current = false;
|
|
48
103
|
const trimmed = next.trim();
|
|
@@ -50,38 +105,89 @@ export function DatePicker({ value = null, onChange, placeholder = "YYYY-MM-DD",
|
|
|
50
105
|
onChange?.(null);
|
|
51
106
|
return;
|
|
52
107
|
}
|
|
53
|
-
const parsed =
|
|
108
|
+
const parsed = readDate(trimmed);
|
|
54
109
|
if (parsed) {
|
|
55
110
|
onChange?.(parsed);
|
|
56
|
-
setText(
|
|
111
|
+
setText(writeDate(parsed));
|
|
57
112
|
}
|
|
58
113
|
else {
|
|
59
114
|
// Unreadable: put back what is actually held, rather than keeping a
|
|
60
115
|
// date on screen that the caller never received.
|
|
61
|
-
setText(value ?
|
|
116
|
+
setText(value ? writeDate(value) : "");
|
|
62
117
|
}
|
|
63
118
|
};
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
119
|
+
/** The day picked, wearing the time the field already held. */
|
|
120
|
+
const withHeldTime = (day) => {
|
|
121
|
+
const [hours, minutes] = (value ? formatTime(value) : defaultTime)
|
|
122
|
+
.split(":")
|
|
123
|
+
.map(Number);
|
|
124
|
+
const next = new Date(day);
|
|
125
|
+
next.setHours(hours || 0, minutes || 0, 0, 0);
|
|
126
|
+
return next;
|
|
127
|
+
};
|
|
128
|
+
/** The time the field holds right now, or the one a new pick starts from. */
|
|
129
|
+
const held = (() => {
|
|
130
|
+
const [hours, minutes] = (value ? formatTime(value) : defaultTime)
|
|
131
|
+
.split(":")
|
|
132
|
+
.map(Number);
|
|
133
|
+
return { hour: hours || 0, minute: minutes || 0 };
|
|
134
|
+
})();
|
|
135
|
+
/** The day the field holds, at the time just chosen. */
|
|
136
|
+
const atTime = (hour, minute) => {
|
|
137
|
+
const next = new Date(value ?? new Date());
|
|
138
|
+
next.setHours(hour, minute, 0, 0);
|
|
139
|
+
return next;
|
|
140
|
+
};
|
|
141
|
+
const calendar = useMemo(() => (_jsxs(Box, { sx: { p: 3 }, children: [_jsx(CalendarPicker, { value: value, defaultMonth: value ?? undefined, onChange: (date) => {
|
|
142
|
+
// A day picked while a time is held keeps that time: somebody
|
|
143
|
+
// moving a window from the 3rd to the 4th did not mean to move
|
|
144
|
+
// it to midnight.
|
|
145
|
+
const next = withTime ? withHeldTime(date) : date;
|
|
146
|
+
onChange?.(next);
|
|
147
|
+
setText(writeDate(next));
|
|
148
|
+
if (!withTime) {
|
|
149
|
+
setOpen(false);
|
|
150
|
+
}
|
|
151
|
+
}, min: min, max: max, isDisabled: isDisabled, weekStartsOn: weekStartsOn, locale: locale, "aria-label": `${ariaLabel}, choose a day` }), withTime && (_jsxs(Box, { sx: {
|
|
152
|
+
mt: 3,
|
|
153
|
+
pt: 3,
|
|
154
|
+
borderTop: "1px solid",
|
|
155
|
+
borderColor: "border.default",
|
|
156
|
+
}, children: [_jsx(TimeColumns, { hour: held.hour, minute: held.minute, step: timeStep, disabled: disabled, labelPrefix: ariaLabel, onChange: (hour, minute) => {
|
|
157
|
+
const next = atTime(hour, minute);
|
|
158
|
+
onChange?.(next);
|
|
159
|
+
setText(writeDate(next));
|
|
160
|
+
} }), _jsx(Box, { sx: {
|
|
161
|
+
display: "flex",
|
|
162
|
+
alignItems: "center",
|
|
163
|
+
justifyContent: "flex-end",
|
|
164
|
+
gap: 2,
|
|
165
|
+
mt: 2,
|
|
166
|
+
}, children: _jsx(Button, { size: "small", variant: "primary", onClick: () => setOpen(false), children: "Done" }) })] }))] })),
|
|
167
|
+
// `withHeldTime`, `held` and `atTime` all close over `value` and
|
|
168
|
+
// `defaultTime`, both of which are named here.
|
|
169
|
+
[
|
|
69
170
|
ariaLabel,
|
|
70
|
-
|
|
171
|
+
defaultTime,
|
|
172
|
+
disabled,
|
|
71
173
|
isDisabled,
|
|
72
174
|
locale,
|
|
73
175
|
max,
|
|
74
176
|
min,
|
|
75
177
|
onChange,
|
|
178
|
+
size,
|
|
179
|
+
timeStep,
|
|
76
180
|
value,
|
|
77
181
|
weekStartsOn,
|
|
182
|
+
withTime,
|
|
183
|
+
writeDate,
|
|
78
184
|
]);
|
|
79
185
|
return (_jsxs(Box, { ref: anchorRef, sx: { display: "inline-block" }, children: [_jsx(AnchoredOverlay, { open: open, onOpen: () => !disabled && setOpen(true), onClose: () => setOpen(false),
|
|
80
186
|
// The field's own box is the anchor; `useRef` types its current as
|
|
81
187
|
// nullable, which is not how the overlay declares what it takes.
|
|
82
188
|
anchorRef: anchorRef,
|
|
83
189
|
// The field is the anchor; the button inside it does the opening.
|
|
84
|
-
renderAnchor: null, overlayProps: { role: "dialog", "aria-label": `${ariaLabel} calendar` }, children: calendar }), _jsx(TextInput, { id: id, "aria-label": ariaLabel, "aria-haspopup": "dialog", "aria-expanded": open, value: text, placeholder:
|
|
190
|
+
renderAnchor: null, overlayProps: { role: "dialog", "aria-label": `${ariaLabel} calendar` }, children: calendar }), _jsx(TextInput, { id: id, "aria-label": ariaLabel, "aria-haspopup": "dialog", "aria-expanded": open, value: text, placeholder: emptyText, disabled: disabled, size: size, validationStatus: validationStatus, onChange: (event) => {
|
|
85
191
|
typing.current = true;
|
|
86
192
|
setText(event.target.value);
|
|
87
193
|
}, onBlur: (event) => commit(event.target.value), onKeyDown: (event) => {
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** `7` → `07`. Two digits, always, because a column of ragged numbers reads badly. */
|
|
2
|
+
export declare const pad: (value: number) => string;
|
|
3
|
+
export interface TimeColumnsProps {
|
|
4
|
+
/** The hour shown as chosen, 0–23. */
|
|
5
|
+
hour: number;
|
|
6
|
+
/** The minute shown as chosen, 0–59. */
|
|
7
|
+
minute: number;
|
|
8
|
+
/** Minutes between the options offered. 1 lists all sixty. */
|
|
9
|
+
step?: number;
|
|
10
|
+
/** Told when either column is used, with the whole time. */
|
|
11
|
+
onChange: (hour: number, minute: number) => void;
|
|
12
|
+
disabled?: boolean;
|
|
13
|
+
/** Names the columns for a screen reader: "Start date, hour". Not drawn —
|
|
14
|
+
* on screen the two columns are headed "Hour" and "Minute", and a whole
|
|
15
|
+
* field label repeated over them reads as noise. */
|
|
16
|
+
labelPrefix?: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* The two columns, side by side, with the time they currently mean between
|
|
20
|
+
* them.
|
|
21
|
+
*/
|
|
22
|
+
export declare function TimeColumns({ hour, minute, step, onChange, disabled, labelPrefix, }: TimeColumnsProps): import("react").JSX.Element;
|
|
23
|
+
export default TimeColumns;
|
|
@@ -0,0 +1,88 @@
|
|
|
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
|
+
* Hours and minutes, as two columns the reader scrolls.
|
|
8
|
+
*
|
|
9
|
+
* The browser's own `type="time"` control draws its dropdown itself: unstyled,
|
|
10
|
+
* unaware of the page's theme, and — because it is the platform's own popup —
|
|
11
|
+
* *over* whatever is already open. Beside a calendar that means the time
|
|
12
|
+
* dropdown covers the days, which is the one thing somebody choosing a moment
|
|
13
|
+
* needs to keep seeing.
|
|
14
|
+
*
|
|
15
|
+
* So the columns are drawn here, in Primer, inside the overlay that already
|
|
16
|
+
* exists. Not a second overlay: a menu anchored inside an `AnchoredOverlay`
|
|
17
|
+
* portals out of it, and the outer overlay's own click-away closes everything
|
|
18
|
+
* the moment the pointer lands in the inner one.
|
|
19
|
+
*
|
|
20
|
+
* @module components/date-picker/TimeColumns
|
|
21
|
+
*/
|
|
22
|
+
import { useEffect, useRef } from "react";
|
|
23
|
+
import { ActionList, Box, Text } from "@primer/react";
|
|
24
|
+
/** `7` → `07`. Two digits, always, because a column of ragged numbers reads badly. */
|
|
25
|
+
export const pad = (value) => `${value}`.padStart(2, "0");
|
|
26
|
+
const HOURS = Array.from({ length: 24 }, (_, index) => index);
|
|
27
|
+
function Column({ label, ariaLabel, values, selected, onPick, disabled, }) {
|
|
28
|
+
const scroller = useRef(null);
|
|
29
|
+
const chosen = useRef(null);
|
|
30
|
+
// The chosen value, in view, the moment the column is drawn. A column that
|
|
31
|
+
// opens at midnight when it holds 17:00 makes the reader scroll to find out
|
|
32
|
+
// what it is already set to.
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
const item = chosen.current;
|
|
35
|
+
if (!item) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
// After layout, and against the scroller rather than whatever the list
|
|
39
|
+
// happens to be positioned against.
|
|
40
|
+
const frame = requestAnimationFrame(() => {
|
|
41
|
+
item.scrollIntoView({ block: "center", inline: "nearest" });
|
|
42
|
+
});
|
|
43
|
+
return () => cancelAnimationFrame(frame);
|
|
44
|
+
}, [selected]);
|
|
45
|
+
return (_jsxs(Box, { sx: { flex: 1, minWidth: 0 }, children: [_jsx(Text, { sx: {
|
|
46
|
+
display: "block",
|
|
47
|
+
fontSize: 0,
|
|
48
|
+
color: "fg.muted",
|
|
49
|
+
mb: 1,
|
|
50
|
+
textAlign: "center",
|
|
51
|
+
}, children: label }), _jsx(Box, { ref: scroller, sx: {
|
|
52
|
+
height: 132,
|
|
53
|
+
overflowY: "auto",
|
|
54
|
+
border: "1px solid",
|
|
55
|
+
borderColor: "border.default",
|
|
56
|
+
borderRadius: 2,
|
|
57
|
+
bg: "canvas.default",
|
|
58
|
+
// The scrollbar is the only hint that there is more below, so it
|
|
59
|
+
// stays: a hidden one turns the column into a list of six hours.
|
|
60
|
+
scrollbarWidth: "thin",
|
|
61
|
+
}, children: _jsx(ActionList, { selectionVariant: "single", "aria-label": ariaLabel, sx: { p: 1 }, children: values.map((value) => (_jsx(ActionList.Item, { ref: value === selected ? chosen : undefined, selected: value === selected, disabled: disabled, onSelect: () => onPick(value), sx: { justifyContent: "center", fontVariantNumeric: "tabular-nums" }, children: pad(value) }, value))) }) })] }));
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* The two columns, side by side, with the time they currently mean between
|
|
65
|
+
* them.
|
|
66
|
+
*/
|
|
67
|
+
export function TimeColumns({ hour, minute, step = 5, onChange, disabled, labelPrefix, }) {
|
|
68
|
+
const every = Math.min(60, Math.max(1, Math.floor(step) || 1));
|
|
69
|
+
const minutes = Array.from({ length: Math.ceil(60 / every) }, (_, index) => index * every);
|
|
70
|
+
// A minute the step does not land on — 17:07 read back from a stored value —
|
|
71
|
+
// still has to be selectable, or choosing an hour would silently move it.
|
|
72
|
+
const shown = minutes.includes(minute)
|
|
73
|
+
? minutes
|
|
74
|
+
: [...minutes, minute].sort((left, right) => left - right);
|
|
75
|
+
return (_jsxs(Box, { children: [_jsxs(Box, { sx: {
|
|
76
|
+
display: "flex",
|
|
77
|
+
alignItems: "baseline",
|
|
78
|
+
justifyContent: "space-between",
|
|
79
|
+
gap: 2,
|
|
80
|
+
mb: 1,
|
|
81
|
+
}, children: [_jsx(Text, { sx: { fontSize: 0, color: "fg.muted" }, children: "Time" }), _jsxs(Text, { sx: {
|
|
82
|
+
fontFamily: "mono",
|
|
83
|
+
fontSize: 1,
|
|
84
|
+
fontWeight: "bold",
|
|
85
|
+
fontVariantNumeric: "tabular-nums",
|
|
86
|
+
}, children: [pad(hour), ":", pad(minute)] })] }), _jsxs(Box, { sx: { display: "flex", gap: 2 }, children: [_jsx(Column, { label: "Hour", ariaLabel: labelPrefix ? `${labelPrefix}, hour` : "Hour", values: HOURS, selected: hour, disabled: disabled, onPick: (next) => onChange(next, minute) }), _jsx(Column, { label: "Minute", ariaLabel: labelPrefix ? `${labelPrefix}, minute` : "Minute", values: shown, selected: minute, disabled: disabled, onPick: (next) => onChange(hour, next) })] })] }));
|
|
87
|
+
}
|
|
88
|
+
export default TimeColumns;
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/*
|
|
3
|
+
* Copyright (c) 2021-2026 Datalayer, Inc.
|
|
4
|
+
* Distributed under the terms of the Modified BSD License.
|
|
5
|
+
*/
|
|
2
6
|
import { useTheme } from '@primer/react';
|
|
3
7
|
import { CircleCurrentColorIcon } from '@datalayer/icons-react';
|
|
4
8
|
export const CircleIcon = ({ color = 'white', variant = 'fg', }) => {
|
package/lib/components/index.js
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2021-2026 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
1
5
|
export * from "./appearance/AppearanceControls";
|
|
2
6
|
export * from "./appearance/AppearanceControlsWithStore";
|
|
3
7
|
export * from "./appearance/AppearanceMenu";
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
/*
|
|
3
|
+
* Copyright (c) 2021-2026 Datalayer, Inc.
|
|
4
|
+
* Distributed under the terms of the Modified BSD License.
|
|
5
|
+
*/
|
|
2
6
|
import { useEffect, useState } from 'react';
|
|
3
7
|
import { IconButton, Overlay as PrimerOverlay, ThemeProvider, useTheme } from "@primer/react";
|
|
4
8
|
import { XIcon } from "@primer/octicons-react";
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/*
|
|
3
|
+
* Copyright (c) 2021-2026 Datalayer, Inc.
|
|
4
|
+
* Distributed under the terms of the Modified BSD License.
|
|
5
|
+
*/
|
|
2
6
|
import { useState, useRef } from "react";
|
|
3
7
|
import { Heading, Text, Box, Button } from "@primer/react";
|
|
4
8
|
import { SideOverlay } from '../..';
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/*
|
|
3
|
+
* Copyright (c) 2021-2026 Datalayer, Inc.
|
|
4
|
+
* Distributed under the terms of the Modified BSD License.
|
|
5
|
+
*/
|
|
2
6
|
import { useEffect, useState } from "react";
|
|
3
7
|
import { useTheme, Box, CounterLabel, FormControl } from "@primer/react";
|
|
4
8
|
export const Slider = ({ name = "slider", id = "slider", min = 0, max = 10, value = 5, label = "label", step = 1, markers = false, displayValue = true, disabled = false, orientation = "horizontal", width = "200px", onChange, }) => {
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/*
|
|
3
|
+
* Copyright (c) 2021-2026 Datalayer, Inc.
|
|
4
|
+
* Distributed under the terms of the Modified BSD License.
|
|
5
|
+
*/
|
|
2
6
|
import { useState } from 'react';
|
|
3
7
|
import { Box, CounterLabel } from "@primer/react";
|
|
4
8
|
import { Slider } from '../../index';
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/*
|
|
3
|
+
* Copyright (c) 2021-2026 Datalayer, Inc.
|
|
4
|
+
* Distributed under the terms of the Modified BSD License.
|
|
5
|
+
*/
|
|
2
6
|
import { useEffect, useMemo, useState } from 'react';
|
|
3
7
|
import { IconButton } from '@primer/react';
|
|
4
8
|
import { XIcon } from '@primer/octicons-react';
|
|
@@ -169,7 +173,19 @@ export const SlidingPanel = ({ isOpen, message, details, onDismiss, position = '
|
|
|
169
173
|
display: 'flex',
|
|
170
174
|
pointerEvents: 'none',
|
|
171
175
|
...anchorStyles,
|
|
172
|
-
}, children: _jsxs(Box, { role: variant === 'error' || variant === 'danger' ? 'alert' : 'status', "aria-live": variant === 'error' || variant === 'danger' ? 'assertive' : 'polite', onClick: onDismiss
|
|
176
|
+
}, children: _jsxs(Box, { role: variant === 'error' || variant === 'danger' ? 'alert' : 'status', "aria-live": variant === 'error' || variant === 'danger' ? 'assertive' : 'polite', onClick: onDismiss
|
|
177
|
+
? event => {
|
|
178
|
+
// A tap on the panel dismisses it — but not a click on a
|
|
179
|
+
// link, button or field in its content, nor the end of a
|
|
180
|
+
// text selection: those are someone using what it says.
|
|
181
|
+
const target = event.target;
|
|
182
|
+
if (target.closest('a, button, input, select, textarea, label, [role="button"], [contenteditable="true"]') ||
|
|
183
|
+
(window.getSelection()?.toString() ?? '') !== '') {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
onDismiss();
|
|
187
|
+
}
|
|
188
|
+
: undefined, sx: {
|
|
173
189
|
pointerEvents: 'auto',
|
|
174
190
|
position: 'relative',
|
|
175
191
|
display: 'flex',
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/*
|
|
3
|
+
* Copyright (c) 2021-2026 Datalayer, Inc.
|
|
4
|
+
* Distributed under the terms of the Modified BSD License.
|
|
5
|
+
*/
|
|
2
6
|
import { useRef, useState } from 'react';
|
|
3
7
|
import { Box, Button, Heading } from '@primer/react';
|
|
4
8
|
import { SlidingPanel } from './SlidingPanel';
|
package/lib/index.js
CHANGED
|
@@ -112,7 +112,6 @@ function ActivityLine({ label }) {
|
|
|
112
112
|
} }), _jsx(Text, { children: label })] }));
|
|
113
113
|
}
|
|
114
114
|
export function PageLayout({ page, hasPage = page !== undefined && page !== null, panel, band, chips, picker, transient, activity, bandMode = "docked", panelMode = "docked", panelSide = "right", sheetWidth = PAGE_SHEET_WIDTH, pageSize, panelWidth = PAGE_PANEL_WIDTH, }) {
|
|
115
|
-
const floating = bandMode === "floating";
|
|
116
115
|
const bandInPanel = bandMode === "panel";
|
|
117
116
|
const hasBand = band !== undefined && band !== null;
|
|
118
117
|
// One width for the sheet, the band's mount and the chips: the column.
|
|
@@ -238,73 +237,90 @@ export function PageLayout({ page, hasPage = page !== undefined && page !== null
|
|
|
238
237
|
pointerEvents: "none",
|
|
239
238
|
zIndex: -1,
|
|
240
239
|
display: "flex",
|
|
241
|
-
}, children: page }))] })] }), !hasPage || !panel ? null :
|
|
240
|
+
}, children: page }))] })] }), !hasPage || !panel ? null : (_jsxs(Box, { "data-page-panel": panelShown ? "" : undefined, "data-page-panel-mode": panelMode, "aria-hidden": panelShown ? undefined : "true", sx: {
|
|
242
241
|
minWidth: 0,
|
|
243
242
|
minHeight: 0,
|
|
244
243
|
display: "flex",
|
|
245
244
|
flexDirection: "column",
|
|
246
245
|
bg: "canvas.default",
|
|
247
246
|
borderColor: "border.default",
|
|
248
|
-
...(
|
|
247
|
+
...(!panelShown
|
|
249
248
|
? {
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
//
|
|
255
|
-
|
|
256
|
-
|
|
249
|
+
// Out of sight, and out of the way of the pointer, while
|
|
250
|
+
// whatever is in it goes on running. No `z-index` here:
|
|
251
|
+
// it would make a stacking context, and the composer —
|
|
252
|
+
// which lives in this column whether the panel is open or
|
|
253
|
+
// not — has to be able to float over the page from it.
|
|
254
|
+
position: "absolute",
|
|
255
|
+
inset: 0,
|
|
256
|
+
visibility: "hidden",
|
|
257
|
+
pointerEvents: "none",
|
|
258
|
+
overflow: "visible",
|
|
257
259
|
}
|
|
258
|
-
: panelMode === "
|
|
260
|
+
: panelMode === "docked"
|
|
259
261
|
? {
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
top: 0,
|
|
264
|
-
bottom: 0,
|
|
265
|
-
[panelSide]: 0,
|
|
266
|
-
width: panelWidth,
|
|
267
|
-
maxWidth: "85%",
|
|
262
|
+
flex: `0 0 ${panelWidth}px`,
|
|
263
|
+
maxWidth: "45%",
|
|
264
|
+
order: panelSide === "left" ? -1 : undefined,
|
|
268
265
|
[panelSide === "left" ? "borderRight" : "borderLeft"]: "1px solid",
|
|
269
|
-
|
|
270
|
-
|
|
266
|
+
// Over the canvas, and beside the band rather than under
|
|
267
|
+
// it: the band belongs to the page column.
|
|
268
|
+
zIndex: 1,
|
|
271
269
|
}
|
|
272
|
-
:
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
270
|
+
: panelMode === "overlay"
|
|
271
|
+
? {
|
|
272
|
+
// Over the page's right edge, the page untouched
|
|
273
|
+
// under it; against the root, as the float is.
|
|
274
|
+
position: "absolute",
|
|
275
|
+
top: 0,
|
|
276
|
+
bottom: 0,
|
|
277
|
+
[panelSide]: 0,
|
|
278
|
+
width: panelWidth,
|
|
279
|
+
maxWidth: "85%",
|
|
280
|
+
[panelSide === "left" ? "borderRight" : "borderLeft"]: "1px solid",
|
|
281
|
+
boxShadow: "shadow.large",
|
|
282
|
+
zIndex: 5,
|
|
283
|
+
}
|
|
284
|
+
: {
|
|
285
|
+
// A window in the page's corner.
|
|
286
|
+
position: "absolute",
|
|
287
|
+
[panelSide]: 16,
|
|
288
|
+
bottom: 16,
|
|
289
|
+
width: panelWidth,
|
|
290
|
+
maxWidth: "calc(100% - 32px)",
|
|
291
|
+
height: "min(70%, 560px)",
|
|
292
|
+
border: "1px solid",
|
|
293
|
+
borderRadius: 2,
|
|
294
|
+
boxShadow: "shadow.large",
|
|
295
|
+
overflow: "hidden",
|
|
296
|
+
zIndex: 5,
|
|
297
|
+
}),
|
|
286
298
|
}, children: [_jsx(Box, { sx: {
|
|
287
299
|
flex: "1 1 auto",
|
|
288
300
|
minHeight: 0,
|
|
289
301
|
display: "flex",
|
|
290
302
|
flexDirection: "column",
|
|
291
303
|
"& > *": { flex: "1 1 auto", minHeight: 0 },
|
|
292
|
-
}, children: panel }),
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
304
|
+
}, children: panel }), hasBand && bandMode !== "docked" ? (_jsxs(Box, { "data-page-panel-band": bandInPanel ? "" : undefined, sx: bandInPanel
|
|
305
|
+
? {
|
|
306
|
+
flex: "0 0 auto",
|
|
307
|
+
borderTop: "1px solid",
|
|
308
|
+
borderColor: "border.default",
|
|
309
|
+
// Visible even while the column itself is hidden —
|
|
310
|
+
// which it is until something opens the panel.
|
|
311
|
+
visibility: "visible",
|
|
312
|
+
pointerEvents: "auto",
|
|
313
|
+
"& > *": { justifyContent: "center" },
|
|
314
|
+
}
|
|
315
|
+
: {
|
|
316
|
+
/* The card positions itself against this layout's
|
|
317
|
+
root; this only has to stay out of its way, and
|
|
318
|
+
stay visible when the column around it is not. */
|
|
319
|
+
position: "absolute",
|
|
320
|
+
inset: 0,
|
|
321
|
+
visibility: "visible",
|
|
322
|
+
pointerEvents: "none",
|
|
323
|
+
"& > *": { pointerEvents: "auto" },
|
|
324
|
+
}, children: [bandInPanel ? chips : null, band] })) : null] }))] }), transient] }));
|
|
309
325
|
}
|
|
310
326
|
export default PageLayout;
|
package/lib/story-helpers.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ type StoryContext = Record<string, unknown> & {
|
|
|
8
8
|
parameters: Record<string, unknown>;
|
|
9
9
|
};
|
|
10
10
|
export declare const colormodeFromScheme: (colorScheme: string) => "light" | "dark";
|
|
11
|
-
export declare const withThemeProvider: (Story: React.FC<React.PropsWithChildren<StoryContext>>, context: StoryContext) => string | number | bigint | boolean | Iterable<React.ReactNode> | React.
|
|
11
|
+
export declare const withThemeProvider: (Story: React.FC<React.PropsWithChildren<StoryContext>>, context: StoryContext) => string | number | bigint | boolean | Iterable<React.ReactNode> | Promise<React.ReactNode> | React.JSX.Element | null | undefined;
|
|
12
12
|
export declare const toolbarTypes: {
|
|
13
13
|
colorScheme: {
|
|
14
14
|
name: string;
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
/*
|
|
3
3
|
* Copyright (c) 2021-2024 Datalayer, Inc.
|
|
4
|
-
*
|
|
5
|
-
* Datalayer License
|
|
4
|
+
* Distributed under the terms of the Modified BSD License.
|
|
6
5
|
*/
|
|
7
6
|
/**
|
|
8
7
|
* Shared palette types, theme-aware colour resolution, and reusable
|
package/lib/utils/Portals.js
CHANGED
|
@@ -119,7 +119,9 @@ export const setupPrimerPortals = (colormode) => {
|
|
|
119
119
|
* `setupPrimerPortals` creates it; before that call there is none, and this
|
|
120
120
|
* answers `null` rather than inventing one.
|
|
121
121
|
*/
|
|
122
|
-
export const getPrimerPortalRoot = () => document
|
|
122
|
+
export const getPrimerPortalRoot = () => typeof document === 'undefined'
|
|
123
|
+
? null
|
|
124
|
+
: document.getElementById(PRIMER_PORTAL_ROOT_ID);
|
|
123
125
|
/* ─── camelCase → kebab-case ─────────────────────────────────────────── */
|
|
124
126
|
const camelToKebab = (s) => s.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`);
|
|
125
127
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@datalayer/primer-addons",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.23",
|
|
4
4
|
"license": "BSD-3-Clause",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"build": "npm run build:lib",
|
|
@@ -37,13 +37,19 @@
|
|
|
37
37
|
"@primer/octicons-react": "^19.35.0",
|
|
38
38
|
"@primer/primitives": "^10.7.0",
|
|
39
39
|
"@primer/react": "^37.31.0",
|
|
40
|
-
"react": "
|
|
41
|
-
"react-dom": "19.2.8",
|
|
40
|
+
"@primer/react-brand": "^0.58.2",
|
|
42
41
|
"react-loading-skeleton": "^3.5.0",
|
|
43
42
|
"styled-components": "^5.3.10",
|
|
44
43
|
"zustand": "^5.0.0"
|
|
45
44
|
},
|
|
45
|
+
"peerDependencies": {
|
|
46
|
+
"react": "^18.3.1 || ^19.0.0",
|
|
47
|
+
"react-dom": "^18.3.1 || ^19.0.0"
|
|
48
|
+
},
|
|
46
49
|
"devDependencies": {
|
|
50
|
+
"react": "19.2.8",
|
|
51
|
+
"react-dom": "19.2.8",
|
|
52
|
+
"rimraf": "^6.0.1",
|
|
47
53
|
"@storybook/addon-docs": "^9.0.15",
|
|
48
54
|
"@storybook/addon-essentials": "9.0.0-alpha.12",
|
|
49
55
|
"@storybook/addon-interactions": "^9.0.0-alpha.10",
|