@flamingo-stack/openframe-frontend-core 0.0.490-snapshot.20260728181420 → 0.0.490
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/dist/{chunk-VD4QDSP7.cjs → chunk-2MKUHJ3O.cjs} +40 -40
- package/dist/{chunk-VD4QDSP7.cjs.map → chunk-2MKUHJ3O.cjs.map} +1 -1
- package/dist/{chunk-EHHX22JL.cjs → chunk-3IEVA5AO.cjs} +93 -93
- package/dist/{chunk-EHHX22JL.cjs.map → chunk-3IEVA5AO.cjs.map} +1 -1
- package/dist/{chunk-IUGJAILN.js → chunk-6CFFISUC.js} +3 -3
- package/dist/{chunk-CZTKQVIQ.js → chunk-6CJ2K7BD.js} +3 -3
- package/dist/{chunk-ZEC5U5T7.js → chunk-6QK3ONDD.js} +2 -2
- package/dist/{chunk-GL33BHGZ.cjs → chunk-7I42V2OD.cjs} +73 -73
- package/dist/{chunk-GL33BHGZ.cjs.map → chunk-7I42V2OD.cjs.map} +1 -1
- package/dist/{chunk-GTMW4KY7.js → chunk-AIRNG27G.js} +6 -6
- package/dist/{chunk-ITV6LSG7.cjs → chunk-AW63ET2A.cjs} +19 -19
- package/dist/{chunk-ITV6LSG7.cjs.map → chunk-AW63ET2A.cjs.map} +1 -1
- package/dist/{chunk-TTYJAZ7P.cjs → chunk-BIKOJ4FH.cjs} +32 -58
- package/dist/chunk-BIKOJ4FH.cjs.map +1 -0
- package/dist/{chunk-35CTCUJ7.js → chunk-BZMOEWWS.js} +6 -6
- package/dist/{chunk-QNFAZIFC.cjs → chunk-D5LQMRPU.cjs} +5 -5
- package/dist/{chunk-QNFAZIFC.cjs.map → chunk-D5LQMRPU.cjs.map} +1 -1
- package/dist/{chunk-T4BUS6JV.js → chunk-EGXCWZYP.js} +3 -3
- package/dist/{chunk-HRMOCJKU.cjs → chunk-EJQUYZXF.cjs} +12 -12
- package/dist/{chunk-HRMOCJKU.cjs.map → chunk-EJQUYZXF.cjs.map} +1 -1
- package/dist/{chunk-J2V4B6FF.cjs → chunk-FY5XAW3H.cjs} +18 -18
- package/dist/{chunk-J2V4B6FF.cjs.map → chunk-FY5XAW3H.cjs.map} +1 -1
- package/dist/{chunk-YKE2KZ2Y.cjs → chunk-GMPYR3MX.cjs} +33 -33
- package/dist/{chunk-YKE2KZ2Y.cjs.map → chunk-GMPYR3MX.cjs.map} +1 -1
- package/dist/{chunk-6O3YTVLG.cjs → chunk-GUTYUMGV.cjs} +12 -12
- package/dist/{chunk-6O3YTVLG.cjs.map → chunk-GUTYUMGV.cjs.map} +1 -1
- package/dist/{chunk-QKWOBV55.js → chunk-GZK7FWJJ.js} +12 -12
- package/dist/chunk-GZK7FWJJ.js.map +1 -0
- package/dist/{chunk-7NRRONK5.cjs → chunk-IEZ3YBAP.cjs} +32 -32
- package/dist/{chunk-7NRRONK5.cjs.map → chunk-IEZ3YBAP.cjs.map} +1 -1
- package/dist/{chunk-THC3K262.cjs → chunk-KP5KRRD6.cjs} +3 -3
- package/dist/{chunk-THC3K262.cjs.map → chunk-KP5KRRD6.cjs.map} +1 -1
- package/dist/{chunk-U2JIGUXF.js → chunk-MPYDX5WX.js} +4 -4
- package/dist/{chunk-ZDUJ6BDI.js → chunk-NGEFYPY7.js} +4 -4
- package/dist/{chunk-CYIUPI7J.js → chunk-OQIMH2WQ.js} +1004 -1167
- package/dist/{chunk-CYIUPI7J.js.map → chunk-OQIMH2WQ.js.map} +1 -1
- package/dist/{chunk-WEUNPE3S.js → chunk-PB3BTZDU.js} +113 -139
- package/dist/chunk-PB3BTZDU.js.map +1 -0
- package/dist/{chunk-6WTCJI5R.js → chunk-RW2LPL72.js} +2 -2
- package/dist/{chunk-SGL6URIJ.cjs → chunk-STYA77PH.cjs} +12 -12
- package/dist/chunk-STYA77PH.cjs.map +1 -0
- package/dist/{chunk-PVSXTCJY.cjs → chunk-UZZT65PQ.cjs} +40 -40
- package/dist/{chunk-PVSXTCJY.cjs.map → chunk-UZZT65PQ.cjs.map} +1 -1
- package/dist/{chunk-2YQK6LHR.js → chunk-VDZO6IDR.js} +5 -5
- package/dist/{chunk-QF54RPHZ.js → chunk-VL2YMDPH.js} +6 -6
- package/dist/{chunk-2RTNC3KP.cjs → chunk-VUXNXWOQ.cjs} +873 -1036
- package/dist/chunk-VUXNXWOQ.cjs.map +1 -0
- package/dist/{chunk-6XNWT2KC.js → chunk-WLM3F3WU.js} +3 -3
- package/dist/components/case-studies/index.cjs +10 -10
- package/dist/components/case-studies/index.js +4 -4
- package/dist/components/chat/index.cjs +4 -4
- package/dist/components/chat/index.js +3 -3
- package/dist/components/contact/index.cjs +5 -5
- package/dist/components/contact/index.js +4 -4
- package/dist/components/docs/index.cjs +7 -7
- package/dist/components/docs/index.js +6 -6
- package/dist/components/embeds/index.cjs +5 -5
- package/dist/components/embeds/index.js +4 -4
- package/dist/components/faq/index.cjs +6 -6
- package/dist/components/faq/index.js +5 -5
- package/dist/components/features/index.cjs +4 -4
- package/dist/components/features/index.js +3 -3
- package/dist/components/features/select-button.d.ts.map +1 -1
- package/dist/components/help-center-pages/index.cjs +23 -23
- package/dist/components/help-center-pages/index.js +14 -14
- package/dist/components/index.cjs +161 -161
- package/dist/components/index.js +12 -12
- package/dist/components/layout/page-layout.d.ts +1 -7
- package/dist/components/layout/page-layout.d.ts.map +1 -1
- package/dist/components/layout/title-block.d.ts +1 -23
- package/dist/components/layout/title-block.d.ts.map +1 -1
- package/dist/components/navigation/index.cjs +4 -4
- package/dist/components/navigation/index.js +3 -3
- package/dist/components/onboarding-guides/index.cjs +8 -8
- package/dist/components/onboarding-guides/index.js +7 -7
- package/dist/components/related-content/index.cjs +6 -6
- package/dist/components/related-content/index.js +5 -5
- package/dist/components/tickets/index.cjs +7 -7
- package/dist/components/tickets/index.js +6 -6
- package/dist/components/ui/autocomplete.d.ts.map +1 -1
- package/dist/components/ui/checkbox-block.d.ts.map +1 -1
- package/dist/components/ui/date-picker.d.ts.map +1 -1
- package/dist/components/ui/field-wrapper.d.ts +1 -10
- package/dist/components/ui/field-wrapper.d.ts.map +1 -1
- package/dist/components/ui/file-manager/index.cjs +50 -50
- package/dist/components/ui/file-manager/index.js +1 -1
- package/dist/components/ui/floating-tooltip.d.ts +1 -8
- package/dist/components/ui/floating-tooltip.d.ts.map +1 -1
- package/dist/components/ui/index.cjs +4 -4
- package/dist/components/ui/index.js +3 -3
- package/dist/components/ui/input-trigger.d.ts.map +1 -1
- package/dist/components/ui/input.d.ts.map +1 -1
- package/dist/components/ui/page-actions.d.ts +1 -7
- package/dist/components/ui/page-actions.d.ts.map +1 -1
- package/dist/components/ui/radio-group.d.ts.map +1 -1
- package/dist/components/ui/search-input.d.ts.map +1 -1
- package/dist/components/ui/select.d.ts.map +1 -1
- package/dist/components/ui/tab-navigation.d.ts +1 -10
- package/dist/components/ui/tab-navigation.d.ts.map +1 -1
- package/dist/components/ui/tag-search-input.d.ts.map +1 -1
- package/dist/components/ui/textarea.d.ts.map +1 -1
- package/dist/components/ui/truncate-text.d.ts +2 -2
- package/dist/components/ui/truncate-text.d.ts.map +1 -1
- package/dist/hooks/index.cjs +2 -4
- package/dist/hooks/index.cjs.map +1 -1
- package/dist/hooks/index.js +1 -3
- package/dist/hooks/ui/index.d.ts +0 -1
- package/dist/hooks/ui/index.d.ts.map +1 -1
- package/dist/index.cjs +4 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +3 -5
- package/package.json +1 -1
- package/src/components/features/select-button.tsx +1 -4
- package/src/components/layout/page-layout.tsx +0 -16
- package/src/components/layout/title-block.tsx +7 -106
- package/src/components/ui/.field-wrapper.md +4 -5
- package/src/components/ui/.tab-navigation.md +1 -14
- package/src/components/ui/autocomplete.tsx +1 -13
- package/src/components/ui/button/button.tsx +1 -1
- package/src/components/ui/checkbox-block.tsx +3 -27
- package/src/components/ui/date-picker.tsx +1 -15
- package/src/components/ui/field-wrapper.tsx +6 -12
- package/src/components/ui/floating-tooltip.tsx +1 -9
- package/src/components/ui/input-trigger.tsx +1 -8
- package/src/components/ui/input.tsx +1 -8
- package/src/components/ui/page-actions.tsx +30 -142
- package/src/components/ui/radio-group.tsx +3 -28
- package/src/components/ui/search-input.tsx +1 -2
- package/src/components/ui/select.tsx +0 -7
- package/src/components/ui/tab-navigation.tsx +4 -26
- package/src/components/ui/tag-search-input.tsx +2 -9
- package/src/components/ui/textarea.tsx +1 -8
- package/src/components/ui/truncate-text.tsx +18 -3
- package/src/hooks/ui/index.ts +0 -1
- package/dist/chunk-2RTNC3KP.cjs.map +0 -1
- package/dist/chunk-QKWOBV55.js.map +0 -1
- package/dist/chunk-SGL6URIJ.cjs.map +0 -1
- package/dist/chunk-TTYJAZ7P.cjs.map +0 -1
- package/dist/chunk-WEUNPE3S.js.map +0 -1
- package/dist/hooks/ui/use-is-truncated.d.ts +0 -33
- package/dist/hooks/ui/use-is-truncated.d.ts.map +0 -1
- package/src/hooks/ui/use-is-truncated.ts +0 -66
- /package/dist/{chunk-IUGJAILN.js.map → chunk-6CFFISUC.js.map} +0 -0
- /package/dist/{chunk-CZTKQVIQ.js.map → chunk-6CJ2K7BD.js.map} +0 -0
- /package/dist/{chunk-ZEC5U5T7.js.map → chunk-6QK3ONDD.js.map} +0 -0
- /package/dist/{chunk-GTMW4KY7.js.map → chunk-AIRNG27G.js.map} +0 -0
- /package/dist/{chunk-35CTCUJ7.js.map → chunk-BZMOEWWS.js.map} +0 -0
- /package/dist/{chunk-T4BUS6JV.js.map → chunk-EGXCWZYP.js.map} +0 -0
- /package/dist/{chunk-U2JIGUXF.js.map → chunk-MPYDX5WX.js.map} +0 -0
- /package/dist/{chunk-ZDUJ6BDI.js.map → chunk-NGEFYPY7.js.map} +0 -0
- /package/dist/{chunk-6WTCJI5R.js.map → chunk-RW2LPL72.js.map} +0 -0
- /package/dist/{chunk-2YQK6LHR.js.map → chunk-VDZO6IDR.js.map} +0 -0
- /package/dist/{chunk-QF54RPHZ.js.map → chunk-VL2YMDPH.js.map} +0 -0
- /package/dist/{chunk-6XNWT2KC.js.map → chunk-WLM3F3WU.js.map} +0 -0
|
@@ -26,13 +26,6 @@ interface FloatingTooltipProps {
|
|
|
26
26
|
delayDuration?: number
|
|
27
27
|
/** Disable the tooltip without unmounting the trigger wrapper. */
|
|
28
28
|
disabled?: boolean
|
|
29
|
-
/**
|
|
30
|
-
* Classes for the trigger wrapper — the `div` this component puts around
|
|
31
|
-
* `children` to anchor the tooltip. That div becomes the flex/grid item in the
|
|
32
|
-
* caller's layout, so anything the child needed there (`min-w-0`, `flex-1`)
|
|
33
|
-
* must move onto it. Omit it and the wrapper carries no class, exactly as before.
|
|
34
|
-
*/
|
|
35
|
-
triggerClassName?: string
|
|
36
29
|
}
|
|
37
30
|
|
|
38
31
|
// Parse colored text markup like [YELLOW]text[/YELLOW] into JSX
|
|
@@ -92,7 +85,6 @@ export function FloatingTooltip({
|
|
|
92
85
|
className,
|
|
93
86
|
delayDuration = 0,
|
|
94
87
|
disabled = false,
|
|
95
|
-
triggerClassName,
|
|
96
88
|
}: FloatingTooltipProps) {
|
|
97
89
|
const [isOpen, setIsOpen] = React.useState(false)
|
|
98
90
|
const arrowRef = React.useRef<HTMLDivElement>(null)
|
|
@@ -159,7 +151,7 @@ export function FloatingTooltip({
|
|
|
159
151
|
|
|
160
152
|
return (
|
|
161
153
|
<>
|
|
162
|
-
<div ref={refs.setReference}
|
|
154
|
+
<div ref={refs.setReference} {...getReferenceProps()}>
|
|
163
155
|
{children}
|
|
164
156
|
</div>
|
|
165
157
|
<FloatingPortal>
|
|
@@ -52,14 +52,7 @@ export const InputTrigger = React.forwardRef<HTMLButtonElement, InputTriggerProp
|
|
|
52
52
|
"data-[state=open]:border-ods-accent data-[state=open]:hover:border-ods-accent",
|
|
53
53
|
invalid &&
|
|
54
54
|
"border-ods-error enabled:hover:border-ods-error data-[state=open]:border-ods-error",
|
|
55
|
-
|
|
56
|
-
// whole control (a blanket opacity also washes out the border and the
|
|
57
|
-
// adornment icons, which no other field does). The child rule catches
|
|
58
|
-
// the label span, which sets its own placeholder colour. Scoped to
|
|
59
|
-
// DIRECT children: `selectedLabel` is a ReactNode the caller owns, so
|
|
60
|
-
// anything inside it that sets its own colour keeps it.
|
|
61
|
-
"disabled:cursor-not-allowed disabled:bg-ods-bg",
|
|
62
|
-
"disabled:text-ods-text-disabled disabled:[&>span]:text-ods-text-disabled disabled:[&_svg]:text-ods-text-disabled",
|
|
55
|
+
"disabled:cursor-not-allowed disabled:opacity-50 disabled:bg-ods-bg",
|
|
63
56
|
"transition-colors duration-200",
|
|
64
57
|
className,
|
|
65
58
|
)}
|
|
@@ -83,15 +83,8 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|
|
83
83
|
"bg-ods-card border-ods-border has-[:focus]:border-ods-accent",
|
|
84
84
|
// Hover & active (not disabled)
|
|
85
85
|
!props.disabled && "hover:bg-ods-bg-hover hover:border-ods-border-hover active:bg-ods-bg-active active:border-ods-border-active",
|
|
86
|
-
// Disabled
|
|
87
|
-
// `text-ods-text-secondary`, so grey them from here — a disabled field
|
|
88
|
-
// must read as one flat colour, not grey text next to a live icon.
|
|
89
|
-
// Scoped to the DIRECT span children (the two adornment wrappers): a
|
|
90
|
-
// descendant rule would also repaint whatever the caller renders INSIDE
|
|
91
|
-
// an adornment, which is passed verbatim precisely so it can own its
|
|
92
|
-
// colour (a `Tag` there went invisible against its own fill).
|
|
86
|
+
// Disabled
|
|
93
87
|
props.disabled && "!cursor-not-allowed bg-ods-bg",
|
|
94
|
-
"has-[:disabled]:[&>span]:text-ods-text-disabled has-[:disabled]:[&_svg]:text-ods-text-disabled",
|
|
95
88
|
// Invalid
|
|
96
89
|
isInvalid && invalidBorderClasses[variantIsInvalid ? (errorVariant as "error" | "warning") : "error"],
|
|
97
90
|
className
|
|
@@ -6,7 +6,6 @@ import { Chevron02DownIcon } from '../icons-v2-generated'
|
|
|
6
6
|
import { ActionsMenuDropdown, type ActionsMenuGroup, type ActionsMenuItem } from './actions-menu'
|
|
7
7
|
import type { ButtonProps, SplitButtonIconAction } from './button'
|
|
8
8
|
import { Button, SplitButton } from './button'
|
|
9
|
-
import { Skeleton } from './skeleton'
|
|
10
9
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from './tooltip'
|
|
11
10
|
|
|
12
11
|
export type PageActionButton = {
|
|
@@ -207,104 +206,26 @@ export interface PageActionsProps {
|
|
|
207
206
|
*/
|
|
208
207
|
selector?: React.ReactNode
|
|
209
208
|
className?: string
|
|
210
|
-
/**
|
|
211
|
-
* Render placeholders instead of the actions — for pages whose action SET
|
|
212
|
-
* depends on data still in flight. Opt-in: a page whose actions are already
|
|
213
|
-
* final while its title loads keeps rendering them.
|
|
214
|
-
*/
|
|
215
|
-
loading?: boolean
|
|
216
209
|
}
|
|
217
210
|
|
|
218
211
|
const ACTIONS_GAP = 'gap-[var(--spacing-system-xs)]'
|
|
219
212
|
|
|
220
|
-
/** Matches `Button` size="icon" / size="default" exactly, so a placeholder and the
|
|
221
|
-
* button that replaces it occupy the same box at both breakpoints. `rounded-md`
|
|
222
|
-
* is the same token `Button` itself uses — a raw `6px` would agree today and
|
|
223
|
-
* drift the moment a platform overrides `--radius`. */
|
|
224
|
-
const SKELETON_HEIGHT = 'h-11 md:h-12'
|
|
225
|
-
const SKELETON_ICON_WIDTH = 'w-11 md:w-12'
|
|
226
|
-
const SKELETON_LABEL_WIDTH = 'w-[120px] md:w-[140px]'
|
|
227
|
-
const SKELETON_RADIUS = 'rounded-md'
|
|
228
|
-
|
|
229
|
-
/**
|
|
230
|
-
* Placeholders for actions whose SHAPE isn't known yet — the page is still
|
|
231
|
-
* loading the record that decides which actions exist (e.g. an archived entity
|
|
232
|
-
* offers "Unarchive" where an active one offers "Edit").
|
|
233
|
-
*
|
|
234
|
-
* Why not render the actions anyway: a button is a promise. Painting the
|
|
235
|
-
* optimistic set means a flash of the wrong header AND a live click target that
|
|
236
|
-
* can act on the wrong assumption before the data lands. A grey box promises
|
|
237
|
-
* nothing and can't be clicked.
|
|
238
|
-
*
|
|
239
|
-
* The placeholders are derived from the actions the caller passed — one per
|
|
240
|
-
* action, each sized like the button that action WILL render — so the common
|
|
241
|
-
* case (the data confirms that set) settles with no layout shift at all. Sizing
|
|
242
|
-
* per action matters: `renderRawActionButton` collapses a label-less action (or
|
|
243
|
-
* one flagged `iconOnlyOnDesktop`) to a square `size="icon"` button, and a
|
|
244
|
-
* 140px-wide placeholder snapping to 48px is exactly the shift this is meant to
|
|
245
|
-
* avoid.
|
|
246
|
-
*
|
|
247
|
-
* An empty list still yields one placeholder: `loading` means the action set is
|
|
248
|
-
* unknown, not known-empty. Its shape comes from `emptyFallback`, because what
|
|
249
|
-
* an empty header settles into differs per variant — `icon-buttons` and
|
|
250
|
-
* `menu-primary` fall back to the square "…" overflow trigger, `primary-buttons`
|
|
251
|
-
* has no overflow trigger at all and settles into a labelled button.
|
|
252
|
-
*/
|
|
253
|
-
function ActionSkeletons({
|
|
254
|
-
actions,
|
|
255
|
-
fullWidth,
|
|
256
|
-
emptyFallback = 'icon',
|
|
257
|
-
}: {
|
|
258
|
-
actions: PageActionButton[]
|
|
259
|
-
fullWidth?: boolean
|
|
260
|
-
emptyFallback?: 'icon' | 'label'
|
|
261
|
-
}) {
|
|
262
|
-
const items = actions.length > 0 ? actions : [null]
|
|
263
|
-
return (
|
|
264
|
-
<>
|
|
265
|
-
{items.map((action, idx) => {
|
|
266
|
-
// Mirrors `renderRawActionButton`'s `isIconOnly`. The mobile bottom bar
|
|
267
|
-
// stretches only labelled buttons (`fullWidth: !!action.label`), so an
|
|
268
|
-
// icon action keeps its square box there too.
|
|
269
|
-
const isIconOnly = action ? !action.label || !!action.iconOnlyOnDesktop : emptyFallback === 'icon'
|
|
270
|
-
return (
|
|
271
|
-
<Skeleton
|
|
272
|
-
// Static placeholder list — index IS the identity here.
|
|
273
|
-
key={`action-skeleton-${idx}`}
|
|
274
|
-
className={cn(
|
|
275
|
-
SKELETON_HEIGHT,
|
|
276
|
-
SKELETON_RADIUS,
|
|
277
|
-
isIconOnly ? SKELETON_ICON_WIDTH : fullWidth ? 'flex-1' : SKELETON_LABEL_WIDTH,
|
|
278
|
-
)}
|
|
279
|
-
/>
|
|
280
|
-
)
|
|
281
|
-
})}
|
|
282
|
-
</>
|
|
283
|
-
)
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
/** Mobile placeholder for the variants that collapse to ONE trigger (icon button or "…"). */
|
|
287
|
-
function MobileTriggerSkeleton() {
|
|
288
|
-
return <Skeleton className={cn(SKELETON_HEIGHT, SKELETON_ICON_WIDTH, SKELETON_RADIUS)} />
|
|
289
|
-
}
|
|
290
|
-
|
|
291
213
|
export function PageActions({
|
|
292
214
|
variant = 'icon-buttons',
|
|
293
215
|
actions,
|
|
294
216
|
menuActions,
|
|
295
217
|
selector,
|
|
296
218
|
className,
|
|
297
|
-
loading,
|
|
298
219
|
}: PageActionsProps) {
|
|
299
220
|
if (variant === 'icon-buttons') {
|
|
300
|
-
return <IconButtonsVariant actions={actions} menuActions={menuActions} selector={selector} className={className}
|
|
221
|
+
return <IconButtonsVariant actions={actions} menuActions={menuActions} selector={selector} className={className} />
|
|
301
222
|
}
|
|
302
223
|
|
|
303
224
|
if (variant === 'menu-primary') {
|
|
304
|
-
return <MenuPrimaryVariant actions={actions} menuActions={menuActions || []} selector={selector} className={className}
|
|
225
|
+
return <MenuPrimaryVariant actions={actions} menuActions={menuActions || []} selector={selector} className={className} />
|
|
305
226
|
}
|
|
306
227
|
|
|
307
|
-
return <PrimaryButtonsVariant actions={actions} className={className}
|
|
228
|
+
return <PrimaryButtonsVariant actions={actions} className={className} />
|
|
308
229
|
}
|
|
309
230
|
|
|
310
231
|
function IconButtonsVariant({
|
|
@@ -312,13 +233,11 @@ function IconButtonsVariant({
|
|
|
312
233
|
menuActions,
|
|
313
234
|
selector,
|
|
314
235
|
className,
|
|
315
|
-
loading,
|
|
316
236
|
}: {
|
|
317
237
|
actions: PageActionButton[]
|
|
318
238
|
menuActions?: ActionsMenuGroup[]
|
|
319
239
|
selector?: React.ReactNode
|
|
320
240
|
className?: string
|
|
321
|
-
loading?: boolean
|
|
322
241
|
}) {
|
|
323
242
|
const desktopActions = actions.filter(a => !a.showOnlyMobile)
|
|
324
243
|
const hasMenuActions = !!menuActions && menuActions.some(g => g.items.length > 0)
|
|
@@ -338,25 +257,17 @@ function IconButtonsVariant({
|
|
|
338
257
|
{/* Desktop: every action as an icon button + optional overflow menu */}
|
|
339
258
|
<div className={cn('hidden md:flex items-center', ACTIONS_GAP, className)}>
|
|
340
259
|
{selector}
|
|
341
|
-
{
|
|
342
|
-
<
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
{renderActionButton(action)}
|
|
348
|
-
</React.Fragment>
|
|
349
|
-
))}
|
|
350
|
-
{hasMenuActions && <ActionsMenuDropdown groups={menuActions} />}
|
|
351
|
-
</>
|
|
352
|
-
)}
|
|
260
|
+
{desktopActions.map((action, idx) => (
|
|
261
|
+
<React.Fragment key={actionKey(action, idx)}>
|
|
262
|
+
{renderActionButton(action)}
|
|
263
|
+
</React.Fragment>
|
|
264
|
+
))}
|
|
265
|
+
{hasMenuActions && <ActionsMenuDropdown groups={menuActions} />}
|
|
353
266
|
</div>
|
|
354
267
|
|
|
355
268
|
{/* Mobile: single icon button OR all actions merged into one "..." menu */}
|
|
356
269
|
<div className={cn('flex md:hidden', className)}>
|
|
357
|
-
{
|
|
358
|
-
<MobileTriggerSkeleton />
|
|
359
|
-
) : useSingleActionMobile && singleAction ? (
|
|
270
|
+
{useSingleActionMobile && singleAction ? (
|
|
360
271
|
renderActionButton(singleAction, { iconOnly: true })
|
|
361
272
|
) : hasMobileMenuItems ? (
|
|
362
273
|
<ActionsMenuDropdown groups={mobileMenuGroups} />
|
|
@@ -373,11 +284,9 @@ function IconButtonsVariant({
|
|
|
373
284
|
function PrimaryButtonsVariant({
|
|
374
285
|
actions,
|
|
375
286
|
className,
|
|
376
|
-
loading,
|
|
377
287
|
}: {
|
|
378
288
|
actions: PageActionButton[]
|
|
379
289
|
className?: string
|
|
380
|
-
loading?: boolean
|
|
381
290
|
}) {
|
|
382
291
|
// Sort: outline first, accent last (rightmost on desktop).
|
|
383
292
|
const sortedActions = [...actions].sort((a, b) => {
|
|
@@ -391,20 +300,14 @@ function PrimaryButtonsVariant({
|
|
|
391
300
|
return (
|
|
392
301
|
<>
|
|
393
302
|
<div className={cn('hidden md:flex items-center', ACTIONS_GAP, className)}>
|
|
394
|
-
{
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
)
|
|
399
|
-
desktopActions.map((action, idx) => (
|
|
400
|
-
<React.Fragment key={`desktop-${actionKey(action, idx)}`}>
|
|
401
|
-
{renderActionButton(action)}
|
|
402
|
-
</React.Fragment>
|
|
403
|
-
))
|
|
404
|
-
)}
|
|
303
|
+
{desktopActions.map((action, idx) => (
|
|
304
|
+
<React.Fragment key={`desktop-${actionKey(action, idx)}`}>
|
|
305
|
+
{renderActionButton(action)}
|
|
306
|
+
</React.Fragment>
|
|
307
|
+
))}
|
|
405
308
|
</div>
|
|
406
309
|
|
|
407
|
-
<MobileBottomActions actions={sortedActions}
|
|
310
|
+
<MobileBottomActions actions={sortedActions} />
|
|
408
311
|
</>
|
|
409
312
|
)
|
|
410
313
|
}
|
|
@@ -418,13 +321,11 @@ function MenuPrimaryVariant({
|
|
|
418
321
|
menuActions,
|
|
419
322
|
selector,
|
|
420
323
|
className,
|
|
421
|
-
loading,
|
|
422
324
|
}: {
|
|
423
325
|
actions: PageActionButton[]
|
|
424
326
|
menuActions: ActionsMenuGroup[]
|
|
425
327
|
selector?: React.ReactNode
|
|
426
328
|
className?: string
|
|
427
|
-
loading?: boolean
|
|
428
329
|
}) {
|
|
429
330
|
const desktopActions = actions.filter(a => !a.showOnlyMobile)
|
|
430
331
|
const hasMenuActions = menuActions.some(g => g.items.length > 0)
|
|
@@ -439,47 +340,34 @@ function MenuPrimaryVariant({
|
|
|
439
340
|
<>
|
|
440
341
|
<div className={cn('hidden md:flex items-center', ACTIONS_GAP, className)}>
|
|
441
342
|
{selector}
|
|
442
|
-
{
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
<React.Fragment key={`desktop-${actionKey(action, idx)}`}>
|
|
449
|
-
{renderActionButton({ ...action, variant: action.variant || 'accent' })}
|
|
450
|
-
</React.Fragment>
|
|
451
|
-
))}
|
|
452
|
-
</>
|
|
453
|
-
)}
|
|
343
|
+
{hasMenuActions && <ActionsMenuDropdown groups={menuActions} />}
|
|
344
|
+
{desktopActions.map((action, idx) => (
|
|
345
|
+
<React.Fragment key={`desktop-${actionKey(action, idx)}`}>
|
|
346
|
+
{renderActionButton({ ...action, variant: action.variant || 'accent' })}
|
|
347
|
+
</React.Fragment>
|
|
348
|
+
))}
|
|
454
349
|
</div>
|
|
455
350
|
|
|
456
351
|
<div className={cn('flex md:hidden', className)}>
|
|
457
|
-
{
|
|
352
|
+
{hasMobileMenuItems ? <ActionsMenuDropdown groups={mobileMenuGroups} /> : null}
|
|
458
353
|
</div>
|
|
459
354
|
</>
|
|
460
355
|
)
|
|
461
356
|
}
|
|
462
357
|
|
|
463
|
-
function MobileBottomActions({ actions
|
|
358
|
+
function MobileBottomActions({ actions }: { actions: PageActionButton[] }) {
|
|
464
359
|
return (
|
|
465
360
|
<div className={cn(
|
|
466
361
|
'fixed md:hidden bottom-0 left-0 right-0 z-50',
|
|
467
362
|
'bg-ods-card border-t border-ods-border',
|
|
468
|
-
|
|
469
|
-
// only value it ever uses is the MOBILE one, and `l` is 16px there. The
|
|
470
|
-
// fixed variant is what keeps the 24px the bar has always had.
|
|
471
|
-
'flex items-start p-[var(--spacing-system-lf)]',
|
|
363
|
+
'flex items-start pt-6 pb-6 px-6',
|
|
472
364
|
ACTIONS_GAP,
|
|
473
365
|
)}>
|
|
474
|
-
{
|
|
475
|
-
<
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
{renderActionButton(action, { fullWidth: !!action.label })}
|
|
480
|
-
</React.Fragment>
|
|
481
|
-
))
|
|
482
|
-
)}
|
|
366
|
+
{actions.map((action, idx) => (
|
|
367
|
+
<React.Fragment key={`mobile-${actionKey(action, idx)}`}>
|
|
368
|
+
{renderActionButton(action, { fullWidth: !!action.label })}
|
|
369
|
+
</React.Fragment>
|
|
370
|
+
))}
|
|
483
371
|
</div>
|
|
484
372
|
)
|
|
485
373
|
}
|
|
@@ -81,10 +81,6 @@ const RadioGroupBlock = React.forwardRef<
|
|
|
81
81
|
<div className={cn("relative flex w-full flex-col", className)}>
|
|
82
82
|
<RadioGroupPrimitive.Root
|
|
83
83
|
ref={ref}
|
|
84
|
-
// Same marker every field exposes — it is how a form finds (and
|
|
85
|
-
// scrolls to) the first control that failed validation. The GROUP is
|
|
86
|
-
// the field here; the individual options are not separately invalid.
|
|
87
|
-
data-invalid={error ? true : undefined}
|
|
88
84
|
className={cn(
|
|
89
85
|
"w-full",
|
|
90
86
|
isGrouped
|
|
@@ -110,10 +106,6 @@ const RadioGroupBlock = React.forwardRef<
|
|
|
110
106
|
"flex w-full items-center gap-3",
|
|
111
107
|
"transition-colors duration-200",
|
|
112
108
|
isDisabled ? "cursor-not-allowed" : "cursor-pointer",
|
|
113
|
-
// Hover / active move the BACKGROUND only, like Input and
|
|
114
|
-
// CheckboxBlock — the border stays put so an unselected row
|
|
115
|
-
// never flashes "selected" under the cursor.
|
|
116
|
-
!isDisabled && "hover:bg-ods-bg-hover active:bg-ods-bg-active",
|
|
117
109
|
isGrouped
|
|
118
110
|
? cn(
|
|
119
111
|
"bg-ods-card px-[var(--spacing-system-sf)] py-[var(--spacing-system-xs,8px)]",
|
|
@@ -122,17 +114,12 @@ const RadioGroupBlock = React.forwardRef<
|
|
|
122
114
|
)
|
|
123
115
|
: cn(
|
|
124
116
|
"rounded-[6px] border bg-ods-card p-[var(--spacing-system-sf)]",
|
|
117
|
+
!isDisabled && "hover:border-ods-accent/30",
|
|
125
118
|
!isDisabled &&
|
|
126
119
|
"has-[[data-state=checked]]:bg-[var(--ods-system-greys-soft-grey)]/30 has-[[data-state=checked]]:border-ods-accent/40",
|
|
127
120
|
error ? "border-ods-error" : "border-ods-border"
|
|
128
121
|
),
|
|
129
|
-
|
|
130
|
-
// greyed text, not a fade of the whole row. The greying lives
|
|
131
|
-
// on the TEXT COLUMN below, not here: as a descendant rule on
|
|
132
|
-
// the row it also repainted whatever `trailing` renders — a
|
|
133
|
-
// `Tag`'s label is a span too, and it went invisible against
|
|
134
|
-
// its own fill.
|
|
135
|
-
isDisabled && "bg-ods-bg",
|
|
122
|
+
isDisabled && "opacity-50",
|
|
136
123
|
itemClassName
|
|
137
124
|
)}
|
|
138
125
|
>
|
|
@@ -145,10 +132,7 @@ const RadioGroupBlock = React.forwardRef<
|
|
|
145
132
|
error ? "border-ods-error" : "border-[var(--color-border-strong)]",
|
|
146
133
|
"transition-colors duration-150",
|
|
147
134
|
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ods-accent focus-visible:ring-offset-2 focus-visible:ring-offset-ods-card",
|
|
148
|
-
// Disabled: the EMPTY dot dims with the text; a selected one
|
|
149
|
-
// keeps its accent so "on but locked" stays readable.
|
|
150
135
|
"disabled:cursor-not-allowed",
|
|
151
|
-
"disabled:data-[state=unchecked]:bg-ods-bg disabled:data-[state=unchecked]:border-ods-border",
|
|
152
136
|
"data-[state=checked]:border-ods-accent"
|
|
153
137
|
)}
|
|
154
138
|
>
|
|
@@ -156,16 +140,7 @@ const RadioGroupBlock = React.forwardRef<
|
|
|
156
140
|
<span className="block h-1/2 w-1/2 rounded-full bg-ods-accent" />
|
|
157
141
|
</RadioGroupPrimitive.Indicator>
|
|
158
142
|
</RadioGroupPrimitive.Item>
|
|
159
|
-
<div
|
|
160
|
-
className={cn(
|
|
161
|
-
"flex min-w-0 flex-1 flex-col justify-center",
|
|
162
|
-
// Direct children only — these are the two component-owned
|
|
163
|
-
// wrappers. `option.label` / `option.description` are
|
|
164
|
-
// ReactNode, so a descendant rule would repaint content the
|
|
165
|
-
// caller styled itself.
|
|
166
|
-
isDisabled && "[&>span]:text-ods-text-disabled",
|
|
167
|
-
)}
|
|
168
|
-
>
|
|
143
|
+
<div className="flex min-w-0 flex-1 flex-col justify-center">
|
|
169
144
|
<span
|
|
170
145
|
className={cn(
|
|
171
146
|
"font-[family-name:var(--font-h4-family)] font-[number:var(--font-h4-weight)] text-[length:var(--font-size-h4-body)] leading-[24px]",
|
|
@@ -106,8 +106,7 @@ const innerInputStyles = cn(
|
|
|
106
106
|
"flex-1 min-w-[60px] bg-transparent border-none outline-none",
|
|
107
107
|
"text-h4",
|
|
108
108
|
"text-ods-text-primary placeholder:text-ods-text-secondary",
|
|
109
|
-
|
|
110
|
-
"disabled:cursor-not-allowed disabled:text-ods-text-disabled disabled:placeholder:text-ods-border",
|
|
109
|
+
"disabled:cursor-not-allowed",
|
|
111
110
|
"touch-manipulation"
|
|
112
111
|
)
|
|
113
112
|
|
|
@@ -42,14 +42,7 @@ const SelectTrigger = React.forwardRef<
|
|
|
42
42
|
"enabled:hover:bg-ods-bg-hover enabled:hover:border-ods-border-hover enabled:active:bg-ods-bg-active enabled:active:border-ods-border-active",
|
|
43
43
|
!isInvalid && "data-[state=open]:border-ods-accent data-[state=open]:hover:border-ods-accent",
|
|
44
44
|
"group",
|
|
45
|
-
// Disabled - match Input exactly: value greys out, placeholder dims
|
|
46
|
-
// further. The `data-[placeholder]` rule above is a class+attribute
|
|
47
|
-
// selector, so the disabled placeholder rule stacks `:disabled` on top
|
|
48
|
-
// of it to win on specificity rather than on source order.
|
|
49
45
|
"disabled:!cursor-not-allowed disabled:bg-ods-bg",
|
|
50
|
-
"disabled:text-ods-text-disabled disabled:data-[placeholder]:text-ods-border",
|
|
51
|
-
// The chevron sets its own colour — grey it with the value.
|
|
52
|
-
"disabled:[&_svg]:text-ods-text-disabled",
|
|
53
46
|
"transition-colors duration-200 cursor-pointer",
|
|
54
47
|
"[&>span]:line-clamp-1",
|
|
55
48
|
isInvalid && "border-ods-error enabled:hover:border-ods-error enabled:active:border-ods-error data-[state=open]:border-ods-error",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import React, { useState, useEffect,
|
|
3
|
+
import React, { useState, useEffect, useMemo, useRef, useCallback } from 'react'
|
|
4
4
|
import { useSearchParams, useRouter, usePathname } from '../../embed-shims/next-navigation'
|
|
5
5
|
import { cn } from '../../utils/cn'
|
|
6
6
|
|
|
@@ -38,14 +38,8 @@ interface TabNavigationProps {
|
|
|
38
38
|
urlSync?: boolean | TabNavigationUrlSyncOptions
|
|
39
39
|
defaultTab?: string // Fallback when no valid tab in URL or initial value
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
* a `isStale` flag — true while the bar has already moved to a newly clicked
|
|
44
|
-
* tab but the body is still showing the PREVIOUS one (see `deferredActiveTab`
|
|
45
|
-
* below). Use it to dim the body or show a pending hint; without it a slow
|
|
46
|
-
* tab looks like a tab that simply did nothing.
|
|
47
|
-
*/
|
|
48
|
-
children?: (activeTab: string, state: { isStale: boolean }) => React.ReactNode
|
|
41
|
+
// Render prop to provide active tab to children
|
|
42
|
+
children?: (activeTab: string) => React.ReactNode
|
|
49
43
|
}
|
|
50
44
|
|
|
51
45
|
export function TabNavigation({
|
|
@@ -125,22 +119,6 @@ export function TabNavigation({
|
|
|
125
119
|
}
|
|
126
120
|
}
|
|
127
121
|
|
|
128
|
-
// The tab BAR follows the click immediately; the tab BODY is deferred, which
|
|
129
|
-
// makes React treat swapping it as a transition. That is the whole fix for the
|
|
130
|
-
// flash: a tab whose data is not in the client cache yet SUSPENDS the moment it
|
|
131
|
-
// mounts, and outside a transition React has to show that Suspense fallback —
|
|
132
|
-
// so the skeleton appears for a frame before the real content lands. Inside a
|
|
133
|
-
// transition React keeps the previous tab on screen and swaps only once the new
|
|
134
|
-
// one is ready. Deferring the body rather than the whole state keeps the
|
|
135
|
-
// underline responsive, so the click still feels instant.
|
|
136
|
-
//
|
|
137
|
-
// The cost of that trade is that a SLOW tab leaves the previous tab's content
|
|
138
|
-
// under the new tab's underline with nothing saying so — worse than a skeleton,
|
|
139
|
-
// because stale data reads as fresh data. `isStale` is handed to the render prop
|
|
140
|
-
// so the body can mark itself while it waits.
|
|
141
|
-
const deferredActiveTab = useDeferredValue(activeTab)
|
|
142
|
-
const isStale = activeTab !== deferredActiveTab
|
|
143
|
-
|
|
144
122
|
const scrollRef = useRef<HTMLDivElement>(null)
|
|
145
123
|
const activeTabRef = useRef<HTMLButtonElement>(null)
|
|
146
124
|
const isFirstActiveScrollRef = useRef(true)
|
|
@@ -313,7 +291,7 @@ export function TabNavigation({
|
|
|
313
291
|
</div>
|
|
314
292
|
|
|
315
293
|
{/* Render children with active tab if provided */}
|
|
316
|
-
{children && children(
|
|
294
|
+
{children && children(activeTab)}
|
|
317
295
|
</>
|
|
318
296
|
)
|
|
319
297
|
}
|
|
@@ -140,13 +140,7 @@ export function TagSearchInput<T = string>({
|
|
|
140
140
|
"has-[:focus]:border-ods-accent",
|
|
141
141
|
!disabled && "hover:bg-ods-bg-hover hover:border-ods-border-hover",
|
|
142
142
|
disabled && "bg-ods-bg",
|
|
143
|
-
disabled && "cursor-not-allowed",
|
|
144
|
-
// Match Input: grey the text and the icons, don't fade the whole
|
|
145
|
-
// control (a blanket opacity also washes out the border). Icons only —
|
|
146
|
-
// this container has no adornment spans of its own, so a `[&_span]`
|
|
147
|
-
// rule would ONLY have reached the selected `Tag` chips, whose label is
|
|
148
|
-
// a span, and greyed them into their own fill in the light theme.
|
|
149
|
-
"has-[:disabled]:[&_svg]:text-ods-text-disabled",
|
|
143
|
+
disabled && "cursor-not-allowed opacity-50",
|
|
150
144
|
className,
|
|
151
145
|
)}
|
|
152
146
|
onClick={() => {
|
|
@@ -205,8 +199,7 @@ export function TagSearchInput<T = string>({
|
|
|
205
199
|
"flex-1 min-w-[60px] bg-transparent border-none outline-none",
|
|
206
200
|
"text-h4",
|
|
207
201
|
"text-ods-text-primary placeholder:text-ods-text-secondary",
|
|
208
|
-
|
|
209
|
-
"disabled:cursor-not-allowed disabled:text-ods-text-disabled disabled:placeholder:text-ods-border",
|
|
202
|
+
"disabled:cursor-not-allowed",
|
|
210
203
|
)}
|
|
211
204
|
/>
|
|
212
205
|
</div>
|
|
@@ -119,12 +119,6 @@ const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
|
|
|
119
119
|
!hideBorder && cn("rounded-[6px] border bg-ods-card border-ods-border", accentBorder),
|
|
120
120
|
!hideBorder && !props.disabled && "hover:bg-ods-bg-hover hover:border-ods-border-hover active:bg-ods-bg-active active:border-ods-border-active",
|
|
121
121
|
!hideBorder && props.disabled && "!cursor-not-allowed bg-ods-bg",
|
|
122
|
-
// Start / end icons carry their own colour — grey them with the value
|
|
123
|
-
// so a disabled field reads as one flat colour. Scoped to the DIRECT
|
|
124
|
-
// span children (the two icon wrappers): the nodes inside them are
|
|
125
|
-
// rendered verbatim so callers can pass an interactive trigger and own
|
|
126
|
-
// its styling, and a descendant rule would overwrite exactly that.
|
|
127
|
-
"has-[:disabled]:[&>span]:text-ods-text-disabled has-[:disabled]:[&_svg]:text-ods-text-disabled",
|
|
128
122
|
!hideBorder && isInvalid && cn("border-ods-error hover:border-ods-error", accentBorderError),
|
|
129
123
|
hideBorder && "bg-transparent",
|
|
130
124
|
)}
|
|
@@ -156,8 +150,7 @@ const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
|
|
|
156
150
|
// internally instead of pushing the icon out of frame.
|
|
157
151
|
"max-h-[160px] overflow-y-auto",
|
|
158
152
|
"scrollbar-thin scrollbar-track-transparent scrollbar-thumb-ods-border/30 hover:scrollbar-thumb-ods-text-secondary/30",
|
|
159
|
-
|
|
160
|
-
"disabled:cursor-not-allowed disabled:text-ods-text-disabled disabled:placeholder:text-ods-border",
|
|
153
|
+
"disabled:cursor-not-allowed disabled:placeholder:text-ods-border",
|
|
161
154
|
className,
|
|
162
155
|
)}
|
|
163
156
|
{...props}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import { type ReactNode } from 'react'
|
|
4
|
-
import { useIsTruncated } from '../../hooks/ui/use-is-truncated'
|
|
3
|
+
import React, { type ReactNode, useEffect, useRef, useState } from 'react'
|
|
5
4
|
import { cn } from '../../utils/cn'
|
|
6
5
|
import { FloatingTooltip } from './floating-tooltip'
|
|
7
6
|
|
|
@@ -70,8 +69,24 @@ export function TruncateText({
|
|
|
70
69
|
tone = 'primary',
|
|
71
70
|
mono = false,
|
|
72
71
|
}: TruncateTextProps) {
|
|
72
|
+
const ref = useRef<HTMLSpanElement>(null)
|
|
73
|
+
const [isTruncated, setIsTruncated] = useState(false)
|
|
73
74
|
const isMultiLine = lines > 1
|
|
74
|
-
|
|
75
|
+
|
|
76
|
+
useEffect(() => {
|
|
77
|
+
const el = ref.current
|
|
78
|
+
if (!el) return
|
|
79
|
+
const check = () => {
|
|
80
|
+
const overflows = isMultiLine
|
|
81
|
+
? el.scrollHeight > el.clientHeight + 1
|
|
82
|
+
: el.scrollWidth > el.clientWidth + 1
|
|
83
|
+
setIsTruncated(overflows)
|
|
84
|
+
}
|
|
85
|
+
check()
|
|
86
|
+
const ro = new ResizeObserver(check)
|
|
87
|
+
ro.observe(el)
|
|
88
|
+
return () => ro.disconnect()
|
|
89
|
+
}, [children, isMultiLine])
|
|
75
90
|
|
|
76
91
|
const clampClass = isMultiLine
|
|
77
92
|
? LINE_CLAMP_CLASS[lines as Exclude<typeof lines, 1>]
|
package/src/hooks/ui/index.ts
CHANGED
|
@@ -6,7 +6,6 @@ export * from './use-focus-trap'
|
|
|
6
6
|
export * from './use-header-height'
|
|
7
7
|
export * from './use-horizontal-scrollbar'
|
|
8
8
|
export * from './use-image-edge-color'
|
|
9
|
-
export * from './use-is-truncated'
|
|
10
9
|
export * from './use-local-storage'
|
|
11
10
|
export * from './use-marquee-engine'
|
|
12
11
|
export * from './use-media-query'
|