@@ -14,6 +14,9 @@ import {
1414 Separator ,
1515 Box ,
1616 toast ,
17+ Popover ,
18+ PopoverTrigger ,
19+ PopoverContent ,
1720} from "@webstudio-is/design-system" ;
1821import { MinusIcon , PlusIcon } from "@webstudio-is/icons" ;
1922import { useStore } from "@nanostores/react" ;
@@ -30,8 +33,9 @@ type BreakpointEditorItemProps = {
3033
3134const BreakpointFormData = z . object ( {
3235 label : z . string ( ) ,
33- type : z . enum ( [ "minWidth" , "maxWidth" ] ) ,
34- value : z . string ( ) . transform ( Number ) ,
36+ type : z . enum ( [ "minWidth" , "maxWidth" ] ) . optional ( ) ,
37+ value : z . string ( ) . transform ( Number ) . optional ( ) ,
38+ condition : z . string ( ) . optional ( ) ,
3539} ) ;
3640
3741const useHandleChangeComplete = (
@@ -67,8 +71,16 @@ const useHandleChangeComplete = (
6771 const newBreakpoint : Breakpoint = {
6872 id : breakpoint . id ,
6973 label : parsed . data . label ,
70- [ parsed . data . type ] : parsed . data . value ,
7174 } ;
75+
76+ // If condition is set, use it exclusively
77+ if ( parsed . data . condition && parsed . data . condition . trim ( ) !== "" ) {
78+ newBreakpoint . condition = parsed . data . condition . trim ( ) ;
79+ } else if ( parsed . data . type && parsed . data . value !== undefined ) {
80+ // Otherwise use width
81+ newBreakpoint [ parsed . data . type ] = parsed . data . value ;
82+ }
83+
7284 onChangeComplete ( newBreakpoint ) ;
7385 } ;
7486 const handleChangeCompleteRef = useRef ( handleChangeComplete ) ;
@@ -89,6 +101,11 @@ const BreakpointEditorItem = ({
89101 const { formRef, handleChangeComplete, handleChange } =
90102 useHandleChangeComplete ( breakpoint , onChangeComplete ) ;
91103
104+ const [ conditionValue , setConditionValue ] = useState (
105+ breakpoint . condition ?? ""
106+ ) ;
107+ const hasCondition = conditionValue . trim ( ) !== "" ;
108+
92109 return (
93110 < Flex gap = "2" >
94111 < form
@@ -125,14 +142,16 @@ const BreakpointEditorItem = ({
125142 }
126143 defaultValue = { breakpoint . maxWidth ? "maxWidth" : "minWidth" }
127144 onChange = { handleChangeComplete }
145+ disabled = { hasCondition }
128146 />
129147 < InputField
130148 css = { { flexShrink : 1 } }
131149 defaultValue = { breakpoint . minWidth ?? breakpoint . maxWidth ?? 0 }
132150 type = "number"
133151 name = "value"
134152 min = { 0 }
135- required
153+ required = { ! hasCondition }
154+ disabled = { hasCondition }
136155 suffix = {
137156 < Text
138157 variant = "unit"
@@ -145,6 +164,18 @@ const BreakpointEditorItem = ({
145164 }
146165 />
147166 </ Flex >
167+ < InputField
168+ name = "condition"
169+ css = { { width : "100%" } }
170+ type = "text"
171+ value = { conditionValue }
172+ onChange = { ( event ) => {
173+ setConditionValue ( event . target . value ) ;
174+ handleChange ( ) ;
175+ } }
176+ placeholder = "e.g., orientation: portrait"
177+ onBlur = { handleChangeComplete }
178+ />
148179 </ Flex >
149180 </ form >
150181 < IconButton
@@ -160,22 +191,38 @@ const BreakpointEditorItem = ({
160191
161192type BreakpointsEditorProps = {
162193 onDelete : ( breakpoint : Breakpoint ) => void ;
194+ children : React . ReactNode ;
195+ open ?: boolean ;
196+ onOpenChange ?: ( open : boolean ) => void ;
163197} ;
164198
165- export const BreakpointsEditor = ( { onDelete } : BreakpointsEditorProps ) => {
199+ export const BreakpointsEditor = ( {
200+ onDelete,
201+ children,
202+ open,
203+ onOpenChange,
204+ } : BreakpointsEditorProps ) => {
166205 const breakpoints = useStore ( $breakpoints ) ;
167206 const [ addedBreakpoints , setAddedBreakpoints ] = useState < Breakpoint [ ] > ( [ ] ) ;
168207 const initialBreakpointsRef = useRef (
169208 groupBreakpoints ( Array . from ( breakpoints . values ( ) ) )
170209 ) ;
210+ const initialBreakpointsFlat = [
211+ ...initialBreakpointsRef . current . widthBased ,
212+ ...initialBreakpointsRef . current . custom ,
213+ ] ;
171214 const allBreakpoints = [
172215 ...addedBreakpoints ,
173- ...initialBreakpointsRef . current . filter (
216+ ...initialBreakpointsFlat . filter (
174217 ( breakpoint ) =>
175218 addedBreakpoints . find ( ( added ) => added . id === breakpoint . id ) ===
176219 undefined
177220 ) ,
178- ] . filter ( ( breakpoint ) => isBaseBreakpoint ( breakpoint ) === false ) ;
221+ ] . filter (
222+ ( breakpoint ) =>
223+ breakpoint . condition !== undefined ||
224+ isBaseBreakpoint ( breakpoint ) === false
225+ ) ;
179226
180227 const handleChangeComplete = ( breakpoint : Breakpoint ) => {
181228 serverSyncStore . createTransaction ( [ $breakpoints ] , ( breakpoints ) => {
@@ -184,47 +231,54 @@ export const BreakpointsEditor = ({ onDelete }: BreakpointsEditorProps) => {
184231 } ;
185232
186233 return (
187- < Flex direction = "column" >
188- < PanelTitle
189- css = { { paddingInline : theme . panel . paddingInline } }
190- suffix = {
191- < IconButton
192- onClick = { ( ) => {
193- const newBreakpoint : Breakpoint = {
194- id : nanoid ( ) ,
195- label : "" ,
196- minWidth : 0 ,
197- } ;
198- setAddedBreakpoints ( [ newBreakpoint , ...addedBreakpoints ] ) ;
199- } }
234+ < Popover open = { open } onOpenChange = { onOpenChange } modal >
235+ < PopoverTrigger asChild > { children } </ PopoverTrigger >
236+ < PopoverContent >
237+ < Flex direction = "column" >
238+ < PanelTitle
239+ css = { { paddingInline : theme . panel . paddingInline } }
240+ suffix = {
241+ < IconButton
242+ onClick = { ( ) => {
243+ const newBreakpoint : Breakpoint = {
244+ id : nanoid ( ) ,
245+ label : "" ,
246+ minWidth : 0 ,
247+ } ;
248+ setAddedBreakpoints ( [ newBreakpoint , ...addedBreakpoints ] ) ;
249+ } }
250+ >
251+ < PlusIcon />
252+ </ IconButton >
253+ }
200254 >
201- < PlusIcon />
202- </ IconButton >
203- }
204- >
205- { "Breakpoints" }
206- </ PanelTitle >
207- < Separator / >
208- < Fragment >
209- { allBreakpoints . map ( ( breakpoint , index , all ) => {
210- return (
211- < Fragment key = { breakpoint . id } >
212- < Box css = { { p : theme . panel . padding } } >
213- < BreakpointEditorItem
214- breakpoint = { breakpoint }
215- onChangeComplete = { handleChangeComplete }
216- onDelete = { onDelete }
217- autoFocus = { index === 0 }
218- />
219- </ Box >
220- { index < all . length - 1 && < PopoverSeparator /> }
221- </ Fragment >
222- ) ;
223- } ) }
224- </ Fragment >
225- { allBreakpoints . length === 0 && (
226- < Text css = { { margin : theme . spacing [ 10 ] } } > No breakpoints found </ Text >
227- ) }
228- </ Flex >
255+ { "Breakpoints" }
256+ </ PanelTitle >
257+ < Separator />
258+ < Fragment >
259+ { allBreakpoints . map ( ( breakpoint , index , all ) => {
260+ return (
261+ < Fragment key = { breakpoint . id } >
262+ < Box css = { { p : theme . panel . padding } } >
263+ < BreakpointEditorItem
264+ breakpoint = { breakpoint }
265+ onChangeComplete = { handleChangeComplete }
266+ onDelete = { onDelete }
267+ autoFocus = { index === 0 }
268+ />
269+ </ Box >
270+ { index < all . length - 1 && < PopoverSeparator /> }
271+ </ Fragment >
272+ ) ;
273+ } ) }
274+ </ Fragment >
275+ { allBreakpoints . length === 0 && (
276+ < Text css = { { margin : theme . spacing [ 10 ] } } >
277+ No breakpoints found
278+ </ Text >
279+ ) }
280+ </ Flex >
281+ </ PopoverContent >
282+ </ Popover >
229283 ) ;
230284} ;
0 commit comments