@microbit/ui 0.1.0-alpha.8 → 0.1.0
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/LICENSE.md +8 -0
- package/README.md +171 -20
- package/lang/ui.ar.json +62 -0
- package/lang/ui.ca.json +43 -3
- package/lang/ui.cy.json +62 -0
- package/lang/ui.de.json +44 -4
- package/lang/ui.en-us.json +40 -0
- package/lang/ui.en.json +40 -0
- package/lang/ui.es-es.json +43 -3
- package/lang/ui.fr.json +44 -4
- package/lang/ui.ga-ie.json +43 -3
- package/lang/ui.it.json +62 -0
- package/lang/ui.ja.json +44 -4
- package/lang/ui.ko.json +44 -4
- package/lang/ui.lo.json +62 -0
- package/lang/ui.lol.json +40 -0
- package/lang/ui.nl.json +44 -4
- package/lang/ui.pl.json +44 -4
- package/lang/ui.pt-br.json +44 -4
- package/lang/ui.vi.json +62 -0
- package/lang/ui.zh-cn.json +44 -4
- package/lang/ui.zh-tw.json +44 -4
- package/package.json +12 -10
- package/reset.css +12 -0
- package/src/Avatar.recipe.ts +191 -0
- package/src/Avatar.tsx +278 -0
- package/src/Breadcrumb.recipe.ts +45 -0
- package/src/Breadcrumb.tsx +114 -0
- package/src/Button.recipe.ts +88 -50
- package/src/Button.tsx +67 -14
- package/src/ButtonGroup.tsx +37 -15
- package/src/Card.recipe.ts +1 -2
- package/src/Card.tsx +2 -1
- package/src/Checkbox.recipe.ts +49 -14
- package/src/Checkbox.tsx +111 -32
- package/src/CheckboxGroup.tsx +70 -0
- package/src/CloseButton.tsx +3 -3
- package/src/CloseIcon.tsx +6 -4
- package/src/Code.tsx +1 -1
- package/src/Collapse.tsx +13 -14
- package/src/ComboBox.tsx +246 -0
- package/src/Divider.tsx +40 -7
- package/src/Drawer.recipe.ts +21 -10
- package/src/Drawer.tsx +3 -4
- package/src/ExternalLink.tsx +43 -0
- package/src/Fade.tsx +18 -4
- package/src/Field.recipe.ts +114 -0
- package/src/Field.tsx +187 -0
- package/src/GridList.recipe.ts +57 -0
- package/src/GridList.tsx +81 -0
- package/src/Heading.recipe.ts +20 -1
- package/src/Heading.tsx +3 -3
- package/src/Icon.tsx +23 -7
- package/src/IconButton.tsx +8 -18
- package/src/Image.tsx +1 -1
- package/src/Input.recipe.ts +41 -28
- package/src/Input.tsx +23 -7
- package/src/InputGroup.tsx +26 -12
- package/src/Kbd.tsx +1 -1
- package/src/Link.tsx +3 -3
- package/src/LinkBox.tsx +2 -3
- package/src/LinkButton.tsx +14 -13
- package/src/List.tsx +5 -7
- package/src/ListBox.recipe.ts +43 -0
- package/src/ListBox.tsx +88 -0
- package/src/Menu.recipe.ts +22 -19
- package/src/Menu.tsx +54 -28
- package/src/Modal.recipe.ts +17 -9
- package/src/Modal.tsx +123 -24
- package/src/MoreMenuButton.tsx +63 -0
- package/src/NativeSelect.tsx +41 -14
- package/src/NativeSelectField.tsx +84 -0
- package/src/NumberField.recipe.ts +56 -15
- package/src/NumberField.tsx +74 -22
- package/src/PopoverArrow.tsx +1 -2
- package/src/ProgressBar.tsx +3 -5
- package/src/Radio.recipe.ts +108 -0
- package/src/Radio.tsx +62 -0
- package/src/RadioGroup.tsx +68 -0
- package/src/Select.recipe.ts +211 -0
- package/src/Select.tsx +194 -0
- package/src/SharedUIProvider.tsx +63 -7
- package/src/Skeleton.tsx +146 -0
- package/src/Slide.tsx +2 -2
- package/src/Slider.recipe.ts +27 -16
- package/src/Slider.tsx +20 -12
- package/src/Spinner.tsx +11 -9
- package/src/Svg.tsx +2 -3
- package/src/Switch.recipe.ts +57 -11
- package/src/Switch.tsx +39 -7
- package/src/Text.recipe.ts +26 -0
- package/src/Text.tsx +6 -2
- package/src/TextField.tsx +39 -40
- package/src/Toast.recipe.ts +52 -15
- package/src/Toast.tsx +147 -48
- package/src/Tooltip.recipe.ts +32 -0
- package/src/Tooltip.tsx +68 -28
- package/src/TooltipButton.tsx +229 -0
- package/src/UnmountCallback.tsx +2 -2
- package/src/VisuallyHidden.tsx +1 -1
- package/src/base-preset.ts +329 -84
- package/src/base-tokens.ts +0 -3
- package/src/button-icon.ts +4 -5
- package/src/data-attrs.ts +16 -0
- package/src/dense-preset.ts +107 -0
- package/src/hooks/useBreakpointValue.ts +4 -4
- package/src/hooks/useClipboard.ts +43 -7
- package/src/hooks/useDisclosure.ts +31 -0
- package/src/hooks/useMediaQuery.ts +3 -4
- package/src/hooks/usePrevious.ts +1 -4
- package/src/index.ts +20 -3
- package/src/rac-locale.ts +33 -0
- package/src/system.ts +22 -1
- package/src/TextField.recipe.ts +0 -54
package/src/Switch.recipe.ts
CHANGED
|
@@ -6,15 +6,15 @@
|
|
|
6
6
|
import { defineSlotRecipe } from "@pandacss/dev";
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
* Switch slot recipe
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* Switch slot recipe with an sm/md/lg size scale. The track has a 2px inset;
|
|
10
|
+
* the thumb matches the track height and slides by the track width/height
|
|
11
|
+
* difference when selected.
|
|
12
12
|
*
|
|
13
13
|
* State styling keys off data attributes stamped by the shared-ui Switch
|
|
14
14
|
* (react-aria provides the state via render props).
|
|
15
15
|
*
|
|
16
|
-
* Registered in the base preset (base-preset.ts)
|
|
17
|
-
*
|
|
16
|
+
* Registered in the base preset (base-preset.ts), which also has the
|
|
17
|
+
* `staticCss` entry that keeps the runtime-prop size variants generated.
|
|
18
18
|
*/
|
|
19
19
|
export const switchRecipe = defineSlotRecipe({
|
|
20
20
|
className: "switch",
|
|
@@ -35,8 +35,6 @@ export const switchRecipe = defineSlotRecipe({
|
|
|
35
35
|
boxSizing: "content-box",
|
|
36
36
|
borderRadius: "full",
|
|
37
37
|
p: "0.5",
|
|
38
|
-
width: "1.875rem",
|
|
39
|
-
height: "4",
|
|
40
38
|
transitionProperty: "background-color",
|
|
41
39
|
transitionDuration: "fast",
|
|
42
40
|
bg: "gray.300",
|
|
@@ -44,7 +42,7 @@ export const switchRecipe = defineSlotRecipe({
|
|
|
44
42
|
bg: "controlCheckedBg",
|
|
45
43
|
},
|
|
46
44
|
"&[data-focus-visible]": {
|
|
47
|
-
|
|
45
|
+
focusRing: "outline",
|
|
48
46
|
},
|
|
49
47
|
"&[data-disabled]": { opacity: 0.4 },
|
|
50
48
|
// Forced-colors modes strip author backgrounds, flattening track and
|
|
@@ -61,10 +59,18 @@ export const switchRecipe = defineSlotRecipe({
|
|
|
61
59
|
transitionProperty: "transform",
|
|
62
60
|
transitionDuration: "normal",
|
|
63
61
|
borderRadius: "inherit",
|
|
64
|
-
|
|
65
|
-
|
|
62
|
+
// The thumb rests at the track's start and slides to its end, so the
|
|
63
|
+
// travel has to reverse in RTL — a transform is physical however the
|
|
64
|
+
// rest of the switch is laid out. Distance is per size, below.
|
|
65
|
+
// Fallbacks carry a unit: `calc(-1 * 0)` is a number, which is invalid
|
|
66
|
+
// where a length is wanted, and would drop the RTL transform whole.
|
|
66
67
|
"&[data-selected]": {
|
|
67
|
-
transform: "translateX(
|
|
68
|
+
transform: "translateX(var(--switch-travel, 0px))",
|
|
69
|
+
},
|
|
70
|
+
_rtl: {
|
|
71
|
+
"&[data-selected]": {
|
|
72
|
+
transform: "translateX(calc(-1 * var(--switch-travel, 0px)))",
|
|
73
|
+
},
|
|
68
74
|
},
|
|
69
75
|
_forcedColors: {
|
|
70
76
|
bg: "ButtonText",
|
|
@@ -77,4 +83,44 @@ export const switchRecipe = defineSlotRecipe({
|
|
|
77
83
|
"&[data-disabled]": { opacity: 0.4 },
|
|
78
84
|
},
|
|
79
85
|
},
|
|
86
|
+
variants: {
|
|
87
|
+
// `start` puts the label first and the control at the row's end — the
|
|
88
|
+
// settings-row pattern (a preference name beside its switch), the toggle
|
|
89
|
+
// counterpart of the field recipe's `labelPosition="side"`. Values are
|
|
90
|
+
// start/end rather than top/side because the default label is already
|
|
91
|
+
// beside the control, after it. The label keeps an end margin so a long
|
|
92
|
+
// translation can't butt against the track (the SelectFormControl
|
|
93
|
+
// lesson — see the field-chrome roadmap bullet).
|
|
94
|
+
labelPosition: {
|
|
95
|
+
end: {},
|
|
96
|
+
start: {
|
|
97
|
+
root: {
|
|
98
|
+
flexDirection: "row-reverse",
|
|
99
|
+
justifyContent: "space-between",
|
|
100
|
+
width: "100%",
|
|
101
|
+
},
|
|
102
|
+
label: { marginStart: "0", marginEnd: "3" },
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
// Track width x height per size; the thumb's travel is the difference
|
|
106
|
+
// between them.
|
|
107
|
+
size: {
|
|
108
|
+
sm: {
|
|
109
|
+
track: { width: "1.375rem", height: "3" },
|
|
110
|
+
thumb: { width: "3", height: "3", "--switch-travel": "0.625rem" },
|
|
111
|
+
},
|
|
112
|
+
md: {
|
|
113
|
+
track: { width: "1.875rem", height: "4" },
|
|
114
|
+
thumb: { width: "4", height: "4", "--switch-travel": "0.875rem" },
|
|
115
|
+
},
|
|
116
|
+
lg: {
|
|
117
|
+
track: { width: "2.875rem", height: "6" },
|
|
118
|
+
thumb: { width: "6", height: "6", "--switch-travel": "1.375rem" },
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
defaultVariants: {
|
|
123
|
+
size: "md",
|
|
124
|
+
labelPosition: "end",
|
|
125
|
+
},
|
|
80
126
|
});
|
package/src/Switch.tsx
CHANGED
|
@@ -3,38 +3,59 @@
|
|
|
3
3
|
*
|
|
4
4
|
* SPDX-License-Identifier: MIT
|
|
5
5
|
*/
|
|
6
|
-
import { ReactNode } from "react";
|
|
6
|
+
import { ReactNode, useId } from "react";
|
|
7
7
|
import {
|
|
8
8
|
Switch as RACSwitch,
|
|
9
9
|
SwitchProps as RACSwitchProps,
|
|
10
10
|
} from "react-aria-components";
|
|
11
11
|
import { css, cx } from "styled-system/css";
|
|
12
|
-
import { switchRecipe } from "styled-system/recipes";
|
|
12
|
+
import { switchRecipe, SwitchRecipeVariantProps } from "styled-system/recipes";
|
|
13
13
|
import { SystemStyleObject } from "styled-system/types";
|
|
14
|
+
import { FieldHelperText } from "./Field";
|
|
14
15
|
|
|
15
16
|
export interface SwitchProps
|
|
16
|
-
extends Omit<RACSwitchProps, "className" | "children" | "style"
|
|
17
|
+
extends Omit<RACSwitchProps, "className" | "children" | "style">,
|
|
18
|
+
SwitchRecipeVariantProps {
|
|
17
19
|
/** Per-instance style overrides for the root, merged after the recipe. */
|
|
18
20
|
css?: SystemStyleObject;
|
|
19
21
|
className?: string;
|
|
20
22
|
children?: ReactNode;
|
|
23
|
+
/**
|
|
24
|
+
* Help text below the switch, wired to its `aria-describedby` — the same
|
|
25
|
+
* chrome the labelled fields' `helperText` renders. With it the component
|
|
26
|
+
* gains a wrapping `<div>`, so the switch-plus-text moves as one block.
|
|
27
|
+
*/
|
|
28
|
+
helperText?: ReactNode;
|
|
29
|
+
/** Per-instance style overrides for the helper text. */
|
|
30
|
+
helperTextCss?: SystemStyleObject;
|
|
21
31
|
}
|
|
22
32
|
|
|
23
33
|
/**
|
|
24
|
-
* Switch — react-aria-components <Switch
|
|
25
|
-
*
|
|
34
|
+
* Switch — a styled react-aria-components <Switch>. Children render as the
|
|
35
|
+
* label; pass `aria-label` for label-less switches. `labelPosition="start"`
|
|
36
|
+
* is the settings-row layout: label first, switch at the row's end.
|
|
26
37
|
*/
|
|
27
38
|
export const Switch = ({
|
|
39
|
+
size,
|
|
40
|
+
labelPosition,
|
|
28
41
|
css: cssProp,
|
|
29
42
|
className,
|
|
30
43
|
children,
|
|
44
|
+
helperText,
|
|
45
|
+
helperTextCss,
|
|
31
46
|
...rest
|
|
32
47
|
}: SwitchProps) => {
|
|
33
|
-
const slots = switchRecipe();
|
|
34
|
-
|
|
48
|
+
const slots = switchRecipe({ size, labelPosition });
|
|
49
|
+
const helperId = useId();
|
|
50
|
+
const describedBy =
|
|
51
|
+
[rest["aria-describedby"], helperText != null ? helperId : undefined]
|
|
52
|
+
.filter(Boolean)
|
|
53
|
+
.join(" ") || undefined;
|
|
54
|
+
const switchElement = (
|
|
35
55
|
<RACSwitch
|
|
36
56
|
className={cx(slots.root, cssProp ? css(cssProp) : undefined, className)}
|
|
37
57
|
{...rest}
|
|
58
|
+
aria-describedby={describedBy}
|
|
38
59
|
>
|
|
39
60
|
{({ isSelected, isFocusVisible, isDisabled }) => {
|
|
40
61
|
const state = {
|
|
@@ -57,4 +78,15 @@ export const Switch = ({
|
|
|
57
78
|
}}
|
|
58
79
|
</RACSwitch>
|
|
59
80
|
);
|
|
81
|
+
if (helperText == null) {
|
|
82
|
+
return switchElement;
|
|
83
|
+
}
|
|
84
|
+
return (
|
|
85
|
+
<div>
|
|
86
|
+
{switchElement}
|
|
87
|
+
<FieldHelperText id={helperId} css={helperTextCss}>
|
|
88
|
+
{helperText}
|
|
89
|
+
</FieldHelperText>
|
|
90
|
+
</div>
|
|
91
|
+
);
|
|
60
92
|
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* (c) 2026, Micro:bit Educational Foundation and contributors
|
|
3
|
+
*
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*/
|
|
6
|
+
import { defineRecipe } from "@pandacss/dev";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Text recipe — sizes only, no default: with no `size`, Text inherits, which
|
|
10
|
+
* is what most family apps expect. A config recipe so an app preset can set
|
|
11
|
+
* an app-wide default size by extending `defaultVariants` (python-editor
|
|
12
|
+
* defaults to md, which its shrunken fontSize scale renders at 0.9rem).
|
|
13
|
+
*
|
|
14
|
+
* Registered in the base preset (base-preset.ts).
|
|
15
|
+
*/
|
|
16
|
+
export const text = defineRecipe({
|
|
17
|
+
className: "text",
|
|
18
|
+
jsx: ["Text"],
|
|
19
|
+
variants: {
|
|
20
|
+
size: {
|
|
21
|
+
sm: { fontSize: "sm" },
|
|
22
|
+
md: { fontSize: "md" },
|
|
23
|
+
lg: { fontSize: "lg" },
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
});
|
package/src/Text.tsx
CHANGED
|
@@ -4,9 +4,13 @@
|
|
|
4
4
|
* SPDX-License-Identifier: MIT
|
|
5
5
|
*/
|
|
6
6
|
import { styled } from "styled-system/jsx";
|
|
7
|
+
import { text } from "styled-system/recipes";
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Text — a paragraph that accepts Panda style props. Use `as` to render a
|
|
10
|
-
* different element (`span`, `div`, `h2`).
|
|
11
|
+
* different element (`span`, `div`, `h2`).
|
|
12
|
+
* Backed by the `text` config recipe: `size` picks a type-scale size, and an
|
|
13
|
+
* app preset can extend the recipe's `defaultVariants` to give all its Text
|
|
14
|
+
* a default size (see Text.recipe.ts).
|
|
11
15
|
*/
|
|
12
|
-
export const Text = styled("p");
|
|
16
|
+
export const Text = styled("p", text);
|
package/src/TextField.tsx
CHANGED
|
@@ -5,82 +5,81 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { FocusEvent, forwardRef, ReactNode } from "react";
|
|
7
7
|
import {
|
|
8
|
-
FieldError,
|
|
9
8
|
Input as RACInput,
|
|
10
|
-
Label as RACLabel,
|
|
11
|
-
Text as RACText,
|
|
12
9
|
TextField as RACTextField,
|
|
13
10
|
TextFieldProps as RACTextFieldProps,
|
|
14
11
|
} from "react-aria-components";
|
|
15
|
-
import {
|
|
16
|
-
import { field, input } from "styled-system/recipes";
|
|
12
|
+
import { field, input, InputVariantProps } from "styled-system/recipes";
|
|
17
13
|
import { SystemStyleObject } from "styled-system/types";
|
|
14
|
+
import {
|
|
15
|
+
FieldLabel,
|
|
16
|
+
FieldLayoutProps,
|
|
17
|
+
FieldSupport,
|
|
18
|
+
FieldSupportProps,
|
|
19
|
+
} from "./Field";
|
|
18
20
|
|
|
19
21
|
export interface TextFieldProps
|
|
20
22
|
extends Omit<
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
RACTextFieldProps,
|
|
24
|
+
"className" | "children" | "style" | "onFocus" | "onBlur"
|
|
25
|
+
>,
|
|
26
|
+
InputVariantProps,
|
|
27
|
+
FieldSupportProps,
|
|
28
|
+
FieldLayoutProps {
|
|
29
|
+
/** Visible label (asterisk added when `isRequired`). */
|
|
25
30
|
label: ReactNode;
|
|
26
|
-
/**
|
|
27
|
-
|
|
28
|
-
/** Shown below the input when `isInvalid` (Chakra's FormErrorMessage). */
|
|
29
|
-
errorMessage?: ReactNode;
|
|
30
|
-
/** Per-instance style overrides for the helper text. */
|
|
31
|
-
helperTextCss?: SystemStyleObject;
|
|
31
|
+
/** Label style overrides. */
|
|
32
|
+
labelCss?: SystemStyleObject;
|
|
32
33
|
onFocus?: (e: FocusEvent<HTMLInputElement>) => void;
|
|
33
34
|
/** Input autocapitalize attribute (react-aria's TextField omits it). */
|
|
34
35
|
autoCapitalize?: "off" | "none" | "on" | "sentences" | "words" | "characters";
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
/**
|
|
38
|
-
* TextField — a labelled single-line text input
|
|
39
|
-
*
|
|
40
|
-
* forwarded to the input element.
|
|
39
|
+
* TextField — a labelled single-line text input with optional help and error
|
|
40
|
+
* text. The ref is forwarded to the input element.
|
|
41
41
|
*/
|
|
42
42
|
export const TextField = forwardRef<HTMLInputElement, TextFieldProps>(
|
|
43
43
|
function TextField(
|
|
44
44
|
{
|
|
45
45
|
label,
|
|
46
|
+
labelCss,
|
|
46
47
|
helperText,
|
|
47
48
|
errorMessage,
|
|
48
49
|
helperTextCss,
|
|
50
|
+
labelPosition,
|
|
49
51
|
onFocus,
|
|
50
52
|
autoCapitalize,
|
|
51
|
-
...
|
|
53
|
+
...props
|
|
52
54
|
},
|
|
53
55
|
ref,
|
|
54
56
|
) {
|
|
55
|
-
|
|
57
|
+
// As Input: forward every recipe variant group, not just `size`, so a
|
|
58
|
+
// preset that adds one keeps working.
|
|
59
|
+
const [variantProps, rest] = input.splitVariantProps(props);
|
|
60
|
+
const slots = field({ size: variantProps.size, labelPosition });
|
|
56
61
|
return (
|
|
57
62
|
<RACTextField {...rest} className={slots.root}>
|
|
58
|
-
<
|
|
63
|
+
<FieldLabel
|
|
64
|
+
size={variantProps.size}
|
|
65
|
+
labelPosition={labelPosition}
|
|
66
|
+
isRequired={rest.isRequired}
|
|
67
|
+
css={labelCss}
|
|
68
|
+
>
|
|
59
69
|
{label}
|
|
60
|
-
|
|
61
|
-
<span aria-hidden className={slots.requiredIndicator}>
|
|
62
|
-
*
|
|
63
|
-
</span>
|
|
64
|
-
) : null}
|
|
65
|
-
</RACLabel>
|
|
70
|
+
</FieldLabel>
|
|
66
71
|
<RACInput
|
|
67
72
|
ref={ref}
|
|
68
|
-
className={input()}
|
|
73
|
+
className={input(variantProps)}
|
|
69
74
|
onFocus={onFocus}
|
|
70
75
|
autoCapitalize={autoCapitalize}
|
|
71
76
|
/>
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
)}
|
|
79
|
-
>
|
|
80
|
-
{helperText}
|
|
81
|
-
</RACText>
|
|
82
|
-
)}
|
|
83
|
-
<FieldError className={slots.errorMessage}>{errorMessage}</FieldError>
|
|
77
|
+
<FieldSupport
|
|
78
|
+
helperText={helperText}
|
|
79
|
+
errorMessage={errorMessage}
|
|
80
|
+
helperTextCss={helperTextCss}
|
|
81
|
+
labelPosition={labelPosition}
|
|
82
|
+
/>
|
|
84
83
|
</RACTextField>
|
|
85
84
|
);
|
|
86
85
|
},
|
package/src/Toast.recipe.ts
CHANGED
|
@@ -6,10 +6,9 @@
|
|
|
6
6
|
import { defineSlotRecipe } from "@pandacss/dev";
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
* Toast slot recipe —
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* touching the recipe.
|
|
9
|
+
* Toast slot recipe — a solid alert card with white text, teal for every
|
|
10
|
+
* status except error. Status colours come from the `toast*Bg` semantic
|
|
11
|
+
* tokens so brand presets can retune them without touching the recipe.
|
|
13
12
|
*
|
|
14
13
|
* Registered in the base preset (base-preset.ts). The status variant
|
|
15
14
|
* is chosen at runtime from the toast content, so it needs the preset's
|
|
@@ -17,13 +16,36 @@ import { defineSlotRecipe } from "@pandacss/dev";
|
|
|
17
16
|
*/
|
|
18
17
|
export const toast = defineSlotRecipe({
|
|
19
18
|
className: "toast",
|
|
20
|
-
slots: [
|
|
19
|
+
slots: [
|
|
20
|
+
"region",
|
|
21
|
+
"root",
|
|
22
|
+
"content",
|
|
23
|
+
"body",
|
|
24
|
+
"icon",
|
|
25
|
+
"title",
|
|
26
|
+
"description",
|
|
27
|
+
"closeButton",
|
|
28
|
+
],
|
|
21
29
|
base: {
|
|
22
30
|
region: {
|
|
23
31
|
position: "fixed",
|
|
24
32
|
top: "4",
|
|
25
33
|
left: "50%",
|
|
26
34
|
transform: "translateX(-50%)",
|
|
35
|
+
// Percentage centring of a text-width region lands the toasts on a
|
|
36
|
+
// fractional pixel. Chrome floors ::view-transition-group's transform to
|
|
37
|
+
// a whole CSS pixel, so the snapshot the enter/exit animation displays
|
|
38
|
+
// sits up to 1px left of the element it stands in for, and every toast
|
|
39
|
+
// jumps right as the transition hands back to the real DOM (visible
|
|
40
|
+
// above 1x, where the two no longer round to the same device pixel).
|
|
41
|
+
// Rounding the region's own offsets keeps it on whole pixels so the two
|
|
42
|
+
// agree. round() postdates view transitions in Chrome (130 vs 111), so
|
|
43
|
+
// the guard keeps the plain centring for the versions in between rather
|
|
44
|
+
// than dropping the declarations and losing it altogether.
|
|
45
|
+
"@supports (left: round(50%, 1px))": {
|
|
46
|
+
left: "round(50%, 1px)",
|
|
47
|
+
transform: "translateX(round(-50%, 1px))",
|
|
48
|
+
},
|
|
27
49
|
zIndex: "toast",
|
|
28
50
|
display: "flex",
|
|
29
51
|
flexDirection: "column",
|
|
@@ -34,18 +56,35 @@ export const toast = defineSlotRecipe({
|
|
|
34
56
|
position: "relative",
|
|
35
57
|
display: "flex",
|
|
36
58
|
alignItems: "flex-start",
|
|
37
|
-
gap: "3",
|
|
38
59
|
p: "4",
|
|
39
60
|
// Leave room for the absolutely-positioned close button plus breathing
|
|
40
61
|
// space so its hover area never overlaps the title text (24px button at
|
|
41
|
-
// 4px inset spans ~4-28px from the
|
|
42
|
-
// ~12px gap).
|
|
43
|
-
|
|
62
|
+
// 4px inset spans ~4-28px from the end edge; content stops at 40px for
|
|
63
|
+
// a ~12px gap). Logical, like the button's own inset.
|
|
64
|
+
paddingEnd: "10",
|
|
44
65
|
borderRadius: "md",
|
|
45
66
|
boxShadow: "lg",
|
|
46
67
|
color: "white",
|
|
47
68
|
maxW: "sm",
|
|
48
69
|
pointerEvents: "auto",
|
|
70
|
+
// The card is focusable (RAC gives it tabindex), so it takes the
|
|
71
|
+
// house ring rather than the UA default. Ink, because the ring is
|
|
72
|
+
// drawn on the page, not the card — Toast.tsx tags the close button.
|
|
73
|
+
outline: "none",
|
|
74
|
+
_focusVisible: { focusRing: "outline" },
|
|
75
|
+
},
|
|
76
|
+
// The icon has to be inside the alert region to be announced with the
|
|
77
|
+
// message, so this row — not the root — is what lays the two out.
|
|
78
|
+
content: {
|
|
79
|
+
display: "flex",
|
|
80
|
+
alignItems: "flex-start",
|
|
81
|
+
gap: "3",
|
|
82
|
+
// Let the text shrink below its content width so long words wrap
|
|
83
|
+
// instead of pushing past the toast's maxW.
|
|
84
|
+
minWidth: 0,
|
|
85
|
+
},
|
|
86
|
+
body: {
|
|
87
|
+
minWidth: 0,
|
|
49
88
|
},
|
|
50
89
|
icon: {
|
|
51
90
|
fontSize: "1.25rem",
|
|
@@ -58,9 +97,8 @@ export const toast = defineSlotRecipe({
|
|
|
58
97
|
mt: "1",
|
|
59
98
|
},
|
|
60
99
|
closeButton: {
|
|
61
|
-
// A sized box with the glyph centred (
|
|
62
|
-
//
|
|
63
|
-
// affordance.
|
|
100
|
+
// A sized box with the glyph centred (24px box, 2xs glyph) gives
|
|
101
|
+
// padding around the X and a hover affordance.
|
|
64
102
|
position: "absolute",
|
|
65
103
|
top: "1",
|
|
66
104
|
insetEnd: "1",
|
|
@@ -79,11 +117,10 @@ export const toast = defineSlotRecipe({
|
|
|
79
117
|
outline: "none",
|
|
80
118
|
transitionProperty: "background-color, box-shadow",
|
|
81
119
|
transitionDuration: "normal",
|
|
82
|
-
//
|
|
83
|
-
// light mode, not a bright highlight.
|
|
120
|
+
// Hover is a subtle dark overlay (blackAlpha), not a bright highlight.
|
|
84
121
|
_hover: { bg: "blackAlpha.100" },
|
|
85
122
|
_active: { bg: "blackAlpha.200" },
|
|
86
|
-
_focusVisible: {
|
|
123
|
+
_focusVisible: { focusRing: "outline" },
|
|
87
124
|
},
|
|
88
125
|
},
|
|
89
126
|
variants: {
|