@ponti-studios/ui 1.5.0 → 1.6.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/package.json +1 -1
- package/src/components/filters/filter-select.stories.tsx +3 -3
- package/src/components/filters/filter-select.tsx +5 -7
- package/src/components/forms/calendar.tsx +6 -1
- package/src/components/forms/date-field.stories.tsx +38 -0
- package/src/components/forms/date-field.tsx +5 -0
- package/src/components/forms/date-picker.tsx +14 -1
- package/src/components/forms/select.stories.tsx +116 -0
- package/src/components/forms/select.tsx +36 -12
- package/src/components/navigation/app-navigation.stories.tsx +42 -0
- package/src/styles/nokia.css +1 -3
package/package.json
CHANGED
|
@@ -25,7 +25,7 @@ export default meta;
|
|
|
25
25
|
type Story = StoryObj<typeof meta>;
|
|
26
26
|
|
|
27
27
|
export const Default: Story = {
|
|
28
|
-
args: { value:
|
|
28
|
+
args: { value: null },
|
|
29
29
|
play: async ({ canvasElement }) => {
|
|
30
30
|
const canvas = within(canvasElement);
|
|
31
31
|
const trigger = canvas.getByRole("combobox", { name: "Status" });
|
|
@@ -44,7 +44,7 @@ export const WithValue: Story = {
|
|
|
44
44
|
};
|
|
45
45
|
|
|
46
46
|
export const Loading: Story = {
|
|
47
|
-
args: { value:
|
|
47
|
+
args: { value: null, isLoading: true, options: [] },
|
|
48
48
|
play: async ({ canvasElement }) => {
|
|
49
49
|
const canvas = within(canvasElement);
|
|
50
50
|
await expect(canvas.getByRole("combobox", { name: "Status" })).toBeInTheDocument();
|
|
@@ -53,7 +53,7 @@ export const Loading: Story = {
|
|
|
53
53
|
};
|
|
54
54
|
|
|
55
55
|
export const Empty: Story = {
|
|
56
|
-
args: { value:
|
|
56
|
+
args: { value: null, options: [], emptyLabel: "No statuses found" },
|
|
57
57
|
play: async ({ canvasElement }) => {
|
|
58
58
|
const canvas = within(canvasElement);
|
|
59
59
|
await expect(canvas.getByRole("combobox", { name: "Status" })).toBeInTheDocument();
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { useId, useMemo } from "react";
|
|
2
2
|
|
|
3
3
|
import { cn } from "../../lib/utils";
|
|
4
|
-
import { Label } from "../primitives/label";
|
|
5
4
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../forms/select";
|
|
5
|
+
import { Label } from "../primitives/label";
|
|
6
6
|
|
|
7
7
|
export interface FilterSelectOption {
|
|
8
8
|
value: string;
|
|
@@ -10,8 +10,8 @@ export interface FilterSelectOption {
|
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
export interface FilterSelectProps {
|
|
13
|
-
value: string;
|
|
14
|
-
onChange: (value: string) => void;
|
|
13
|
+
value: string | null;
|
|
14
|
+
onChange: (value: string | null) => void;
|
|
15
15
|
options: FilterSelectOption[];
|
|
16
16
|
label: string;
|
|
17
17
|
loadingText?: string;
|
|
@@ -23,8 +23,6 @@ export interface FilterSelectProps {
|
|
|
23
23
|
id?: string;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
const ALL_VALUE = "";
|
|
27
|
-
|
|
28
26
|
export function FilterSelect({
|
|
29
27
|
label,
|
|
30
28
|
value,
|
|
@@ -41,7 +39,7 @@ export function FilterSelect({
|
|
|
41
39
|
const generatedId = useId();
|
|
42
40
|
const selectId = id ?? generatedId;
|
|
43
41
|
const items = useMemo(
|
|
44
|
-
() => [{ value:
|
|
42
|
+
() => [{ value: null, label: placeholder }, ...options],
|
|
45
43
|
[options, placeholder],
|
|
46
44
|
);
|
|
47
45
|
|
|
@@ -53,7 +51,7 @@ export function FilterSelect({
|
|
|
53
51
|
<SelectValue placeholder={placeholder} />
|
|
54
52
|
</SelectTrigger>
|
|
55
53
|
<SelectContent className="bg-background max-h-[250px] min-w-36 overflow-y-auto">
|
|
56
|
-
<SelectItem
|
|
54
|
+
<SelectItem>{placeholder}</SelectItem>
|
|
57
55
|
{isLoading ? (
|
|
58
56
|
<div className="text-muted-foreground px-2 py-1.5 text-sm">{loadingText}</div>
|
|
59
57
|
) : options.length === 0 ? (
|
|
@@ -16,7 +16,12 @@ function Calendar({ className, classNames, showOutsideDays = true, ...props }: C
|
|
|
16
16
|
months: "flex flex-col gap-4 sm:flex-row",
|
|
17
17
|
month: "flex flex-col gap-4",
|
|
18
18
|
month_caption: "relative flex h-8 items-center justify-center",
|
|
19
|
-
caption_label: "text-sm font-medium",
|
|
19
|
+
caption_label: "flex items-center gap-1 text-sm font-medium",
|
|
20
|
+
dropdowns: "flex items-center gap-1",
|
|
21
|
+
dropdown_root: "relative",
|
|
22
|
+
dropdown: "absolute inset-0 z-10 cursor-pointer opacity-0",
|
|
23
|
+
months_dropdown: "",
|
|
24
|
+
years_dropdown: "",
|
|
20
25
|
nav: "absolute inset-x-4 top-4 z-10 flex items-center justify-between",
|
|
21
26
|
button_previous:
|
|
22
27
|
"inline-flex size-8 p-2 items-center justify-center rounded-full border bg-background text-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50",
|
|
@@ -102,6 +102,44 @@ export const Disabled: Story = {
|
|
|
102
102
|
},
|
|
103
103
|
};
|
|
104
104
|
|
|
105
|
+
// No play function — this one is for manually driving the interaction
|
|
106
|
+
// (e.g. screen recordings), not automated assertions.
|
|
107
|
+
export const DropdownEmpty: Story = {
|
|
108
|
+
args: {
|
|
109
|
+
name: "startDate",
|
|
110
|
+
label: "Start date",
|
|
111
|
+
captionLayout: "dropdown",
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
export const WithDropdownCaption: Story = {
|
|
116
|
+
args: {
|
|
117
|
+
mode: "range",
|
|
118
|
+
startName: "startDate",
|
|
119
|
+
endName: "endDate",
|
|
120
|
+
defaultValue: { from: "2018-03-01" },
|
|
121
|
+
label: "Employment period",
|
|
122
|
+
captionLayout: "dropdown",
|
|
123
|
+
},
|
|
124
|
+
play: async ({ canvasElement }) => {
|
|
125
|
+
const canvas = within(canvasElement);
|
|
126
|
+
|
|
127
|
+
// Jumping back to a date years ago is a month/year select, not repeated
|
|
128
|
+
// prev-month clicks.
|
|
129
|
+
const trigger = canvas.getByRole("button", { name: "Employment period" });
|
|
130
|
+
await userEvent.click(trigger);
|
|
131
|
+
const dialog = await screen.findByRole("dialog");
|
|
132
|
+
|
|
133
|
+
const yearSelect = within(dialog).getByRole("combobox", { name: /year/i });
|
|
134
|
+
await userEvent.selectOptions(yearSelect, "2009");
|
|
135
|
+
const monthSelect = within(dialog).getByRole("combobox", { name: /month/i });
|
|
136
|
+
await userEvent.selectOptions(monthSelect, "May");
|
|
137
|
+
|
|
138
|
+
await userEvent.click(within(dialog).getByText("12"));
|
|
139
|
+
await expect(hiddenValue(canvasElement, "startDate")).toBe("2009-05-12");
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
|
|
105
143
|
export const WithRangeDefaultValue: Story = {
|
|
106
144
|
args: {
|
|
107
145
|
mode: "range",
|
|
@@ -23,6 +23,7 @@ interface DateFieldCommonProps {
|
|
|
23
23
|
containerClassName?: string;
|
|
24
24
|
popoverAlign?: DatePickerProps["popoverAlign"];
|
|
25
25
|
variant?: DatePickerProps["variant"];
|
|
26
|
+
captionLayout?: DatePickerProps["captionLayout"];
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
export interface SingleDateFieldProps extends DateFieldCommonProps {
|
|
@@ -55,6 +56,7 @@ function SingleDateField({
|
|
|
55
56
|
containerClassName,
|
|
56
57
|
popoverAlign,
|
|
57
58
|
variant,
|
|
59
|
+
captionLayout,
|
|
58
60
|
}: SingleDateFieldProps) {
|
|
59
61
|
const generatedId = useId();
|
|
60
62
|
const inputId = id ?? generatedId;
|
|
@@ -72,6 +74,7 @@ function SingleDateField({
|
|
|
72
74
|
containerClassName={containerClassName}
|
|
73
75
|
popoverAlign={popoverAlign}
|
|
74
76
|
variant={variant}
|
|
77
|
+
captionLayout={captionLayout}
|
|
75
78
|
value={value}
|
|
76
79
|
onSelect={setValue}
|
|
77
80
|
/>
|
|
@@ -93,6 +96,7 @@ function RangeDateField({
|
|
|
93
96
|
containerClassName = "flex-1 min-w-0",
|
|
94
97
|
popoverAlign = "start",
|
|
95
98
|
variant = "outline",
|
|
99
|
+
captionLayout = "label",
|
|
96
100
|
}: RangeDateFieldProps) {
|
|
97
101
|
const generatedId = useId();
|
|
98
102
|
const inputId = id ?? generatedId;
|
|
@@ -140,6 +144,7 @@ function RangeDateField({
|
|
|
140
144
|
defaultMonth={range.from ?? range.to}
|
|
141
145
|
resetOnSelect
|
|
142
146
|
onSelect={(next) => setRange({ from: next?.from, to: next?.to })}
|
|
147
|
+
captionLayout={captionLayout}
|
|
143
148
|
/>
|
|
144
149
|
</PopoverContent>
|
|
145
150
|
</Popover>
|
|
@@ -17,6 +17,12 @@ export interface DatePickerProps {
|
|
|
17
17
|
containerClassName?: string;
|
|
18
18
|
popoverAlign?: "start" | "center" | "end";
|
|
19
19
|
variant?: "outline" | "default" | "destructive" | "secondary" | "ghost" | "link";
|
|
20
|
+
/**
|
|
21
|
+
* Calendar caption style. `"dropdown"` swaps the month/year label for
|
|
22
|
+
* select dropdowns, which is much faster for picking dates years in the
|
|
23
|
+
* past (e.g. a job start date) than clicking prev/next repeatedly.
|
|
24
|
+
*/
|
|
25
|
+
captionLayout?: "label" | "dropdown" | "dropdown-months" | "dropdown-years";
|
|
20
26
|
}
|
|
21
27
|
|
|
22
28
|
export function DatePicker({
|
|
@@ -31,6 +37,7 @@ export function DatePicker({
|
|
|
31
37
|
containerClassName = "flex-1 min-w-0",
|
|
32
38
|
popoverAlign = "start",
|
|
33
39
|
variant = "outline",
|
|
40
|
+
captionLayout = "label",
|
|
34
41
|
}: DatePickerProps) {
|
|
35
42
|
return (
|
|
36
43
|
<div className={containerClassName}>
|
|
@@ -60,7 +67,13 @@ export function DatePicker({
|
|
|
60
67
|
align={popoverAlign}
|
|
61
68
|
aria-label={label ?? "Choose a date"}
|
|
62
69
|
>
|
|
63
|
-
<Calendar
|
|
70
|
+
<Calendar
|
|
71
|
+
mode="single"
|
|
72
|
+
selected={value}
|
|
73
|
+
defaultMonth={value}
|
|
74
|
+
onSelect={onSelect}
|
|
75
|
+
captionLayout={captionLayout}
|
|
76
|
+
/>
|
|
64
77
|
</PopoverContent>
|
|
65
78
|
</Popover>
|
|
66
79
|
</div>
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { expect, userEvent, within } from "storybook/test";
|
|
2
4
|
|
|
3
5
|
import {
|
|
4
6
|
Select,
|
|
@@ -36,6 +38,117 @@ export const Default: Story = {
|
|
|
36
38
|
),
|
|
37
39
|
};
|
|
38
40
|
|
|
41
|
+
export const SelectedValue: Story = {
|
|
42
|
+
render: () => (
|
|
43
|
+
<Select defaultValue="banana">
|
|
44
|
+
<SelectTrigger className="w-[180px]" aria-label="Selected fruit">
|
|
45
|
+
<SelectValue placeholder="Select a fruit" />
|
|
46
|
+
</SelectTrigger>
|
|
47
|
+
<SelectContent>
|
|
48
|
+
<SelectItem value="apple">Apple</SelectItem>
|
|
49
|
+
<SelectItem value="banana">Banana</SelectItem>
|
|
50
|
+
</SelectContent>
|
|
51
|
+
</Select>
|
|
52
|
+
),
|
|
53
|
+
play: async ({ canvasElement }) => {
|
|
54
|
+
await expect(within(canvasElement).getByRole("combobox", { name: "Selected fruit" })).toHaveTextContent(
|
|
55
|
+
"Banana",
|
|
56
|
+
);
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export const ExplicitItems: Story = {
|
|
61
|
+
render: () => (
|
|
62
|
+
<Select
|
|
63
|
+
defaultValue="us"
|
|
64
|
+
items={[
|
|
65
|
+
{ value: "us", label: "United States" },
|
|
66
|
+
{ value: "ca", label: "Canada" },
|
|
67
|
+
]}
|
|
68
|
+
>
|
|
69
|
+
<SelectTrigger aria-label="Country">
|
|
70
|
+
<SelectValue placeholder="Select a country" />
|
|
71
|
+
</SelectTrigger>
|
|
72
|
+
<SelectContent>
|
|
73
|
+
<SelectItem value="us">US</SelectItem>
|
|
74
|
+
<SelectItem value="ca">CA</SelectItem>
|
|
75
|
+
</SelectContent>
|
|
76
|
+
</Select>
|
|
77
|
+
),
|
|
78
|
+
play: async ({ canvasElement }) => {
|
|
79
|
+
await expect(within(canvasElement).getByRole("combobox", { name: "Country" })).toHaveTextContent(
|
|
80
|
+
"United States",
|
|
81
|
+
);
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export const NullItem: Story = {
|
|
86
|
+
render: () => (
|
|
87
|
+
<Select
|
|
88
|
+
defaultValue={null}
|
|
89
|
+
items={[{ value: null, label: "All options" }]}
|
|
90
|
+
>
|
|
91
|
+
<SelectTrigger aria-label="Filter">
|
|
92
|
+
<SelectValue placeholder="Choose a filter" />
|
|
93
|
+
</SelectTrigger>
|
|
94
|
+
<SelectContent>
|
|
95
|
+
<SelectItem>All options</SelectItem>
|
|
96
|
+
<SelectItem value="active">Active</SelectItem>
|
|
97
|
+
</SelectContent>
|
|
98
|
+
</Select>
|
|
99
|
+
),
|
|
100
|
+
play: async ({ canvasElement }) => {
|
|
101
|
+
await expect(within(canvasElement).getByRole("combobox", { name: "Filter" })).toHaveTextContent(
|
|
102
|
+
"All options",
|
|
103
|
+
);
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
function ControlledNullSelection() {
|
|
108
|
+
const [value, setValue] = React.useState<string | null>("active");
|
|
109
|
+
const [lastChange, setLastChange] = React.useState<string | null>("active");
|
|
110
|
+
|
|
111
|
+
return (
|
|
112
|
+
<div>
|
|
113
|
+
<Select
|
|
114
|
+
value={value}
|
|
115
|
+
onValueChange={(nextValue) => {
|
|
116
|
+
setValue(nextValue);
|
|
117
|
+
setLastChange(nextValue);
|
|
118
|
+
}}
|
|
119
|
+
items={[
|
|
120
|
+
{ value: null, label: "All options" },
|
|
121
|
+
{ value: "active", label: "Active" },
|
|
122
|
+
]}
|
|
123
|
+
>
|
|
124
|
+
<SelectTrigger aria-label="Controlled filter">
|
|
125
|
+
<SelectValue placeholder="Choose a filter" />
|
|
126
|
+
</SelectTrigger>
|
|
127
|
+
<SelectContent>
|
|
128
|
+
<SelectItem>All options</SelectItem>
|
|
129
|
+
<SelectItem value="active">Active</SelectItem>
|
|
130
|
+
</SelectContent>
|
|
131
|
+
</Select>
|
|
132
|
+
<output aria-label="Last selected value">{lastChange ?? "null"}</output>
|
|
133
|
+
</div>
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export const ControlledNullChange: Story = {
|
|
138
|
+
render: () => <ControlledNullSelection />,
|
|
139
|
+
parameters: { a11y: { test: "off" } },
|
|
140
|
+
play: async ({ canvasElement }) => {
|
|
141
|
+
const canvas = within(canvasElement);
|
|
142
|
+
const trigger = canvas.getByRole("combobox", { name: "Controlled filter" });
|
|
143
|
+
|
|
144
|
+
await userEvent.click(trigger);
|
|
145
|
+
await userEvent.click(await within(document.body).findByRole("option", { name: "All options" }));
|
|
146
|
+
await expect(trigger).toHaveTextContent("All options");
|
|
147
|
+
await expect(canvas.getByRole("status", { name: "Last selected value" })).toHaveTextContent("null");
|
|
148
|
+
await userEvent.keyboard("{Escape}");
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
|
|
39
152
|
export const WithGroups: Story = {
|
|
40
153
|
render: () => (
|
|
41
154
|
<Select>
|
|
@@ -70,6 +183,9 @@ export const Disabled: Story = {
|
|
|
70
183
|
</SelectContent>
|
|
71
184
|
</Select>
|
|
72
185
|
),
|
|
186
|
+
play: async ({ canvasElement }) => {
|
|
187
|
+
await expect(within(canvasElement).getByRole("combobox", { name: "Select a fruit" })).toBeDisabled();
|
|
188
|
+
},
|
|
73
189
|
};
|
|
74
190
|
|
|
75
191
|
export const Small: Story = {
|
|
@@ -6,22 +6,19 @@ import { cn } from "../../lib/utils";
|
|
|
6
6
|
|
|
7
7
|
type SelectProps = Omit<
|
|
8
8
|
React.ComponentProps<typeof SelectPrimitive.Root<string>>,
|
|
9
|
-
"
|
|
9
|
+
"value" | "defaultValue"
|
|
10
10
|
> & {
|
|
11
|
-
value?: string;
|
|
12
|
-
defaultValue?: string;
|
|
13
|
-
onValueChange?: (value: string) => void;
|
|
11
|
+
value?: string | null;
|
|
12
|
+
defaultValue?: string | null;
|
|
14
13
|
};
|
|
15
14
|
|
|
16
|
-
function Select({
|
|
15
|
+
function Select({ children, items, ...props }: SelectProps) {
|
|
16
|
+
const resolvedItems = items ?? getItemsFromChildren(children);
|
|
17
|
+
|
|
17
18
|
return (
|
|
18
|
-
<SelectPrimitive.Root
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
onValueChange={(value) => {
|
|
22
|
-
if (value !== null) onValueChange?.(value);
|
|
23
|
-
}}
|
|
24
|
-
/>
|
|
19
|
+
<SelectPrimitive.Root data-slot="select" items={resolvedItems} {...props}>
|
|
20
|
+
{children}
|
|
21
|
+
</SelectPrimitive.Root>
|
|
25
22
|
);
|
|
26
23
|
}
|
|
27
24
|
|
|
@@ -129,6 +126,33 @@ function SelectItem({
|
|
|
129
126
|
);
|
|
130
127
|
}
|
|
131
128
|
|
|
129
|
+
function getItemsFromChildren(children: React.ReactNode): Record<string, React.ReactNode> {
|
|
130
|
+
const items: Record<string, React.ReactNode> = {};
|
|
131
|
+
|
|
132
|
+
React.Children.forEach(children, (child) => {
|
|
133
|
+
if (!React.isValidElement(child)) return;
|
|
134
|
+
|
|
135
|
+
const element = child as React.ReactElement<{
|
|
136
|
+
children?: React.ReactNode;
|
|
137
|
+
value?: unknown;
|
|
138
|
+
}>;
|
|
139
|
+
|
|
140
|
+
if (element.type === SelectItem) {
|
|
141
|
+
const value = element.props.value;
|
|
142
|
+
if (typeof value === "string") {
|
|
143
|
+
items[value] = element.props.children;
|
|
144
|
+
}
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (element.props.children) {
|
|
149
|
+
Object.assign(items, getItemsFromChildren(element.props.children));
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
return items;
|
|
154
|
+
}
|
|
155
|
+
|
|
132
156
|
function SelectScrollUpButton({ className, ...props }: React.ComponentProps<"div">) {
|
|
133
157
|
return (
|
|
134
158
|
<SelectPrimitive.ScrollUpArrow
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
2
|
import * as React from "react";
|
|
3
|
+
import { userEvent, within } from "storybook/test";
|
|
3
4
|
|
|
4
5
|
import { Button } from "../primitives/button";
|
|
5
6
|
import { Navigation } from "./app-navigation";
|
|
@@ -173,3 +174,44 @@ export const WithExtraContent: Story = {
|
|
|
173
174
|
</>
|
|
174
175
|
),
|
|
175
176
|
};
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* `@internal` — undocumented Nokia 3310-style LCD skin for the mobile Sheet
|
|
180
|
+
* menu, opted into via `variant="nokia"`. Only affects the mobile Sheet, so
|
|
181
|
+
* this story forces a mobile viewport and auto-opens the menu trigger.
|
|
182
|
+
*/
|
|
183
|
+
export const Nokia: Story = {
|
|
184
|
+
parameters: {
|
|
185
|
+
viewport: { defaultViewport: "mobile1" },
|
|
186
|
+
chromatic: { viewports: [375] },
|
|
187
|
+
},
|
|
188
|
+
render: () => (
|
|
189
|
+
<>
|
|
190
|
+
<Navigation variant="nokia">
|
|
191
|
+
<Navigation.Brand>
|
|
192
|
+
<a href="/">Acme</a>
|
|
193
|
+
</Navigation.Brand>
|
|
194
|
+
|
|
195
|
+
<Navigation.List>
|
|
196
|
+
{defaultLinks.map((link) => (
|
|
197
|
+
<Navigation.Item key={link.href} href={link.href} active={link.href === "/pricing"}>
|
|
198
|
+
{link.label}
|
|
199
|
+
</Navigation.Item>
|
|
200
|
+
))}
|
|
201
|
+
</Navigation.List>
|
|
202
|
+
|
|
203
|
+
<Navigation.Action>
|
|
204
|
+
<Button size="md" asChild>
|
|
205
|
+
<a href="/get-started">Get started</a>
|
|
206
|
+
</Button>
|
|
207
|
+
</Navigation.Action>
|
|
208
|
+
</Navigation>
|
|
209
|
+
<PageContent message="Open the menu button to see the Nokia 3310-style LCD skin." />
|
|
210
|
+
</>
|
|
211
|
+
),
|
|
212
|
+
play: async ({ canvasElement }) => {
|
|
213
|
+
const canvas = within(canvasElement);
|
|
214
|
+
const trigger = canvas.getByRole("button", { name: /open menu/i });
|
|
215
|
+
await userEvent.click(trigger);
|
|
216
|
+
},
|
|
217
|
+
};
|
package/src/styles/nokia.css
CHANGED
|
@@ -3,14 +3,12 @@
|
|
|
3
3
|
* skin for the mobile Sheet menu. Not part of the public API surface (no
|
|
4
4
|
* README/Storybook entry); opt in via `<Navigation variant="nokia">`.
|
|
5
5
|
*/
|
|
6
|
-
@import url("https://fonts.googleapis.com/css2?family=VT323&display=swap");
|
|
7
|
-
|
|
8
6
|
@theme {
|
|
9
7
|
--color-nokia-screen: #c8d6a6;
|
|
10
8
|
--color-nokia-screen-deep: #b0c188;
|
|
11
9
|
--color-nokia-ink: #26331b;
|
|
12
10
|
--color-nokia-bezel: #1a1a1a;
|
|
13
|
-
--font-nokia: "
|
|
11
|
+
--font-nokia: ui-monospace, "SF Mono", "Courier New", monospace;
|
|
14
12
|
}
|
|
15
13
|
|
|
16
14
|
/* No `position: relative` here — SheetContent is `position: fixed` via its
|