@12-apps/ui 3.2.0 → 3.4.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/components-catalog.md +8 -0
- package/package.json +2 -1
- package/src/components/data-display/Chart/Chart.tsx +1 -1
- package/src/components/data-display/Chart/Chart.types.ts +10 -0
- package/src/components/data-display/Chart/ChartComposables.tsx +10 -1
- package/src/components/data-display/Chart/SpecChart.tsx +4 -0
- package/src/components/data-display/DataViews/data-views-display-tabs.tsx +40 -15
- package/src/components/data-display/DataViews/data-views-grid-bodies.tsx +7 -15
- package/src/components/data-display/DataViews/data-views-grid-helpers.tsx +21 -0
- package/src/components/data-display/DataViews/data-views-layout-context.tsx +24 -5
- package/src/components/navigation/AppHeader/AppHeader.brand.tsx +102 -0
- package/src/components/navigation/AppHeader/AppHeader.colors.ts +120 -0
- package/src/components/navigation/AppHeader/AppHeader.details.tsx +227 -0
- package/src/components/navigation/AppHeader/AppHeader.identity.tsx +251 -0
- package/src/components/navigation/AppHeader/AppHeader.md +138 -0
- package/src/components/navigation/AppHeader/AppHeader.tsx +232 -0
- package/src/components/navigation/AppHeader/AppHeader.types.ts +190 -0
- package/src/components/navigation/AppHeader/index.ts +16 -0
- package/src/components/navigation/index.ts +1 -0
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { Box } from '@mui/material';
|
|
4
|
+
import React from 'react';
|
|
5
|
+
|
|
6
|
+
import { withDefaults } from '../../../utils/withDefaults';
|
|
7
|
+
|
|
8
|
+
import type { AppHeaderProps } from './AppHeader.types';
|
|
9
|
+
|
|
10
|
+
/** Bar height, before the optional `below` row. */
|
|
11
|
+
const BAR_MIN_HEIGHT = { regular: 64, dense: 52 } as const;
|
|
12
|
+
|
|
13
|
+
/** The props {@link HEADER_DEFAULTS} guarantees a value for. */
|
|
14
|
+
type Defaulted =
|
|
15
|
+
| 'position'
|
|
16
|
+
| 'offsetTop'
|
|
17
|
+
| 'maxWidth'
|
|
18
|
+
| 'divider'
|
|
19
|
+
| 'elevateOnScroll'
|
|
20
|
+
| 'dense'
|
|
21
|
+
| 'disableSpacer'
|
|
22
|
+
| 'dataTestId';
|
|
23
|
+
|
|
24
|
+
/** `AppHeaderProps` as the body sees them: nothing defaulted is still optional. */
|
|
25
|
+
type ResolvedHeaderProps = AppHeaderProps & Required<Pick<AppHeaderProps, Defaulted>>;
|
|
26
|
+
|
|
27
|
+
const HEADER_DEFAULTS: Pick<ResolvedHeaderProps, Defaulted> = {
|
|
28
|
+
position: 'sticky',
|
|
29
|
+
offsetTop: 0,
|
|
30
|
+
maxWidth: 1200,
|
|
31
|
+
divider: true,
|
|
32
|
+
elevateOnScroll: false,
|
|
33
|
+
dense: false,
|
|
34
|
+
disableSpacer: false,
|
|
35
|
+
dataTestId: 'app-header',
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Watch the document scroll so a lifted bar can cast a shadow only once there
|
|
40
|
+
* is something under it. Inert (and never attaches a listener) when off.
|
|
41
|
+
*/
|
|
42
|
+
function useScrolled(enabled: boolean): boolean {
|
|
43
|
+
const [scrolled, setScrolled] = React.useState(false);
|
|
44
|
+
React.useEffect(() => {
|
|
45
|
+
if (!enabled) return undefined;
|
|
46
|
+
const onScroll = (): void => setScrolled(window.scrollY > 0);
|
|
47
|
+
onScroll();
|
|
48
|
+
window.addEventListener('scroll', onScroll, { passive: true });
|
|
49
|
+
return () => window.removeEventListener('scroll', onScroll);
|
|
50
|
+
}, [enabled]);
|
|
51
|
+
return enabled && scrolled;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* The bar's own height, republished whenever it changes.
|
|
56
|
+
*
|
|
57
|
+
* A `fixed` bar is out of flow, so something has to reserve its space — and a
|
|
58
|
+
* constant cannot, because this bar's height is whatever the caller's slots add
|
|
59
|
+
* up to. A search field in `below`, a status line, a wrapped title: each moves
|
|
60
|
+
* it, and a hard-coded spacer would leave the page's first element under the
|
|
61
|
+
* bar or floating below a gap.
|
|
62
|
+
*/
|
|
63
|
+
function useMeasuredHeight(enabled: boolean): [React.RefObject<HTMLElement | null>, number] {
|
|
64
|
+
const ref = React.useRef<HTMLElement | null>(null);
|
|
65
|
+
const [height, setHeight] = React.useState(0);
|
|
66
|
+
React.useEffect(() => {
|
|
67
|
+
const node = ref.current;
|
|
68
|
+
if (!enabled || !node) return undefined;
|
|
69
|
+
const measure = (): void => setHeight(node.getBoundingClientRect().height);
|
|
70
|
+
measure();
|
|
71
|
+
// Measured once unconditionally, observed only where the API exists. jsdom
|
|
72
|
+
// has no ResizeObserver, so constructing it unguarded takes down every
|
|
73
|
+
// consumer's test that happens to render a fixed bar — a crash in THEIR
|
|
74
|
+
// suite, thrown from a component they only laid out.
|
|
75
|
+
if (typeof ResizeObserver === 'undefined') return undefined;
|
|
76
|
+
const observer = new ResizeObserver(measure);
|
|
77
|
+
observer.observe(node);
|
|
78
|
+
return () => observer.disconnect();
|
|
79
|
+
}, [enabled]);
|
|
80
|
+
return [ref, height];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* The bar's own surface. Lifted out of the component both to keep it inside the
|
|
85
|
+
* complexity bar and because the positioning rules are the fiddly part: a
|
|
86
|
+
* `static` bar must claim neither a stacking context nor an offset, and only a
|
|
87
|
+
* `fixed` one needs pinning to the viewport's edges.
|
|
88
|
+
*/
|
|
89
|
+
function surfaceSx({
|
|
90
|
+
position,
|
|
91
|
+
offsetTop,
|
|
92
|
+
divider,
|
|
93
|
+
scrolled,
|
|
94
|
+
}: Required<Pick<AppHeaderProps, 'position' | 'offsetTop' | 'divider'>> & {
|
|
95
|
+
scrolled: boolean;
|
|
96
|
+
}): Record<string, unknown> {
|
|
97
|
+
return {
|
|
98
|
+
position,
|
|
99
|
+
...(position === 'static' ? {} : { top: offsetTop, zIndex: 'appBar' }),
|
|
100
|
+
...(position === 'fixed' ? { left: 0, right: 0 } : {}),
|
|
101
|
+
backgroundColor: 'background.paper',
|
|
102
|
+
borderBottom: divider ? 1 : 0,
|
|
103
|
+
borderColor: 'divider',
|
|
104
|
+
boxShadow: scrolled ? 2 : 0,
|
|
105
|
+
transition: 'box-shadow 150ms ease',
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** The trailing column: the muted note, and the actions under it. */
|
|
110
|
+
const HeaderTrailing: React.FC<Pick<AppHeaderProps, 'meta' | 'actions'>> = ({ meta, actions }) => {
|
|
111
|
+
if (!meta && !actions) return null;
|
|
112
|
+
return (
|
|
113
|
+
<Box
|
|
114
|
+
sx={{
|
|
115
|
+
flex: '0 0 auto',
|
|
116
|
+
display: 'flex',
|
|
117
|
+
flexDirection: 'column',
|
|
118
|
+
alignItems: 'flex-end',
|
|
119
|
+
gap: 0.5,
|
|
120
|
+
}}
|
|
121
|
+
>
|
|
122
|
+
{meta && (
|
|
123
|
+
<Box
|
|
124
|
+
data-testid="app-header-meta"
|
|
125
|
+
sx={{
|
|
126
|
+
fontSize: 11,
|
|
127
|
+
fontWeight: 700,
|
|
128
|
+
letterSpacing: '0.08em',
|
|
129
|
+
textTransform: 'uppercase',
|
|
130
|
+
color: 'text.disabled',
|
|
131
|
+
lineHeight: 1,
|
|
132
|
+
}}
|
|
133
|
+
>
|
|
134
|
+
{meta}
|
|
135
|
+
</Box>
|
|
136
|
+
)}
|
|
137
|
+
{actions && (
|
|
138
|
+
<Box
|
|
139
|
+
component="nav"
|
|
140
|
+
data-testid="app-header-actions"
|
|
141
|
+
sx={{ display: 'flex', alignItems: 'center', gap: 1 }}
|
|
142
|
+
>
|
|
143
|
+
{actions}
|
|
144
|
+
</Box>
|
|
145
|
+
)}
|
|
146
|
+
</Box>
|
|
147
|
+
);
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* The application bar: a row of slots on one surface, plus an optional second
|
|
152
|
+
* row that belongs to it.
|
|
153
|
+
*
|
|
154
|
+
* It knows nothing about routers, sessions, carts or stores — every slot is a
|
|
155
|
+
* `ReactNode` and every behaviour is a callback, which is what lets one bar
|
|
156
|
+
* serve a storefront, a back office and a platform console without any of them
|
|
157
|
+
* leaking into the others. Compose it with {@link AppHeaderIdentity},
|
|
158
|
+
* {@link AppHeaderStatus} and {@link AppHeaderDetails}, or put your own nodes in
|
|
159
|
+
* the slots.
|
|
160
|
+
*
|
|
161
|
+
* The SURFACE always spans the viewport while the CONTENT stops at `maxWidth`
|
|
162
|
+
* and centres, so the same bar reads as a phone header and as a desktop one
|
|
163
|
+
* without a second component or a breakpoint at the call site.
|
|
164
|
+
*/
|
|
165
|
+
export const AppHeader: React.FC<AppHeaderProps> = (props) => {
|
|
166
|
+
// A table of defaults rather than one per destructured prop: each `= default`
|
|
167
|
+
// is a branch, and nine of them put this component over the complexity bar
|
|
168
|
+
// before it rendered anything.
|
|
169
|
+
const {
|
|
170
|
+
leading,
|
|
171
|
+
children,
|
|
172
|
+
actions,
|
|
173
|
+
meta,
|
|
174
|
+
below,
|
|
175
|
+
position,
|
|
176
|
+
offsetTop,
|
|
177
|
+
maxWidth,
|
|
178
|
+
divider,
|
|
179
|
+
elevateOnScroll,
|
|
180
|
+
dense,
|
|
181
|
+
disableSpacer,
|
|
182
|
+
className,
|
|
183
|
+
dataTestId,
|
|
184
|
+
// True by construction — `withDefaults` fills exactly these keys — but not
|
|
185
|
+
// something TS can follow through a `Partial<T>` table.
|
|
186
|
+
} = withDefaults(props, HEADER_DEFAULTS) as ResolvedHeaderProps;
|
|
187
|
+
|
|
188
|
+
const scrolled = useScrolled(elevateOnScroll);
|
|
189
|
+
const needsSpacer = position === 'fixed' && !disableSpacer;
|
|
190
|
+
const [ref, height] = useMeasuredHeight(needsSpacer);
|
|
191
|
+
|
|
192
|
+
return (
|
|
193
|
+
<>
|
|
194
|
+
<Box
|
|
195
|
+
ref={ref}
|
|
196
|
+
component="header"
|
|
197
|
+
className={className}
|
|
198
|
+
data-testid={dataTestId}
|
|
199
|
+
sx={surfaceSx({ position, offsetTop, divider, scrolled })}
|
|
200
|
+
>
|
|
201
|
+
<Box sx={{ maxWidth, mx: 'auto', px: { xs: 2, md: 3 } }}>
|
|
202
|
+
<Box
|
|
203
|
+
sx={{
|
|
204
|
+
display: 'flex',
|
|
205
|
+
alignItems: 'center',
|
|
206
|
+
gap: { xs: 1, md: 2 },
|
|
207
|
+
minHeight: dense ? BAR_MIN_HEIGHT.dense : BAR_MIN_HEIGHT.regular,
|
|
208
|
+
py: 1,
|
|
209
|
+
}}
|
|
210
|
+
>
|
|
211
|
+
{leading}
|
|
212
|
+
{/* The identity takes the slack and is the only part allowed to
|
|
213
|
+
shrink — the actions are targets and the meta note is already
|
|
214
|
+
the smallest thing in the bar. */}
|
|
215
|
+
<Box sx={{ flex: '1 1 auto', minWidth: 0 }}>{children}</Box>
|
|
216
|
+
<HeaderTrailing meta={meta} actions={actions} />
|
|
217
|
+
</Box>
|
|
218
|
+
{below && (
|
|
219
|
+
<Box data-testid={`${dataTestId}-below`} sx={{ pb: 1.5 }}>
|
|
220
|
+
{below}
|
|
221
|
+
</Box>
|
|
222
|
+
)}
|
|
223
|
+
</Box>
|
|
224
|
+
</Box>
|
|
225
|
+
{needsSpacer && <Box aria-hidden data-testid={`${dataTestId}-spacer`} sx={{ height }} />}
|
|
226
|
+
</>
|
|
227
|
+
);
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
AppHeader.displayName = 'AppHeader';
|
|
231
|
+
|
|
232
|
+
export default AppHeader;
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import type { Breakpoint } from '@mui/material/styles';
|
|
2
|
+
import type { ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
import type { ColorValue, SizeValue } from '../../../tokens/scales';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* How the bar sits in the page.
|
|
8
|
+
*
|
|
9
|
+
* `fixed` lifts it out of flow, so {@link AppHeaderProps.disableSpacer} aside it
|
|
10
|
+
* also renders a spacer of its own measured height — a fixed bar with nothing
|
|
11
|
+
* reserving its space is the classic "first paragraph is under the header" bug.
|
|
12
|
+
*/
|
|
13
|
+
export type AppHeaderPosition = 'static' | 'sticky' | 'fixed';
|
|
14
|
+
|
|
15
|
+
/** Which surface the details panel takes on this viewport. */
|
|
16
|
+
export type AppHeaderDetailsPresentation = 'auto' | 'sheet' | 'dialog';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The circular mark that opens the header: a logo when there is one, the name's
|
|
20
|
+
* initials on a derived gradient when there is not.
|
|
21
|
+
*
|
|
22
|
+
* The gradient is DERIVED from `seedColor` rather than configured, so a caller
|
|
23
|
+
* supplies one brand colour and gets a mark that belongs to it. With no seed it
|
|
24
|
+
* derives from the theme's primary, which is what makes the mark correct in an
|
|
25
|
+
* app that has no per-tenant colour at all.
|
|
26
|
+
*/
|
|
27
|
+
export interface AppHeaderBrandProps {
|
|
28
|
+
/** Who the mark stands for. Initials come from it, and it labels the mark. */
|
|
29
|
+
name: string;
|
|
30
|
+
/** Logo image; replaces the initials when it loads. */
|
|
31
|
+
logoUrl?: string | null;
|
|
32
|
+
/** `#RRGGBB` (or any CSS colour) to derive the gradient from. */
|
|
33
|
+
seedColor?: string | null;
|
|
34
|
+
/** @default 'md' */
|
|
35
|
+
size?: SizeValue;
|
|
36
|
+
/** @default 'circle' */
|
|
37
|
+
shape?: 'circle' | 'rounded';
|
|
38
|
+
className?: string;
|
|
39
|
+
dataTestId?: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* The one-line state under the title — a tone dot plus segments read as a
|
|
44
|
+
* sentence ("Aberto agora · Retirada no balcão").
|
|
45
|
+
*/
|
|
46
|
+
export interface AppHeaderStatusProps {
|
|
47
|
+
/** The dot's colour. Omit for no dot. */
|
|
48
|
+
tone?: ColorValue;
|
|
49
|
+
/** The segments, in reading order. Empty entries are dropped. */
|
|
50
|
+
items: ReactNode[];
|
|
51
|
+
/** What goes between segments. @default '·' */
|
|
52
|
+
separator?: ReactNode;
|
|
53
|
+
className?: string;
|
|
54
|
+
dataTestId?: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Mark + title + status, optionally the control that discloses the details
|
|
59
|
+
* panel.
|
|
60
|
+
*
|
|
61
|
+
* There is deliberately no `href` here. A title that navigates is one line of
|
|
62
|
+
* the consumer's own router in the `leading` slot, whereas an `href` prop would
|
|
63
|
+
* have to guess whose link component it is being handed — `to` for one router,
|
|
64
|
+
* `href` for another — and would half-work for everybody. `onDisclose` is the
|
|
65
|
+
* only interaction the block owns: with it the block is a button and a chevron
|
|
66
|
+
* appears, without it the block is static text.
|
|
67
|
+
*/
|
|
68
|
+
export interface AppHeaderIdentityProps
|
|
69
|
+
extends Pick<AppHeaderBrandProps, 'logoUrl' | 'seedColor'> {
|
|
70
|
+
/** The name shown, and the one the mark derives its initials from. */
|
|
71
|
+
title: string;
|
|
72
|
+
/** A quieter line under the title — use `status` for the dotted state line. */
|
|
73
|
+
subtitle?: ReactNode;
|
|
74
|
+
/** The state line, usually an {@link AppHeaderStatusProps} element. */
|
|
75
|
+
status?: ReactNode;
|
|
76
|
+
/** Replaces the derived mark entirely (an icon, an `<img>`, nothing). */
|
|
77
|
+
mark?: ReactNode;
|
|
78
|
+
/** Opens the details panel. Renders the block as a button with a chevron. */
|
|
79
|
+
onDisclose?: () => void;
|
|
80
|
+
/** Whether the panel `onDisclose` opens is currently open (`aria-expanded`). */
|
|
81
|
+
disclosed?: boolean;
|
|
82
|
+
/** Accessible name of the disclosure. @default `Detalhes de {title}` */
|
|
83
|
+
discloseLabel?: string;
|
|
84
|
+
/**
|
|
85
|
+
* The identity is still being resolved — hold a skeleton the size of the real
|
|
86
|
+
* block rather than a fallback name. An app that paints a placeholder title
|
|
87
|
+
* flashes the wrong brand for a frame on every load.
|
|
88
|
+
*/
|
|
89
|
+
loading?: boolean;
|
|
90
|
+
/** @default 'md' */
|
|
91
|
+
size?: SizeValue;
|
|
92
|
+
className?: string;
|
|
93
|
+
dataTestId?: string;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* The bar itself: a row of slots on a surface, with an optional second row that
|
|
98
|
+
* belongs to the same surface (search, tabs, chips).
|
|
99
|
+
*
|
|
100
|
+
* Every slot is a `ReactNode`, so the header knows nothing about sessions,
|
|
101
|
+
* routers, carts or stores — it lays out what it is handed.
|
|
102
|
+
*/
|
|
103
|
+
export interface AppHeaderProps {
|
|
104
|
+
/** Before the identity: a back arrow, a menu toggle, a logo. */
|
|
105
|
+
leading?: ReactNode;
|
|
106
|
+
/** The identity block. Usually an `AppHeaderIdentity`. */
|
|
107
|
+
children?: ReactNode;
|
|
108
|
+
/** Trailing controls: sign-in, cart, account avatar. */
|
|
109
|
+
actions?: ReactNode;
|
|
110
|
+
/**
|
|
111
|
+
* A small muted note pinned to the top-right corner above `actions` — a build
|
|
112
|
+
* tag, an environment name. Hidden from assistive tech is NOT the default: if
|
|
113
|
+
* it is worth showing it is worth reading.
|
|
114
|
+
*/
|
|
115
|
+
meta?: ReactNode;
|
|
116
|
+
/** A second row inside the same surface: a search field, tabs, filter chips. */
|
|
117
|
+
below?: ReactNode;
|
|
118
|
+
/** @default 'sticky' */
|
|
119
|
+
position?: AppHeaderPosition;
|
|
120
|
+
/**
|
|
121
|
+
* A CSS length the bar offsets itself by when `sticky`/`fixed` — for whatever
|
|
122
|
+
* already occupies the top of the viewport (an impersonation banner, an
|
|
123
|
+
* offline bar). Accepts a `var(--x, 0px)` so the offset can be published by
|
|
124
|
+
* the element itself. @default 0
|
|
125
|
+
*/
|
|
126
|
+
offsetTop?: number | string;
|
|
127
|
+
/**
|
|
128
|
+
* How wide the content may grow before it stops and centres. This is what
|
|
129
|
+
* makes one bar work on a phone and on a desktop: the surface still spans the
|
|
130
|
+
* viewport, the content does not. @default 1200
|
|
131
|
+
*/
|
|
132
|
+
maxWidth?: number | string;
|
|
133
|
+
/** Draw the bottom border. @default true */
|
|
134
|
+
divider?: boolean;
|
|
135
|
+
/** Raise a shadow once the page has scrolled under the bar. @default false */
|
|
136
|
+
elevateOnScroll?: boolean;
|
|
137
|
+
/** Tighter vertical rhythm, for a bar with no status line. @default false */
|
|
138
|
+
dense?: boolean;
|
|
139
|
+
/** Skip the flow spacer a `fixed` bar renders. @default false */
|
|
140
|
+
disableSpacer?: boolean;
|
|
141
|
+
className?: string;
|
|
142
|
+
dataTestId?: string;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** One label/value line in the details panel. */
|
|
146
|
+
export interface AppHeaderDetailRow {
|
|
147
|
+
/** Stable key; falls back to the label. */
|
|
148
|
+
id?: string;
|
|
149
|
+
label: string;
|
|
150
|
+
value: ReactNode;
|
|
151
|
+
/** Accent the value ("Aberto até 22h" in success green). */
|
|
152
|
+
tone?: ColorValue;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** The details panel's single full-width call to action. */
|
|
156
|
+
export interface AppHeaderDetailsAction {
|
|
157
|
+
label: string;
|
|
158
|
+
onClick: () => void;
|
|
159
|
+
/** @default 'primary' */
|
|
160
|
+
color?: ColorValue;
|
|
161
|
+
disabled?: boolean;
|
|
162
|
+
dataTestId?: string;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* The panel the identity's disclosure opens.
|
|
167
|
+
*
|
|
168
|
+
* It is ONE component with two presentations because they are one thing to the
|
|
169
|
+
* user: a bottom sheet where the thumb is, a centred dialog where the pointer
|
|
170
|
+
* is. `presentation: 'auto'` picks by viewport so a caller never writes that
|
|
171
|
+
* branch — and never ships a phone-sized sheet stretched across a desktop.
|
|
172
|
+
*/
|
|
173
|
+
export interface AppHeaderDetailsProps {
|
|
174
|
+
open: boolean;
|
|
175
|
+
onClose: () => void;
|
|
176
|
+
title: string;
|
|
177
|
+
subtitle?: ReactNode;
|
|
178
|
+
/** The label/value lines. Rendered above `children`. */
|
|
179
|
+
rows?: AppHeaderDetailRow[];
|
|
180
|
+
/** Anything the rows cannot express — a map, a note, a list. */
|
|
181
|
+
children?: ReactNode;
|
|
182
|
+
/** The single full-width action at the foot of the panel. */
|
|
183
|
+
action?: AppHeaderDetailsAction;
|
|
184
|
+
/** @default 'auto' */
|
|
185
|
+
presentation?: AppHeaderDetailsPresentation;
|
|
186
|
+
/** Below this breakpoint `auto` chooses the sheet. @default 'sm' */
|
|
187
|
+
breakpoint?: Breakpoint;
|
|
188
|
+
className?: string;
|
|
189
|
+
dataTestId?: string;
|
|
190
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export { AppHeader, default } from './AppHeader';
|
|
2
|
+
export { AppHeaderBrand } from './AppHeader.brand';
|
|
3
|
+
export { AppHeaderDetails } from './AppHeader.details';
|
|
4
|
+
export { AppHeaderIdentity, AppHeaderStatus } from './AppHeader.identity';
|
|
5
|
+
export { brandGradient, highlightOf, initialsOf } from './AppHeader.colors';
|
|
6
|
+
export type {
|
|
7
|
+
AppHeaderBrandProps,
|
|
8
|
+
AppHeaderDetailRow,
|
|
9
|
+
AppHeaderDetailsAction,
|
|
10
|
+
AppHeaderDetailsPresentation,
|
|
11
|
+
AppHeaderDetailsProps,
|
|
12
|
+
AppHeaderIdentityProps,
|
|
13
|
+
AppHeaderPosition,
|
|
14
|
+
AppHeaderProps,
|
|
15
|
+
AppHeaderStatusProps,
|
|
16
|
+
} from './AppHeader.types';
|