@skyhook-io/k8s-ui 1.14.6 → 1.14.7
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/applications/ApplicationDetail.tsx +36 -4
- package/src/components/applications/ApplicationsView.tsx +2 -1
- package/src/components/audit/AuditAlerts.tsx +35 -37
- package/src/components/audit/AuditFindingsTable.tsx +23 -27
- package/src/components/charts/PrometheusChartsView.tsx +29 -10
- package/src/components/checks/ChecksView.tsx +56 -26
- package/src/components/gitops/GitOpsDetailLayout.tsx +12 -9
- package/src/components/issues/IssuesView.tsx +35 -54
- package/src/components/logs/LogCore.tsx +40 -9
- package/src/components/resources/ResourcesSidebar.tsx +66 -78
- package/src/components/resources/renderers/ClusterComplianceReportRenderer.tsx +21 -15
- package/src/components/resources/renderers/CompositionRenderer.tsx +30 -10
- package/src/components/resources/renderers/ConfigAuditReportRenderer.tsx +16 -12
- package/src/components/resources/renderers/ExposedSecretReportRenderer.tsx +10 -4
- package/src/components/resources/renderers/GenericRenderer.tsx +13 -7
- package/src/components/resources/renderers/KyvernoPolicyReportRenderer.tsx +6 -4
- package/src/components/resources/renderers/PolicyCoverageSection.tsx +7 -4
- package/src/components/resources/renderers/PrometheusRuleRenderer.tsx +13 -15
- package/src/components/resources/renderers/SbomReportRenderer.tsx +10 -4
- package/src/components/resources/renderers/VulnerabilityReportRenderer.tsx +10 -4
- package/src/components/resources/renderers/rollout/ReplicaSetProgression.tsx +13 -6
- package/src/components/shared/CreateResourceDialog.tsx +7 -4
- package/src/components/timeline/TimelineList.tsx +14 -11
- package/src/components/timeline/TimelineStrip.tsx +26 -4
- package/src/components/timeline/TimelineSwimlanes.tsx +3 -5
- package/src/components/timeline/TimelineToolbar.tsx +43 -10
- package/src/components/topology/TopologyControls.tsx +27 -8
- package/src/components/topology/TopologyGraph.tsx +14 -5
- package/src/components/trace/ReachabilityView.tsx +17 -16
- package/src/components/ui/CardSection.tsx +79 -35
- package/src/components/ui/Collapse.test.tsx +74 -0
- package/src/components/ui/Collapse.tsx +115 -29
- package/src/components/ui/DialogPortal.tsx +7 -2
- package/src/components/ui/ForceDeleteConfirmDialog.tsx +7 -4
- package/src/components/ui/RestrictedState.tsx +7 -4
- package/src/components/ui/RowActionMenu.tsx +20 -8
- package/src/components/ui/SelectMenu.tsx +18 -6
- package/src/components/ui/YamlEditor.tsx +7 -9
- package/src/components/ui/drawer-components.tsx +14 -21
- package/src/components/ui/index.ts +30 -22
- package/src/components/workload/WorkloadView.tsx +6 -10
- package/src/hooks/useAnimatedUnmount.ts +21 -3
- package/src/theme/components.css +4 -1
- package/src/utils/animation.ts +93 -13
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { createContext, useContext, useState } from 'react'
|
|
2
|
-
import {
|
|
2
|
+
import { Copy, Check, Tag, AlertTriangle, CheckCircle, ExternalLink, Layers, X, Minus } from 'lucide-react'
|
|
3
3
|
import { clsx } from 'clsx'
|
|
4
4
|
import { formatAge, formatDuration, formatResources } from '../resources/resource-utils'
|
|
5
5
|
import { getEffectiveResources } from '../../utils/extended-resources'
|
|
6
6
|
import { Tooltip } from './Tooltip'
|
|
7
7
|
import { getKindColorClass } from '../ui/Badge'
|
|
8
|
+
import { Collapse, CollapseChevron, useDisclosure } from './Collapse'
|
|
8
9
|
|
|
9
10
|
// ============================================================================
|
|
10
11
|
// UI COMPONENTS
|
|
@@ -86,26 +87,22 @@ interface SectionProps {
|
|
|
86
87
|
|
|
87
88
|
export function Section({ title, icon: Icon, children, defaultExpanded = true, contentClassName }: SectionProps) {
|
|
88
89
|
const [expanded, setExpanded] = useState(defaultExpanded)
|
|
90
|
+
const { panelId, buttonProps } = useDisclosure(expanded)
|
|
89
91
|
|
|
90
92
|
return (
|
|
91
93
|
<div className="border-b-subtle pb-4 last:border-0">
|
|
92
94
|
<button
|
|
95
|
+
{...buttonProps}
|
|
93
96
|
onClick={() => setExpanded(!expanded)}
|
|
94
97
|
className="flex items-center gap-2 w-full text-left mb-2 hover:text-theme-text-primary transition-colors"
|
|
95
98
|
>
|
|
96
|
-
<
|
|
99
|
+
<CollapseChevron open={expanded} className="w-4 h-4" />
|
|
97
100
|
{Icon && <Icon className="w-4 h-4 text-theme-text-secondary" />}
|
|
98
101
|
<span className="text-sm font-medium text-theme-text-secondary">{title}</span>
|
|
99
102
|
</button>
|
|
100
|
-
<
|
|
101
|
-
className=
|
|
102
|
-
|
|
103
|
-
>
|
|
104
|
-
{/* `relative` is load-bearing — see the note in ui/Collapse.tsx. */}
|
|
105
|
-
<div className="relative overflow-hidden">
|
|
106
|
-
<div className={contentClassName ?? 'pl-6'}>{children}</div>
|
|
107
|
-
</div>
|
|
108
|
-
</div>
|
|
103
|
+
<Collapse open={expanded} id={panelId}>
|
|
104
|
+
<div className={contentClassName ?? 'pl-6'}>{children}</div>
|
|
105
|
+
</Collapse>
|
|
109
106
|
</div>
|
|
110
107
|
)
|
|
111
108
|
}
|
|
@@ -118,25 +115,21 @@ interface ExpandableSectionProps {
|
|
|
118
115
|
|
|
119
116
|
export function ExpandableSection({ title, children, defaultExpanded = true }: ExpandableSectionProps) {
|
|
120
117
|
const [expanded, setExpanded] = useState(defaultExpanded)
|
|
118
|
+
const { panelId, buttonProps } = useDisclosure(expanded)
|
|
121
119
|
|
|
122
120
|
return (
|
|
123
121
|
<div>
|
|
124
122
|
<button
|
|
123
|
+
{...buttonProps}
|
|
125
124
|
onClick={() => setExpanded(!expanded)}
|
|
126
125
|
className="flex items-center gap-2 text-sm text-theme-text-secondary hover:text-theme-text-primary transition-colors mb-1"
|
|
127
126
|
>
|
|
128
|
-
<
|
|
127
|
+
<CollapseChevron open={expanded} className="w-3.5 h-3.5" />
|
|
129
128
|
{title}
|
|
130
129
|
</button>
|
|
131
|
-
<
|
|
132
|
-
className="
|
|
133
|
-
|
|
134
|
-
>
|
|
135
|
-
{/* `relative` is load-bearing — see the note in ui/Collapse.tsx. */}
|
|
136
|
-
<div className="relative overflow-hidden">
|
|
137
|
-
<div className="ml-5">{children}</div>
|
|
138
|
-
</div>
|
|
139
|
-
</div>
|
|
130
|
+
<Collapse open={expanded} id={panelId}>
|
|
131
|
+
<div className="ml-5">{children}</div>
|
|
132
|
+
</Collapse>
|
|
140
133
|
</div>
|
|
141
134
|
)
|
|
142
135
|
}
|
|
@@ -26,7 +26,7 @@ export { ConfirmDialog } from './ConfirmDialog'
|
|
|
26
26
|
export { HealthRing } from './HealthRing'
|
|
27
27
|
export { MetricsChart, MetricsSparkline } from './MetricsChart'
|
|
28
28
|
export * from './drawer-components'
|
|
29
|
-
export { Collapse, CollapseChevron } from './Collapse'
|
|
29
|
+
export { Collapse, CollapseChevron, useDisclosure, disclosurePanelId } from './Collapse'
|
|
30
30
|
export { ResourceBar } from './ResourceBar'
|
|
31
31
|
export { ForceDeleteConfirmDialog } from './ForceDeleteConfirmDialog'
|
|
32
32
|
export { InClusterConsentDialog } from './InClusterConsentDialog'
|
|
@@ -37,31 +37,39 @@ export { YamlEditor, YamlDiffEditor } from './YamlEditor'
|
|
|
37
37
|
// pulling monaco-editor into the main bundle. Anything that mounts a bare
|
|
38
38
|
// `Editor` must await this first, or @monaco-editor/react reaches for its CDN.
|
|
39
39
|
export const ensureMonacoRuntime = () =>
|
|
40
|
-
import(
|
|
40
|
+
import("./monacoRuntime").then(({ ensureMonaco }) => ensureMonaco());
|
|
41
41
|
export type {
|
|
42
42
|
YamlDiagnostic,
|
|
43
43
|
YamlDocumentIdentity,
|
|
44
44
|
YamlEditorProps,
|
|
45
45
|
YamlSchemaLoader,
|
|
46
46
|
YamlSchemaLoadResult,
|
|
47
|
-
} from
|
|
48
|
-
export { YamlReview } from
|
|
49
|
-
export type { YamlPreviewResult, YamlReviewProps } from
|
|
50
|
-
export { RowActionMenu } from
|
|
51
|
-
export type { RowActionItem } from
|
|
47
|
+
} from "./YamlEditor";
|
|
48
|
+
export { YamlReview } from "./YamlReview";
|
|
49
|
+
export type { YamlPreviewResult, YamlReviewProps } from "./YamlReview";
|
|
50
|
+
export { RowActionMenu } from "./RowActionMenu";
|
|
51
|
+
export type { RowActionItem } from "./RowActionMenu";
|
|
52
52
|
|
|
53
|
-
export { PageHeader } from
|
|
54
|
-
export { SummaryTile } from
|
|
55
|
-
export type { SummaryTone } from
|
|
56
|
-
export { Facet, FacetSection, FacetButton } from
|
|
57
|
-
export type { FacetTone } from
|
|
58
|
-
export { SortableTh, TH_CLASS } from
|
|
59
|
-
export type { SortDir } from
|
|
60
|
-
export { DistributionBar, DistributionLegendChip } from
|
|
61
|
-
export type { DistributionSegment } from
|
|
62
|
-
export { SearchPillInput } from
|
|
63
|
-
export type { SearchModifier, SearchPillInputProps } from
|
|
64
|
-
export {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
53
|
+
export { PageHeader } from "./PageHeader";
|
|
54
|
+
export { SummaryTile } from "./SummaryTile";
|
|
55
|
+
export type { SummaryTone } from "./SummaryTile";
|
|
56
|
+
export { Facet, FacetSection, FacetButton } from "./Facet";
|
|
57
|
+
export type { FacetTone } from "./Facet";
|
|
58
|
+
export { SortableTh, TH_CLASS } from "./SortableTh";
|
|
59
|
+
export type { SortDir } from "./SortableTh";
|
|
60
|
+
export { DistributionBar, DistributionLegendChip } from "./DistributionBar";
|
|
61
|
+
export type { DistributionSegment } from "./DistributionBar";
|
|
62
|
+
export { SearchPillInput } from "./SearchPillInput";
|
|
63
|
+
export type { SearchModifier, SearchPillInputProps } from "./SearchPillInput";
|
|
64
|
+
export {
|
|
65
|
+
CardSection,
|
|
66
|
+
CardBody,
|
|
67
|
+
TerminalBlock,
|
|
68
|
+
TerminalBlockLabel,
|
|
69
|
+
renderProse,
|
|
70
|
+
NEUTRAL_CHIP_CLASS,
|
|
71
|
+
KIND_CHIP_CLASS,
|
|
72
|
+
} from "./CardSection";
|
|
73
|
+
export type { CardSectionTone } from "./CardSection";
|
|
74
|
+
export { MultiSelectPicker } from "./MultiSelectPicker";
|
|
75
|
+
export type { MultiSelectPickerProps } from "./MultiSelectPicker";
|
|
@@ -7,6 +7,7 @@ import { FetchResult } from '../ui/FetchResult'
|
|
|
7
7
|
import { PaneLoader } from '../ui/PaneLoader'
|
|
8
8
|
import { useRegisterShortcuts } from '../../hooks/useKeyboardShortcuts'
|
|
9
9
|
import { clsx } from 'clsx'
|
|
10
|
+
import { Collapse } from '../ui/Collapse'
|
|
10
11
|
import {
|
|
11
12
|
ArrowLeft,
|
|
12
13
|
ArrowRight,
|
|
@@ -2949,20 +2950,15 @@ function PodListFrame({
|
|
|
2949
2950
|
{children}
|
|
2950
2951
|
</div>
|
|
2951
2952
|
{hasOverflow && (
|
|
2952
|
-
<
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
)}
|
|
2957
|
-
style={{ transitionTimingFunction: 'cubic-bezier(0.16, 1, 0.3, 1)' }}
|
|
2958
|
-
aria-hidden={!expanded || undefined}
|
|
2959
|
-
>
|
|
2960
|
-
<div className={clsx('min-h-0 overflow-hidden', expanded && 'max-h-[26rem] overflow-y-auto pr-1')} inert={!expanded || undefined}>
|
|
2953
|
+
<Collapse open={expanded}>
|
|
2954
|
+
{/* Long overflow lists scroll inside a capped box once open; the
|
|
2955
|
+
cap comes off while closed so the collapse measures to zero. */}
|
|
2956
|
+
<div className={clsx(expanded && 'max-h-[26rem] overflow-y-auto pr-1')}>
|
|
2961
2957
|
<div className="space-y-2">
|
|
2962
2958
|
{overflow}
|
|
2963
2959
|
</div>
|
|
2964
2960
|
</div>
|
|
2965
|
-
</
|
|
2961
|
+
</Collapse>
|
|
2966
2962
|
)}
|
|
2967
2963
|
{toggle}
|
|
2968
2964
|
</div>
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import { useState, useEffect, useRef } from 'react'
|
|
2
|
-
import { DURATION_NORMAL } from '../utils/animation'
|
|
2
|
+
import { DURATION_NORMAL, prefersReducedMotion } from '../utils/animation'
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Delays React unmount until exit animation completes.
|
|
6
6
|
* - `shouldRender`: whether the component should be in the DOM
|
|
7
7
|
* - `isOpen`: drives CSS transition classes (false → closed state, true → open state)
|
|
8
|
+
*
|
|
9
|
+
* Pass the EXIT duration (overlayExitMs('dialog') etc.) — that is how long the
|
|
10
|
+
* closed state stays mounted. Under reduced motion the app stylesheet zeroes the
|
|
11
|
+
* transition, so the hook disposes at logical close instead of holding an
|
|
12
|
+
* invisible overlay (and its focus / scroll lock) for the exit window.
|
|
8
13
|
*/
|
|
9
14
|
export function useAnimatedUnmount(isVisible: boolean, duration = DURATION_NORMAL) {
|
|
10
15
|
const [shouldRender, setShouldRender] = useState(isVisible)
|
|
@@ -21,8 +26,21 @@ export function useAnimatedUnmount(isVisible: boolean, duration = DURATION_NORMA
|
|
|
21
26
|
return () => cancelAnimationFrame(rafRef.current)
|
|
22
27
|
} else if (shouldRender) {
|
|
23
28
|
setIsOpen(false)
|
|
24
|
-
|
|
25
|
-
|
|
29
|
+
if (prefersReducedMotion()) {
|
|
30
|
+
setShouldRender(false)
|
|
31
|
+
return
|
|
32
|
+
}
|
|
33
|
+
// Start the disposal clock only once the closed state has painted (same
|
|
34
|
+
// double rAF as the open path). A heavy overlay can take a slow frame to
|
|
35
|
+
// commit its closed classes; a timer started at logical close would then
|
|
36
|
+
// unmount it before the exit transition has had its full duration.
|
|
37
|
+
let t = 0
|
|
38
|
+
rafRef.current = requestAnimationFrame(() => {
|
|
39
|
+
rafRef.current = requestAnimationFrame(() => {
|
|
40
|
+
t = window.setTimeout(() => setShouldRender(false), duration)
|
|
41
|
+
})
|
|
42
|
+
})
|
|
43
|
+
return () => { cancelAnimationFrame(rafRef.current); clearTimeout(t) }
|
|
26
44
|
}
|
|
27
45
|
}, [isVisible, duration]) // eslint-disable-line react-hooks/exhaustive-deps — shouldRender intentionally omitted to avoid re-triggering cleanup
|
|
28
46
|
|
package/src/theme/components.css
CHANGED
|
@@ -144,11 +144,14 @@
|
|
|
144
144
|
|
|
145
145
|
/* ── ISSUE ROW DETAILS ── */
|
|
146
146
|
|
|
147
|
+
/* Legacy: prefer <Collapse unmountOnExit> (ui/Collapse.tsx). Kept for any
|
|
148
|
+
straggler; timing mirrors DURATION_DISCLOSURE / CSS_EASE in
|
|
149
|
+
utils/animation.ts — the source-hygiene test keeps them equal. */
|
|
147
150
|
.issue-details-motion {
|
|
148
151
|
display: grid;
|
|
149
152
|
grid-template-rows: 0fr;
|
|
150
153
|
overflow: hidden;
|
|
151
|
-
transition: grid-template-rows
|
|
154
|
+
transition: grid-template-rows 300ms cubic-bezier(0.32, 0.72, 0, 1);
|
|
152
155
|
}
|
|
153
156
|
|
|
154
157
|
.issue-details-motion-open {
|
package/src/utils/animation.ts
CHANGED
|
@@ -1,27 +1,57 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Central animation presets for consistent motion across the UI.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* One curve, a handful of durations, and class fragments that apply them.
|
|
5
|
+
* Every surface that opens, closes, expands or collapses takes its timing
|
|
6
|
+
* from here — the hub (radar-hub-web) re-exports this module so Radar Cloud
|
|
7
|
+
* chrome and the embedded Radar views move alike.
|
|
8
|
+
*
|
|
9
|
+
* Property choice: geometry that has to move (grid-template-rows on a
|
|
10
|
+
* disclosure, width on a drawer) is animated directly; everything else is
|
|
11
|
+
* transform + opacity so it stays GPU-composited.
|
|
6
12
|
*/
|
|
7
13
|
|
|
8
14
|
// -- Easing ------------------------------------------------------------------
|
|
9
15
|
|
|
16
|
+
/** The one UI curve. iOS-style decelerate: fast out of the gate, gentle
|
|
17
|
+
* settle. Used for disclosures, menus, dialogs, sheets and drawers alike so
|
|
18
|
+
* a click reads as the cause of the motion and the content is legible early.
|
|
19
|
+
* Chosen over Material's standard curve in a side-by-side on real content. */
|
|
20
|
+
export const CSS_EASE = 'cubic-bezier(0.32, 0.72, 0, 1)'
|
|
21
|
+
/** Alias with the intent in the name; prefer it in new code. */
|
|
22
|
+
export const EASE_UI = CSS_EASE
|
|
23
|
+
|
|
10
24
|
// Tailwind-safe easing (no spaces — Tailwind splits class strings on whitespace)
|
|
11
25
|
const TW_EASE = 'ease-[cubic-bezier(0.32,0.72,0,1)]'
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
export const CSS_EASE = 'cubic-bezier(0.32, 0.72, 0, 1)'
|
|
26
|
+
/** The same curve as a Tailwind class fragment, for class-string composition. */
|
|
27
|
+
export const TW_EASE_UI = TW_EASE
|
|
15
28
|
|
|
16
29
|
// -- Durations (ms) ----------------------------------------------------------
|
|
17
30
|
|
|
18
|
-
/** Standard UI transitions:
|
|
31
|
+
/** Standard UI transitions: drawers, toasts. */
|
|
19
32
|
export const DURATION_NORMAL = 300
|
|
20
|
-
/** Dock height, subtle layout shifts
|
|
33
|
+
/** Dock height, subtle layout shifts. Deliberately fast: the dock is direct
|
|
34
|
+
* manipulation the user performs dozens of times a day. */
|
|
21
35
|
export const DURATION_DOCK = 150
|
|
22
36
|
/** Toast exit animation */
|
|
23
37
|
export const DURATION_TOAST_EXIT = 200
|
|
24
38
|
|
|
39
|
+
/** In-flow disclosure (Collapse / drawer sections / row expanders): panel
|
|
40
|
+
* and caret share this, open and close alike. Symmetric on purpose — an
|
|
41
|
+
* interrupted toggle reverses cleanly when both directions run one clock. */
|
|
42
|
+
export const DURATION_DISCLOSURE = 300
|
|
43
|
+
|
|
44
|
+
/** Menus, popovers, listboxes. Exits are shorter than entrances: a dismissal
|
|
45
|
+
* should feel immediate. */
|
|
46
|
+
export const DURATION_MENU_IN = 140
|
|
47
|
+
export const DURATION_MENU_OUT = 100
|
|
48
|
+
/** Modal dialogs and command palettes. */
|
|
49
|
+
export const DURATION_DIALOG_IN = 220
|
|
50
|
+
export const DURATION_DIALOG_OUT = 160
|
|
51
|
+
/** Bottom sheets: more travel, a little more time. */
|
|
52
|
+
export const DURATION_SHEET_IN = 280
|
|
53
|
+
export const DURATION_SHEET_OUT = 220
|
|
54
|
+
|
|
25
55
|
/** Drawer ↔ fullscreen expand/collapse morph. Longer than DURATION_NORMAL so a
|
|
26
56
|
* large width change advances in small per-frame steps (reads as fluid, not
|
|
27
57
|
* abrupt). Drives the frame width, the content crossfade, and the JS window
|
|
@@ -32,19 +62,69 @@ export const DURATION_DRAWER_MORPH = 260
|
|
|
32
62
|
* crossfade fixed the reflow that originally needed the gentle start. */
|
|
33
63
|
export const EASE_DRAWER_MORPH = 'cubic-bezier(0.2, 0.8, 0.2, 1)'
|
|
34
64
|
|
|
65
|
+
// -- Reduced motion -----------------------------------------------------------
|
|
66
|
+
|
|
67
|
+
/** True when the OS asks for reduced motion. Presence hooks use it to dispose
|
|
68
|
+
* overlays at logical close instead of after an exit transition that the app
|
|
69
|
+
* stylesheet has already zeroed. SSR- and test-safe. */
|
|
70
|
+
export function prefersReducedMotion(): boolean {
|
|
71
|
+
if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') return false
|
|
72
|
+
return window.matchMedia('(prefers-reduced-motion: reduce)').matches
|
|
73
|
+
}
|
|
74
|
+
|
|
35
75
|
// -- Tailwind class presets ---------------------------------------------------
|
|
36
76
|
// Reusable class fragments — import and spread into clsx() calls.
|
|
37
77
|
|
|
78
|
+
/** Disclosure panel: grid-template-rows 0fr ↔ 1fr on the wrapper. Pair with
|
|
79
|
+
* TRANSITION_CHEVRON on the caret so both run one clock. */
|
|
80
|
+
export const TRANSITION_DISCLOSURE =
|
|
81
|
+
`transition-[grid-template-rows] duration-300 ${TW_EASE} motion-reduce:transition-none`
|
|
82
|
+
|
|
83
|
+
/** Disclosure caret (rotate). Same duration and curve as the panel it opens. */
|
|
84
|
+
export const TRANSITION_CHEVRON =
|
|
85
|
+
`transition-transform duration-300 ${TW_EASE} motion-reduce:transition-none`
|
|
86
|
+
|
|
38
87
|
/** Drawer slide from right — use with translate-x-0/translate-x-full + opacity.
|
|
39
88
|
* Also handles expand/collapse (width) so a single transition covers both. */
|
|
40
89
|
export const TRANSITION_DRAWER =
|
|
41
|
-
`transition-[translate,opacity,width] duration-300 ${TW_EASE} will-change-[transform,width]`
|
|
90
|
+
`transition-[translate,opacity,width] duration-300 ${TW_EASE} will-change-[transform,width] motion-reduce:transition-none`
|
|
42
91
|
|
|
43
|
-
|
|
44
|
-
|
|
92
|
+
/** Overlay backdrop fade. Duration comes from the presence state (see
|
|
93
|
+
* overlayTransitionStyle) so the exit can be shorter than the entrance. */
|
|
45
94
|
export const TRANSITION_BACKDROP =
|
|
46
|
-
|
|
95
|
+
`transition-opacity ${TW_EASE} motion-reduce:transition-none`
|
|
47
96
|
|
|
48
|
-
/** Overlay panel scale + fade — use with scale-100/scale-[0.97] + opacity
|
|
97
|
+
/** Overlay panel scale + fade — use with scale-100/scale-[0.97] + opacity.
|
|
98
|
+
* Duration comes from overlayTransitionStyle, not from this fragment. */
|
|
49
99
|
export const TRANSITION_PANEL =
|
|
50
|
-
`transition-[translate,scale,opacity]
|
|
100
|
+
`transition-[translate,scale,opacity] ${TW_EASE} motion-reduce:transition-none`
|
|
101
|
+
|
|
102
|
+
/** Menu / popover: translate + scale + opacity from its anchor corner. Pair
|
|
103
|
+
* with a transform-origin utility so it grows from the trigger. */
|
|
104
|
+
export const TRANSITION_MENU =
|
|
105
|
+
`transition-[translate,scale,opacity] ${TW_EASE} motion-reduce:transition-none`
|
|
106
|
+
|
|
107
|
+
export type OverlayKind = 'menu' | 'dialog' | 'sheet' | 'drawer'
|
|
108
|
+
|
|
109
|
+
const OVERLAY_DURATIONS: Record<OverlayKind, { in: number; out: number }> = {
|
|
110
|
+
menu: { in: DURATION_MENU_IN, out: DURATION_MENU_OUT },
|
|
111
|
+
dialog: { in: DURATION_DIALOG_IN, out: DURATION_DIALOG_OUT },
|
|
112
|
+
sheet: { in: DURATION_SHEET_IN, out: DURATION_SHEET_OUT },
|
|
113
|
+
// Side drawers keep their symmetric slide (TRANSITION_DRAWER); this entry
|
|
114
|
+
// gives their backdrop the same clock.
|
|
115
|
+
drawer: { in: DURATION_NORMAL, out: DURATION_NORMAL },
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Exit duration for an overlay kind — what useAnimatedUnmount should wait. */
|
|
119
|
+
export function overlayExitMs(kind: OverlayKind): number {
|
|
120
|
+
return OVERLAY_DURATIONS[kind].out
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Inline style that gives an overlay its entrance or exit duration. Apply to
|
|
124
|
+
* the element that carries TRANSITION_PANEL / TRANSITION_MENU / TRANSITION_BACKDROP;
|
|
125
|
+
* the class fragments deliberately carry no duration so the two directions
|
|
126
|
+
* can differ. */
|
|
127
|
+
export function overlayTransitionStyle(isOpen: boolean, kind: OverlayKind): { transitionDuration: string } {
|
|
128
|
+
const d = OVERLAY_DURATIONS[kind]
|
|
129
|
+
return { transitionDuration: `${isOpen ? d.in : d.out}ms` }
|
|
130
|
+
}
|