Skip to content

Commit fae6460

Browse files
committed
combobox
1 parent c083563 commit fae6460

File tree

2 files changed

+99
-6
lines changed

2 files changed

+99
-6
lines changed

apps/builder/app/builder/features/breakpoints/breakpoints-editor.tsx

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { useStore } from "@nanostores/react";
2323
import { $breakpoints } from "~/shared/sync/data-stores";
2424
import { groupBreakpoints, isBaseBreakpoint } from "~/shared/breakpoints";
2525
import { serverSyncStore } from "~/shared/sync/sync-stores";
26+
import { ConditionInput } from "./condition-input";
2627

2728
type BreakpointEditorItemProps = {
2829
breakpoint: Breakpoint;
@@ -164,16 +165,13 @@ const BreakpointEditorItem = ({
164165
}
165166
/>
166167
</Flex>
167-
<InputField
168+
<ConditionInput
168169
name="condition"
169-
css={{ width: "100%" }}
170-
type="text"
171170
value={conditionValue}
172-
onChange={(event) => {
173-
setConditionValue(event.target.value);
171+
onChange={(value) => {
172+
setConditionValue(value);
174173
handleChange();
175174
}}
176-
placeholder="e.g., orientation: portrait"
177175
onBlur={handleChangeComplete}
178176
/>
179177
</Flex>
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import { useMemo } from "react";
2+
import { Combobox } from "@webstudio-is/design-system";
3+
4+
const PREDEFINED_CONDITIONS = [
5+
{ value: "orientation:portrait", label: "Orientation: Portrait" },
6+
{ value: "orientation:landscape", label: "Orientation: Landscape" },
7+
{ value: "hover:hover", label: "Hover: Hover" },
8+
{ value: "hover:none", label: "Hover: None" },
9+
{ value: "prefers-color-scheme:dark", label: "Color Scheme: Dark" },
10+
{ value: "prefers-color-scheme:light", label: "Color Scheme: Light" },
11+
{ value: "prefers-reduced-motion:reduce", label: "Reduced Motion: Reduce" },
12+
{
13+
value: "prefers-reduced-motion:no-preference",
14+
label: "Reduced Motion: No Preference",
15+
},
16+
{ value: "pointer:coarse", label: "Pointer: Coarse" },
17+
{ value: "pointer:fine", label: "Pointer: Fine" },
18+
{ value: "pointer:none", label: "Pointer: None" },
19+
{ value: "any-hover:hover", label: "Any Hover: Hover" },
20+
{ value: "any-hover:none", label: "Any Hover: None" },
21+
{ value: "any-pointer:coarse", label: "Any Pointer: Coarse" },
22+
{ value: "any-pointer:fine", label: "Any Pointer: Fine" },
23+
{ value: "any-pointer:none", label: "Any Pointer: None" },
24+
{ value: "prefers-contrast:more", label: "Contrast: More" },
25+
{ value: "prefers-contrast:less", label: "Contrast: Less" },
26+
{ value: "prefers-contrast:no-preference", label: "Contrast: No Preference" },
27+
{ value: "display-mode:fullscreen", label: "Display Mode: Fullscreen" },
28+
{ value: "display-mode:standalone", label: "Display Mode: Standalone" },
29+
{ value: "display-mode:minimal-ui", label: "Display Mode: Minimal UI" },
30+
{ value: "display-mode:browser", label: "Display Mode: Browser" },
31+
];
32+
33+
type Condition = { value: string; label: string };
34+
35+
type ConditionInputProps = {
36+
name?: string;
37+
value: string;
38+
onChange: (value: string) => void;
39+
onBlur?: () => void;
40+
placeholder?: string;
41+
};
42+
43+
export const ConditionInput = ({
44+
name,
45+
value,
46+
onChange,
47+
onBlur,
48+
placeholder = "e.g., orientation:portrait",
49+
}: ConditionInputProps) => {
50+
// Find the matching condition item or create a custom one
51+
const selectedItem: Condition | null = useMemo(() => {
52+
const found = PREDEFINED_CONDITIONS.find((c) => c.value === value);
53+
if (found) {
54+
return found;
55+
}
56+
// If value doesn't match any predefined condition, create a custom item
57+
if (value) {
58+
return { value, label: value };
59+
}
60+
return null;
61+
}, [value]);
62+
63+
return (
64+
<Combobox<Condition>
65+
value={selectedItem}
66+
itemToString={(item) => item?.value ?? ""}
67+
getItems={() => PREDEFINED_CONDITIONS}
68+
match={(search, items, itemToString) => {
69+
if (!search) {
70+
return items;
71+
}
72+
const searchLower = search.toLowerCase();
73+
return items.filter(
74+
(item) =>
75+
item.label.toLowerCase().includes(searchLower) ||
76+
itemToString(item).toLowerCase().includes(searchLower)
77+
);
78+
}}
79+
getItemProps={(item) => ({
80+
children: item.label,
81+
})}
82+
onItemSelect={(item) => {
83+
if (item) {
84+
onChange(item.value);
85+
}
86+
}}
87+
onChange={(value) => {
88+
onChange(value ?? "");
89+
}}
90+
name={name}
91+
onBlur={onBlur}
92+
placeholder={placeholder}
93+
/>
94+
);
95+
};

0 commit comments

Comments
 (0)