ionbase-ui 0.4.0 → 0.6.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/README.md +2 -0
- package/dist/components/FullCard.d.ts +65 -0
- package/dist/components/FullCard.d.ts.map +1 -0
- package/dist/components/FullCard.js +41 -0
- package/dist/components/FullCard.js.map +1 -0
- package/dist/components/Header.d.ts +43 -11
- package/dist/components/Header.d.ts.map +1 -1
- package/dist/components/Header.js +61 -9
- package/dist/components/Header.js.map +1 -1
- package/dist/components/index.d.ts +2 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +1 -0
- package/dist/components/index.js.map +1 -1
- package/dist/styles/full-card.css +287 -0
- package/dist/styles/header.css +138 -35
- package/dist/styles/index.css +1 -0
- package/dist/styles/tokens/base.css +24 -23
- package/dist/styles/tokens/elevation.css +6 -6
- package/dist/styles/tokens/theme-dark.css +2 -1
- package/dist/styles/tokens/typography.css +3 -3
- package/dist/tokens/index.d.ts +200 -194
- package/dist/tokens/index.d.ts.map +1 -1
- package/dist/tokens/index.js +165 -160
- package/dist/tokens/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export type FullCardAlignment = 'right' | 'left';
|
|
3
|
+
/**
|
|
4
|
+
* Which heading element the headline renders as.
|
|
5
|
+
*
|
|
6
|
+
* `h1` is deliberately absent: a full card is a section within a page, never
|
|
7
|
+
* the page's own title.
|
|
8
|
+
*/
|
|
9
|
+
export type FullCardHeadingLevel = 2 | 3 | 4 | 5 | 6;
|
|
10
|
+
export interface FullCardProps extends Omit<React.HTMLAttributes<HTMLElement>, 'title'> {
|
|
11
|
+
/**
|
|
12
|
+
* Which side the media panel sits on. Matches Figma's `Alignment` variant,
|
|
13
|
+
* whose `Right` / `Left` name the media, not the text.
|
|
14
|
+
*/
|
|
15
|
+
alignment?: FullCardAlignment;
|
|
16
|
+
/** Figma's `Eyebrow` slot — a Badge in the design, but any node works. */
|
|
17
|
+
eyebrow?: React.ReactNode;
|
|
18
|
+
/** Figma's `Headline`. Required: a card with no headline has no accessible name. */
|
|
19
|
+
headline: React.ReactNode;
|
|
20
|
+
/** Figma's `Description`. */
|
|
21
|
+
description?: React.ReactNode;
|
|
22
|
+
/** Figma's `Actions` slot — a secondary Button in the design. */
|
|
23
|
+
actions?: React.ReactNode;
|
|
24
|
+
/**
|
|
25
|
+
* Figma's `Media` slot. Rendered inside the framed screen holder, so pass
|
|
26
|
+
* the screenshot or embed itself, not the frame.
|
|
27
|
+
*/
|
|
28
|
+
media?: React.ReactNode;
|
|
29
|
+
/**
|
|
30
|
+
* Heading element for the headline. Defaults to `h3`, matching the `Type/H3`
|
|
31
|
+
* text style Figma applies — but the level is a document-outline decision
|
|
32
|
+
* only the page knows, so it is a prop rather than a constant.
|
|
33
|
+
*/
|
|
34
|
+
headingLevel?: FullCardHeadingLevel;
|
|
35
|
+
children?: React.ReactNode;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Full Card — the full-bleed case study row from Figma `Full Card` (592:857).
|
|
39
|
+
*
|
|
40
|
+
* A text column beside a framed media panel, split down the middle, mirrored
|
|
41
|
+
* by the `Alignment` variant.
|
|
42
|
+
*
|
|
43
|
+
* The split holds from 1080 and stacks below it, media above content in BOTH
|
|
44
|
+
* alignments — `Alignment` names a horizontal side and
|
|
45
|
+
* stops meaning anything once there is one column, so it does not get to
|
|
46
|
+
* decide the vertical order too. Size is a media query, not a prop, the same
|
|
47
|
+
* call Header makes about Device.
|
|
48
|
+
*
|
|
49
|
+
* WHY THE `show*` BOOLEANS ARE GONE
|
|
50
|
+
*
|
|
51
|
+
* Figma carries `Show Eyebrow`, `Show Description` and `Show Actions` beside
|
|
52
|
+
* the slots they gate, because a Figma component instance always holds every
|
|
53
|
+
* layer and needs a switch to hide one. React has no such constraint — an
|
|
54
|
+
* absent prop is the switch. Badge made the same call with `Show Dot`, and
|
|
55
|
+
* keeping both would have let `showActions` and `actions` disagree.
|
|
56
|
+
*
|
|
57
|
+
* `headline` is required, and it is the only required prop. The heading is
|
|
58
|
+
* what makes this a section rather than a decorated div, and every other part
|
|
59
|
+
* of the card is optional in Figma too.
|
|
60
|
+
*
|
|
61
|
+
* No `'use client'`: nothing here is stateful or interactive. The interactive
|
|
62
|
+
* parts arrive through `actions`, and they carry their own boundary.
|
|
63
|
+
*/
|
|
64
|
+
export declare const FullCard: React.ForwardRefExoticComponent<FullCardProps & React.RefAttributes<HTMLElement>>;
|
|
65
|
+
//# sourceMappingURL=FullCard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FullCard.d.ts","sourceRoot":"","sources":["../../src/components/FullCard.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAqB,MAAM,OAAO,CAAC;AAE1C,MAAM,MAAM,iBAAiB,GAAG,OAAO,GAAG,MAAM,CAAC;AAEjD;;;;;GAKG;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AAErD,MAAM,WAAW,aAAc,SAAQ,IAAI,CACzC,KAAK,CAAC,cAAc,CAAC,WAAW,CAAC,EACjC,OAAO,CACR;IACC;;;OAGG;IACH,SAAS,CAAC,EAAE,iBAAiB,CAAC;IAC9B,0EAA0E;IAC1E,OAAO,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,oFAAoF;IACpF,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,6BAA6B;IAC7B,WAAW,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC9B,iEAAiE;IACjE,OAAO,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B;;;OAGG;IACH,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACxB;;;;OAIG;IACH,YAAY,CAAC,EAAE,oBAAoB,CAAC;IACpC,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,eAAO,MAAM,QAAQ,mFAgEpB,CAAC"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef } from 'react';
|
|
3
|
+
/**
|
|
4
|
+
* Full Card — the full-bleed case study row from Figma `Full Card` (592:857).
|
|
5
|
+
*
|
|
6
|
+
* A text column beside a framed media panel, split down the middle, mirrored
|
|
7
|
+
* by the `Alignment` variant.
|
|
8
|
+
*
|
|
9
|
+
* The split holds from 1080 and stacks below it, media above content in BOTH
|
|
10
|
+
* alignments — `Alignment` names a horizontal side and
|
|
11
|
+
* stops meaning anything once there is one column, so it does not get to
|
|
12
|
+
* decide the vertical order too. Size is a media query, not a prop, the same
|
|
13
|
+
* call Header makes about Device.
|
|
14
|
+
*
|
|
15
|
+
* WHY THE `show*` BOOLEANS ARE GONE
|
|
16
|
+
*
|
|
17
|
+
* Figma carries `Show Eyebrow`, `Show Description` and `Show Actions` beside
|
|
18
|
+
* the slots they gate, because a Figma component instance always holds every
|
|
19
|
+
* layer and needs a switch to hide one. React has no such constraint — an
|
|
20
|
+
* absent prop is the switch. Badge made the same call with `Show Dot`, and
|
|
21
|
+
* keeping both would have let `showActions` and `actions` disagree.
|
|
22
|
+
*
|
|
23
|
+
* `headline` is required, and it is the only required prop. The heading is
|
|
24
|
+
* what makes this a section rather than a decorated div, and every other part
|
|
25
|
+
* of the card is optional in Figma too.
|
|
26
|
+
*
|
|
27
|
+
* No `'use client'`: nothing here is stateful or interactive. The interactive
|
|
28
|
+
* parts arrive through `actions`, and they carry their own boundary.
|
|
29
|
+
*/
|
|
30
|
+
export const FullCard = forwardRef(({ alignment = 'right', eyebrow, headline, description, actions, media, headingLevel = 3, className, children, ...rest }, ref) => {
|
|
31
|
+
const Heading = `h${headingLevel}`;
|
|
32
|
+
return (_jsxs("section", { ...rest, ref: ref, className: [
|
|
33
|
+
'ion-full-card',
|
|
34
|
+
`ion-full-card--media-${alignment}`,
|
|
35
|
+
className,
|
|
36
|
+
]
|
|
37
|
+
.filter(Boolean)
|
|
38
|
+
.join(' '), children: [_jsx("div", { className: "ion-full-card__content", children: _jsxs("div", { className: "ion-full-card__body", children: [eyebrow && _jsx("div", { className: "ion-full-card__eyebrow", children: eyebrow }), _jsx(Heading, { className: "ion-full-card__headline", children: headline }), description && (_jsx("p", { className: "ion-full-card__description", children: description })), actions && _jsx("div", { className: "ion-full-card__actions", children: actions }), children] }) }), _jsx("div", { className: "ion-full-card__media", children: _jsx("div", { className: "ion-full-card__media-mat", children: _jsx("div", { className: "ion-full-card__media-frame", children: _jsx("div", { className: "ion-full-card__media-screen", children: media }) }) }) })] }));
|
|
39
|
+
});
|
|
40
|
+
FullCard.displayName = 'FullCard';
|
|
41
|
+
//# sourceMappingURL=FullCard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FullCard.js","sourceRoot":"","sources":["../../src/components/FullCard.tsx"],"names":[],"mappings":";AAAA,OAAc,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AA2C1C;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,UAAU,CAChC,CACE,EACE,SAAS,GAAG,OAAO,EACnB,OAAO,EACP,QAAQ,EACR,WAAW,EACX,OAAO,EACP,KAAK,EACL,YAAY,GAAG,CAAC,EAChB,SAAS,EACT,QAAQ,EACR,GAAG,IAAI,EACR,EACD,GAAG,EACH,EAAE;IACF,MAAM,OAAO,GAAG,IAAI,YAAY,EAAW,CAAC;IAE5C,OAAO,CACL,sBACM,IAAI,EACR,GAAG,EAAE,GAAG,EACR,SAAS,EAAE;YACT,eAAe;YACf,wBAAwB,SAAS,EAAE;YACnC,SAAS;SACV;aACE,MAAM,CAAC,OAAO,CAAC;aACf,IAAI,CAAC,GAAG,CAAC,aAQZ,cAAK,SAAS,EAAC,wBAAwB,YACrC,eAAK,SAAS,EAAC,qBAAqB,aACjC,OAAO,IAAI,cAAK,SAAS,EAAC,wBAAwB,YAAE,OAAO,GAAO,EACnE,KAAC,OAAO,IAAC,SAAS,EAAC,yBAAyB,YAAE,QAAQ,GAAW,EAChE,WAAW,IAAI,CACd,YAAG,SAAS,EAAC,4BAA4B,YAAE,WAAW,GAAK,CAC5D,EACA,OAAO,IAAI,cAAK,SAAS,EAAC,wBAAwB,YAAE,OAAO,GAAO,EAClE,QAAQ,IACL,GACF,EAQN,cAAK,SAAS,EAAC,sBAAsB,YACnC,cAAK,SAAS,EAAC,0BAA0B,YACvC,cAAK,SAAS,EAAC,4BAA4B,YACzC,cAAK,SAAS,EAAC,6BAA6B,YAAE,KAAK,GAAO,GACtD,GACF,GACF,IACE,CACX,CAAC;AACJ,CAAC,CACF,CAAC;AAEF,QAAQ,CAAC,WAAW,GAAG,UAAU,CAAC"}
|
|
@@ -1,22 +1,54 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
export interface HeaderProps extends React.HTMLAttributes<HTMLElement> {
|
|
2
|
+
export interface HeaderProps extends Omit<React.HTMLAttributes<HTMLElement>, 'onToggle'> {
|
|
3
3
|
/** Figma's Logo slot. Rendered at the start, never shrinks. */
|
|
4
4
|
brand?: React.ReactNode;
|
|
5
|
-
/** Figma's `Center Slot
|
|
5
|
+
/** Figma's `Center Slot` — nav items. Inline on Desktop and Tablet; moves
|
|
6
|
+
* into the Mobile-Open menu below the mobile breakpoint. */
|
|
6
7
|
center?: React.ReactNode;
|
|
7
|
-
/** Figma's `End Slot`. */
|
|
8
|
+
/** Figma's `End Slot` — buttons and actions. Same placement rules as `center`. */
|
|
8
9
|
end?: React.ReactNode;
|
|
10
|
+
/** Controlled Mobile-Open state. Pair with `onOpenChange`. */
|
|
11
|
+
open?: boolean;
|
|
12
|
+
/** Initial Mobile-Open state when uncontrolled. */
|
|
13
|
+
defaultOpen?: boolean;
|
|
14
|
+
/** Fires whenever the menu toggle changes the state, controlled or not. */
|
|
15
|
+
onOpenChange?: (open: boolean) => void;
|
|
16
|
+
/** Accessible name for the mobile menu toggle. */
|
|
17
|
+
menuLabel?: string;
|
|
9
18
|
children?: React.ReactNode;
|
|
10
19
|
}
|
|
11
20
|
/**
|
|
12
|
-
* Header renders Figma's
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
21
|
+
* Header renders Figma's four Device variants — Desktop, Tablet,
|
|
22
|
+
* Mobile-Closed, Mobile-Open — from one DOM tree.
|
|
23
|
+
*
|
|
24
|
+
* TWO AXES, AND THEY ARE NOT THE SAME KIND OF THING
|
|
25
|
+
*
|
|
26
|
+
* Figma spells Device as a single four-way variant, but it is really two: a
|
|
27
|
+
* *breakpoint* (Desktop / Tablet / Mobile) and a *state* (Closed / Open). They
|
|
28
|
+
* are modelled differently here because they are known by different people.
|
|
29
|
+
*
|
|
30
|
+
* Breakpoint is a media query. It is the one variant axis in the system the
|
|
31
|
+
* browser already knows the answer to — a header is Mobile because the
|
|
32
|
+
* viewport is narrow — and having React duplicate that judgement is how the
|
|
33
|
+
* two drift apart. Breakpoints match the Breakpoint collection's container
|
|
34
|
+
* widths: Tablet below 1216, Mobile below 896.
|
|
35
|
+
*
|
|
36
|
+
* Open/Closed is a prop, because only the caller knows. It follows the
|
|
37
|
+
* controlled/uncontrolled pair the rest of the system uses.
|
|
38
|
+
*
|
|
39
|
+
* ONE TREE, TWO LAYOUTS
|
|
40
|
+
*
|
|
41
|
+
* `center` and `end` sit inline in the bar on Desktop and Tablet, and inside
|
|
42
|
+
* the dropped Menu-Container on Mobile-Open. They are rendered once, in one
|
|
43
|
+
* wrapper, which is `display: contents` above the mobile breakpoint and an
|
|
44
|
+
* absolutely positioned panel below it. Rendering them twice — or moving them
|
|
45
|
+
* with JavaScript — would mean the same nav link exists twice in the
|
|
46
|
+
* accessibility tree, and would reset any state a caller put in a slot every
|
|
47
|
+
* time the viewport crossed 896px.
|
|
48
|
+
*
|
|
49
|
+
* The centre slot is no longer hidden on Tablet. Figma's Tablet variant ships
|
|
50
|
+
* it populated; the previous release collapsed it, which was correct for the
|
|
51
|
+
* previous design and is not for this one.
|
|
20
52
|
*
|
|
21
53
|
* A `<header>` element with no explicit role: it is a landmark already when it
|
|
22
54
|
* is a direct child of body, which is where a page header sits.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Header.d.ts","sourceRoot":"","sources":["../../src/components/Header.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Header.d.ts","sourceRoot":"","sources":["../../src/components/Header.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAmD,MAAM,OAAO,CAAC;AA2BxE,MAAM,WAAW,WAAY,SAAQ,IAAI,CACvC,KAAK,CAAC,cAAc,CAAC,WAAW,CAAC,EACjC,UAAU,CACX;IACC,+DAA+D;IAC/D,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACxB;iEAC6D;IAC7D,MAAM,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACzB,kFAAkF;IAClF,GAAG,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACtB,8DAA8D;IAC9D,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,mDAAmD;IACnD,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,2EAA2E;IAC3E,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACvC,kDAAkD;IAClD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,eAAO,MAAM,MAAM,iFAwElB,CAAC"}
|
|
@@ -1,18 +1,70 @@
|
|
|
1
|
+
'use client';
|
|
1
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { forwardRef } from 'react';
|
|
3
|
+
import { forwardRef, useCallback, useId, useState } from 'react';
|
|
4
|
+
/** Figma's `Icon` on the Mobile-Closed toggle. Inlined for the same reason
|
|
5
|
+
* NavItem's chevron is: it is part of the control, not a slot a caller fills. */
|
|
6
|
+
const MenuGlyph = () => (_jsx("svg", { viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", focusable: "false", children: _jsx("path", { d: "M3 6h18M3 12h18M3 18h18", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round" }) }));
|
|
7
|
+
/** Figma's `Icon` on the Mobile-Open toggle. */
|
|
8
|
+
const CloseGlyph = () => (_jsx("svg", { viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", focusable: "false", children: _jsx("path", { d: "m5 5 14 14M19 5 5 19", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round" }) }));
|
|
3
9
|
/**
|
|
4
|
-
* Header renders Figma's
|
|
5
|
-
*
|
|
10
|
+
* Header renders Figma's four Device variants — Desktop, Tablet,
|
|
11
|
+
* Mobile-Closed, Mobile-Open — from one DOM tree.
|
|
6
12
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
13
|
+
* TWO AXES, AND THEY ARE NOT THE SAME KIND OF THING
|
|
14
|
+
*
|
|
15
|
+
* Figma spells Device as a single four-way variant, but it is really two: a
|
|
16
|
+
* *breakpoint* (Desktop / Tablet / Mobile) and a *state* (Closed / Open). They
|
|
17
|
+
* are modelled differently here because they are known by different people.
|
|
18
|
+
*
|
|
19
|
+
* Breakpoint is a media query. It is the one variant axis in the system the
|
|
20
|
+
* browser already knows the answer to — a header is Mobile because the
|
|
21
|
+
* viewport is narrow — and having React duplicate that judgement is how the
|
|
22
|
+
* two drift apart. Breakpoints match the Breakpoint collection's container
|
|
23
|
+
* widths: Tablet below 1216, Mobile below 896.
|
|
24
|
+
*
|
|
25
|
+
* Open/Closed is a prop, because only the caller knows. It follows the
|
|
26
|
+
* controlled/uncontrolled pair the rest of the system uses.
|
|
27
|
+
*
|
|
28
|
+
* ONE TREE, TWO LAYOUTS
|
|
29
|
+
*
|
|
30
|
+
* `center` and `end` sit inline in the bar on Desktop and Tablet, and inside
|
|
31
|
+
* the dropped Menu-Container on Mobile-Open. They are rendered once, in one
|
|
32
|
+
* wrapper, which is `display: contents` above the mobile breakpoint and an
|
|
33
|
+
* absolutely positioned panel below it. Rendering them twice — or moving them
|
|
34
|
+
* with JavaScript — would mean the same nav link exists twice in the
|
|
35
|
+
* accessibility tree, and would reset any state a caller put in a slot every
|
|
36
|
+
* time the viewport crossed 896px.
|
|
37
|
+
*
|
|
38
|
+
* The centre slot is no longer hidden on Tablet. Figma's Tablet variant ships
|
|
39
|
+
* it populated; the previous release collapsed it, which was correct for the
|
|
40
|
+
* previous design and is not for this one.
|
|
12
41
|
*
|
|
13
42
|
* A `<header>` element with no explicit role: it is a landmark already when it
|
|
14
43
|
* is a direct child of body, which is where a page header sits.
|
|
15
44
|
*/
|
|
16
|
-
export const Header = forwardRef(({ brand, center, end,
|
|
45
|
+
export const Header = forwardRef(({ brand, center, end, open: openProp, defaultOpen = false, onOpenChange, menuLabel = 'Menu', className, children, ...rest }, ref) => {
|
|
46
|
+
const menuId = useId();
|
|
47
|
+
const [uncontrolledOpen, setUncontrolledOpen] = useState(defaultOpen);
|
|
48
|
+
const isControlled = openProp !== undefined;
|
|
49
|
+
const isOpen = isControlled ? openProp : uncontrolledOpen;
|
|
50
|
+
const toggle = useCallback(() => {
|
|
51
|
+
const next = !isOpen;
|
|
52
|
+
if (!isControlled)
|
|
53
|
+
setUncontrolledOpen(next);
|
|
54
|
+
onOpenChange?.(next);
|
|
55
|
+
}, [isOpen, isControlled, onOpenChange]);
|
|
56
|
+
/*
|
|
57
|
+
* Escape closes the menu. It is a disclosure rather than a modal — no focus
|
|
58
|
+
* trap, no scroll lock — so Escape is the whole of the dismissal contract,
|
|
59
|
+
* and it is handled on the header rather than the document so a header that
|
|
60
|
+
* is never open never listens.
|
|
61
|
+
*/
|
|
62
|
+
const handleKeyDown = (event) => {
|
|
63
|
+
if (event.key === 'Escape' && isOpen)
|
|
64
|
+
toggle();
|
|
65
|
+
rest.onKeyDown?.(event);
|
|
66
|
+
};
|
|
67
|
+
return (_jsxs("header", { ...rest, onKeyDown: handleKeyDown, ref: ref, "data-open": isOpen || undefined, className: ['ion-header', className].filter(Boolean).join(' '), children: [brand && _jsx("div", { className: "ion-header__brand", children: brand }), _jsxs("div", { className: "ion-header__menu", id: menuId, children: [_jsx("div", { className: "ion-header__center", children: center }), _jsx("div", { className: "ion-header__end", children: end })] }), children, _jsx("button", { type: "button", className: "ion-header__toggle", "aria-expanded": isOpen, "aria-controls": menuId, "aria-label": menuLabel, onClick: toggle, children: isOpen ? _jsx(CloseGlyph, {}) : _jsx(MenuGlyph, {}) })] }));
|
|
68
|
+
});
|
|
17
69
|
Header.displayName = 'Header';
|
|
18
70
|
//# sourceMappingURL=Header.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Header.js","sourceRoot":"","sources":["../../src/components/Header.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Header.js","sourceRoot":"","sources":["../../src/components/Header.tsx"],"names":[],"mappings":"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"}
|
|
@@ -30,4 +30,6 @@ export { Table, TableHead, TableBody, TableRow, TableCell } from './Table.js';
|
|
|
30
30
|
export type { TableProps, TableDensity, TableRowProps, TableRowSelection, TableCellProps, TableCellAlign, TableCellScope, } from './Table.js';
|
|
31
31
|
export { ScrollProgress } from './ScrollProgress.js';
|
|
32
32
|
export type { ScrollProgressProps, ScrollProgressSection, } from './ScrollProgress.js';
|
|
33
|
+
export { FullCard } from './FullCard.js';
|
|
34
|
+
export type { FullCardProps, FullCardAlignment, FullCardHeadingLevel, } from './FullCard.js';
|
|
33
35
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC1C,YAAY,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC/D,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC1D,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,YAAY,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACxD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACzE,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,YAAY,EACV,aAAa,EACb,YAAY,EACZ,cAAc,GACf,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC/C,YAAY,EACV,UAAU,EACV,eAAe,EACf,SAAS,EACT,WAAW,GACZ,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACzE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC3C,YAAY,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC1D,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAClD,YAAY,EACV,WAAW,EACX,gBAAgB,EAChB,UAAU,EACV,WAAW,GACZ,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC3C,YAAY,EACV,SAAS,EACT,aAAa,EACb,QAAQ,EACR,YAAY,GACb,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,YAAY,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AACrE,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,YAAY,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC9E,YAAY,EACV,UAAU,EACV,YAAY,EACZ,aAAa,EACb,iBAAiB,EACjB,cAAc,EACd,cAAc,EACd,cAAc,GACf,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,YAAY,EACV,mBAAmB,EACnB,qBAAqB,GACtB,MAAM,qBAAqB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC1C,YAAY,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC/D,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC1D,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,YAAY,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACxD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACzE,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,YAAY,EACV,aAAa,EACb,YAAY,EACZ,cAAc,GACf,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC/C,YAAY,EACV,UAAU,EACV,eAAe,EACf,SAAS,EACT,WAAW,GACZ,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACzE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC3C,YAAY,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC1D,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAClD,YAAY,EACV,WAAW,EACX,gBAAgB,EAChB,UAAU,EACV,WAAW,GACZ,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC3C,YAAY,EACV,SAAS,EACT,aAAa,EACb,QAAQ,EACR,YAAY,GACb,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,YAAY,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AACrE,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,YAAY,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC9E,YAAY,EACV,UAAU,EACV,YAAY,EACZ,aAAa,EACb,iBAAiB,EACjB,cAAc,EACd,cAAc,EACd,cAAc,GACf,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,YAAY,EACV,mBAAmB,EACnB,qBAAqB,GACtB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,YAAY,EACV,aAAa,EACb,iBAAiB,EACjB,oBAAoB,GACrB,MAAM,eAAe,CAAC"}
|
package/dist/components/index.js
CHANGED
|
@@ -14,4 +14,5 @@ export { Divider } from './Divider.js';
|
|
|
14
14
|
export { NavItem } from './NavItem.js';
|
|
15
15
|
export { Table, TableHead, TableBody, TableRow, TableCell } from './Table.js';
|
|
16
16
|
export { ScrollProgress } from './ScrollProgress.js';
|
|
17
|
+
export { FullCard } from './FullCard.js';
|
|
17
18
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAE1C,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEnC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEnC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAMzC,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAO/C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAE3C,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAOlD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAO3C,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAU9E,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAE1C,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEnC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEnC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAMzC,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAO/C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAE3C,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAOlD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAO3C,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAU9E,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAKrD,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC"}
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Full Card
|
|
3
|
+
*
|
|
4
|
+
* Measured from Figma `Full Card` (592:857) — a component set with one axis,
|
|
5
|
+
* `Alignment`, whose `Right` (590:189) and `Left` (592:858) variants mirror the
|
|
6
|
+
* media panel across the card.
|
|
7
|
+
*
|
|
8
|
+
* content column padding 0 / 48, centred, hugs half the width
|
|
9
|
+
* media column surface/page, 1px rule facing the content, padding-top 48
|
|
10
|
+
* media mat surface/sunken, 8px inset, radius/6xl on the outer top corner
|
|
11
|
+
* media frame surface/default, 1px rule on three sides, radius/5xl
|
|
12
|
+
*
|
|
13
|
+
* WHAT WAS NOT COPIED FROM FIGMA, AND WHY
|
|
14
|
+
*
|
|
15
|
+
* 1. The 366px height and 1296px width are canvas measurements, not
|
|
16
|
+
* constraints. 366 is what the parts add up to — 48 (column) + 8 (mat) +
|
|
17
|
+
* 310 (screen) — so it falls out of the box model and stating it again
|
|
18
|
+
* would only pin the card to one media size. 1296 is the desktop container
|
|
19
|
+
* width, which belongs to the page, not the card.
|
|
20
|
+
*
|
|
21
|
+
* 2. The description container's gap reads 9px in Figma. There is no 9 on the
|
|
22
|
+
* spacing ladder and no token that resolves to one, so it is a nudge rather
|
|
23
|
+
* than a decision; this uses `spacing/8`. Change it in Figma if the 9 was
|
|
24
|
+
* meant, and it becomes a real token here.
|
|
25
|
+
*
|
|
26
|
+
* 3. The left variant carries its media rule on the media column's LEFT edge —
|
|
27
|
+
* i.e. the card's outer edge — while the right variant carries it on the
|
|
28
|
+
* edge facing the text. Only one of those is a divider; the other is a
|
|
29
|
+
* stray line at the boundary of the card. Read as a mirroring oversight in
|
|
30
|
+
* Figma, so both variants draw the rule on the inner edge here. Worth
|
|
31
|
+
* fixing in the file, since the two disagree today.
|
|
32
|
+
*
|
|
33
|
+
* 4. The left variant's media column also hard-codes a 48px top padding where
|
|
34
|
+
* the right variant binds `spacing/48`. Same oversight, same resolution:
|
|
35
|
+
* the token, both times.
|
|
36
|
+
*
|
|
37
|
+
* 5. Figma has no narrow variant of this card, and a 1296-wide two-column row
|
|
38
|
+
* is unusable below its own width. The stacked layout is repo-owned, on the
|
|
39
|
+
* same reasoning as Header's device media queries: the browser knows the
|
|
40
|
+
* viewport, so the caller should not have to say.
|
|
41
|
+
*
|
|
42
|
+
* The split holds from 1080 — a chosen number, not a Breakpoint collection
|
|
43
|
+
* container width, so it is stated here rather than in the token pipeline.
|
|
44
|
+
* The two halves are even only while there is room for it: below roughly
|
|
45
|
+
* 1190 the media column holds at the panel plus its gutter and the text
|
|
46
|
+
* column takes what is left, bottoming out near 375px of text at 1080. The
|
|
47
|
+
* 896 query below changes no layout at all — only the text gutter.
|
|
48
|
+
*
|
|
49
|
+
* STACKED PUTS THE MEDIA ON TOP, IN BOTH ALIGNMENTS
|
|
50
|
+
*
|
|
51
|
+
* `column-reverse`, so the DOM stays content-then-media while the media renders
|
|
52
|
+
* above the text. Two reasons it is the media rather than the headline:
|
|
53
|
+
*
|
|
54
|
+
* - `Alignment` stops meaning anything once there is only one column. Letting
|
|
55
|
+
* it decide the vertical order would turn one axis into two, and a card
|
|
56
|
+
* whose text sits above the panel on some rows and below it on others reads
|
|
57
|
+
* as a bug rather than a variant.
|
|
58
|
+
* - The panel is the thing the card is about. Stacked, it is also the only
|
|
59
|
+
* part with a fixed height, so putting it second would push it below the
|
|
60
|
+
* fold on every narrow viewport.
|
|
61
|
+
*/
|
|
62
|
+
.ion-full-card {
|
|
63
|
+
--ion-full-card-padding-x: var(--spacing-24);
|
|
64
|
+
--ion-full-card-padding-y: var(--spacing-32);
|
|
65
|
+
--ion-full-card-gap: var(--spacing-8);
|
|
66
|
+
--ion-full-card-media-width: 562px;
|
|
67
|
+
|
|
68
|
+
/*
|
|
69
|
+
* The least space allowed between the centre rule and the media panel.
|
|
70
|
+
* Figma's is 86 at the design width (648 column − 562 panel), which is not on
|
|
71
|
+
* the spacing ladder and is a consequence of the 50/50 split rather than a
|
|
72
|
+
* decision. `spacing/48` is the floor, and it is the same 48 the panel is
|
|
73
|
+
* already inset from the top of the card — so when the row is too narrow for
|
|
74
|
+
* an even split, the panel keeps a gutter that matches its own top edge.
|
|
75
|
+
*/
|
|
76
|
+
--ion-full-card-media-gutter: var(--spacing-48);
|
|
77
|
+
|
|
78
|
+
/*
|
|
79
|
+
* Placeholder geometry: the height of Figma's empty `Screen Container`. It
|
|
80
|
+
* is a floor, not a size — real media in the slot sets the height and this
|
|
81
|
+
* stops an empty card collapsing. Override per instance if the media has a
|
|
82
|
+
* known aspect ratio.
|
|
83
|
+
*/
|
|
84
|
+
--ion-full-card-media-min-height: 310px;
|
|
85
|
+
|
|
86
|
+
display: flex;
|
|
87
|
+
flex-direction: column-reverse;
|
|
88
|
+
width: 100%;
|
|
89
|
+
border-top: var(--border-width-default) solid var(--border-default);
|
|
90
|
+
border-bottom: var(--border-width-default) solid var(--border-default);
|
|
91
|
+
font-family: var(--font-family-sans);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/*
|
|
95
|
+
* `flex: 0 0 auto` while stacked, not the `1 1 0` the split uses. In a column
|
|
96
|
+
* flex container a zero basis is a zero HEIGHT that then grows to fill — which
|
|
97
|
+
* is the two halves splitting the viewport instead of sizing to their content.
|
|
98
|
+
* The split re-states `1 1 0` at Desktop, where the axis is horizontal and a
|
|
99
|
+
* zero basis is what makes the halves equal regardless of text length.
|
|
100
|
+
*/
|
|
101
|
+
.ion-full-card__content {
|
|
102
|
+
display: flex;
|
|
103
|
+
flex-direction: column;
|
|
104
|
+
justify-content: center;
|
|
105
|
+
flex: 0 0 auto;
|
|
106
|
+
min-width: 0;
|
|
107
|
+
padding: var(--ion-full-card-padding-y) var(--ion-full-card-padding-x);
|
|
108
|
+
overflow: hidden;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.ion-full-card__body {
|
|
112
|
+
display: flex;
|
|
113
|
+
flex-direction: column;
|
|
114
|
+
align-items: flex-start;
|
|
115
|
+
gap: var(--ion-full-card-gap);
|
|
116
|
+
width: 100%;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.ion-full-card__eyebrow,
|
|
120
|
+
.ion-full-card__actions {
|
|
121
|
+
display: flex;
|
|
122
|
+
align-items: center;
|
|
123
|
+
gap: var(--spacing-8);
|
|
124
|
+
flex-wrap: wrap;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/*
|
|
128
|
+
* The type styles are restated rather than delegated to `.ion-text-h3` /
|
|
129
|
+
* `.ion-text-body-sm`, so the card renders correctly whatever classes the
|
|
130
|
+
* caller puts on the headline. Both sets read the same breakpoint-scoped
|
|
131
|
+
* `type/*` tokens, so they resize together.
|
|
132
|
+
*/
|
|
133
|
+
.ion-full-card__headline {
|
|
134
|
+
margin: 0;
|
|
135
|
+
font-family: var(--font-family-sans);
|
|
136
|
+
font-weight: var(--font-weight-semibold);
|
|
137
|
+
font-size: var(--type-h3);
|
|
138
|
+
line-height: var(--type-h3-line-height);
|
|
139
|
+
color: var(--text-default);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.ion-full-card__description {
|
|
143
|
+
margin: 0;
|
|
144
|
+
font-family: var(--font-family-sans);
|
|
145
|
+
font-weight: var(--font-weight-regular);
|
|
146
|
+
font-size: var(--type-body-sm);
|
|
147
|
+
line-height: var(--type-body-sm-line-height);
|
|
148
|
+
color: var(--text-secondary);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.ion-full-card__media {
|
|
152
|
+
display: flex;
|
|
153
|
+
flex-direction: column;
|
|
154
|
+
justify-content: flex-end;
|
|
155
|
+
align-items: stretch;
|
|
156
|
+
flex: 0 0 auto;
|
|
157
|
+
min-width: 0;
|
|
158
|
+
padding-top: var(--spacing-48);
|
|
159
|
+
|
|
160
|
+
/*
|
|
161
|
+
* Stacked, the panel is inset from the card's left and right edges too, not
|
|
162
|
+
* just the top — and by the text's own gutter, so the panel and the headline
|
|
163
|
+
* share an edge instead of sitting on two arbitrary ones. In the split the
|
|
164
|
+
* panel deliberately bleeds off the outer edge, so this resets to zero there.
|
|
165
|
+
*/
|
|
166
|
+
padding-inline: var(--ion-full-card-padding-x);
|
|
167
|
+
background-color: var(--surface-page);
|
|
168
|
+
|
|
169
|
+
/*
|
|
170
|
+
* The stacked analogue of the vertical rule between the two halves. The
|
|
171
|
+
* frame deliberately has no bottom border, so without this the white screen
|
|
172
|
+
* would run straight into the text with nothing separating them.
|
|
173
|
+
*/
|
|
174
|
+
border-bottom: var(--border-width-default) solid var(--border-default);
|
|
175
|
+
overflow: hidden;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.ion-full-card__media-mat {
|
|
179
|
+
width: 100%;
|
|
180
|
+
padding-top: var(--spacing-8);
|
|
181
|
+
padding-inline: var(--spacing-8);
|
|
182
|
+
background-color: var(--surface-sunken);
|
|
183
|
+
border-start-start-radius: var(--radius-6xl);
|
|
184
|
+
border-start-end-radius: var(--radius-6xl);
|
|
185
|
+
overflow: hidden;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/*
|
|
189
|
+
* Three sides only. The card is a full-bleed band and the panel runs off its
|
|
190
|
+
* bottom edge, so a fourth rule would draw a line the design does not have.
|
|
191
|
+
*/
|
|
192
|
+
.ion-full-card__media-frame {
|
|
193
|
+
width: 100%;
|
|
194
|
+
border: var(--border-width-default) solid var(--border-default);
|
|
195
|
+
border-bottom: none;
|
|
196
|
+
border-start-start-radius: var(--radius-5xl);
|
|
197
|
+
border-start-end-radius: var(--radius-5xl);
|
|
198
|
+
overflow: hidden;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
.ion-full-card__media-screen {
|
|
202
|
+
width: 100%;
|
|
203
|
+
min-height: var(--ion-full-card-media-min-height);
|
|
204
|
+
background-color: var(--surface-default);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/* Tablet is still stacked; only the text gutter opens up. */
|
|
208
|
+
@media (width >= 896px) {
|
|
209
|
+
.ion-full-card {
|
|
210
|
+
--ion-full-card-padding-x: var(--spacing-32);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/*
|
|
215
|
+
* Side by side from 1080 up. Below it the card stacks, media first, the panel
|
|
216
|
+
* takes the text's own gutters and both its top corners round rather than the
|
|
217
|
+
* single outer one.
|
|
218
|
+
*/
|
|
219
|
+
@media (width >= 1080px) {
|
|
220
|
+
.ion-full-card {
|
|
221
|
+
--ion-full-card-padding-x: var(--spacing-48);
|
|
222
|
+
--ion-full-card-padding-y: var(--spacing-0);
|
|
223
|
+
|
|
224
|
+
flex-direction: row;
|
|
225
|
+
align-items: stretch;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.ion-full-card__content {
|
|
229
|
+
flex: 1 1 0;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
.ion-full-card--media-left {
|
|
233
|
+
/*
|
|
234
|
+
* Reversed here, not in the DOM: the markup stays headline → actions →
|
|
235
|
+
* media in both alignments so reading and focus order follow the content
|
|
236
|
+
* rather than the mirror.
|
|
237
|
+
*/
|
|
238
|
+
flex-direction: row-reverse;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/*
|
|
242
|
+
* `1 1 0` for the even split, floored so the panel is never the thing that
|
|
243
|
+
* gives. Below roughly 1190 the halves are narrower than the panel, and
|
|
244
|
+
* without the floor the panel shrank to fill its column — losing the gutter
|
|
245
|
+
* against the centre rule and reading as a full-bleed block right up to the
|
|
246
|
+
* moment the card stacked. The text column absorbs the difference instead:
|
|
247
|
+
* a paragraph reflows, and a panel with no gutter is a different design.
|
|
248
|
+
*/
|
|
249
|
+
.ion-full-card__media {
|
|
250
|
+
align-items: flex-end;
|
|
251
|
+
flex: 1 1 0;
|
|
252
|
+
min-width: calc(
|
|
253
|
+
var(--ion-full-card-media-width) + var(--ion-full-card-media-gutter)
|
|
254
|
+
);
|
|
255
|
+
padding-inline: var(--spacing-0);
|
|
256
|
+
border-bottom: none;
|
|
257
|
+
border-inline-start: var(--border-width-default) solid var(--border-default);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
.ion-full-card--media-left .ion-full-card__media {
|
|
261
|
+
align-items: flex-start;
|
|
262
|
+
border-inline-start: none;
|
|
263
|
+
border-inline-end: var(--border-width-default) solid var(--border-default);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
.ion-full-card__media-mat {
|
|
267
|
+
width: var(--ion-full-card-media-width);
|
|
268
|
+
max-width: 100%;
|
|
269
|
+
padding-inline: var(--spacing-8) var(--spacing-0);
|
|
270
|
+
border-start-end-radius: var(--radius-none);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
.ion-full-card--media-left .ion-full-card__media-mat {
|
|
274
|
+
padding-inline: var(--spacing-0) var(--spacing-8);
|
|
275
|
+
border-start-start-radius: var(--radius-none);
|
|
276
|
+
border-start-end-radius: var(--radius-6xl);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
.ion-full-card__media-frame {
|
|
280
|
+
border-start-end-radius: var(--radius-none);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
.ion-full-card--media-left .ion-full-card__media-frame {
|
|
284
|
+
border-start-start-radius: var(--radius-none);
|
|
285
|
+
border-start-end-radius: var(--radius-5xl);
|
|
286
|
+
}
|
|
287
|
+
}
|