@transferwise/components 46.166.0 → 46.167.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/build/CardContainer/CardContainer.js +34 -0
- package/build/CardContainer/CardContainer.js.map +1 -0
- package/build/CardContainer/CardContainer.mjs +30 -0
- package/build/CardContainer/CardContainer.mjs.map +1 -0
- package/build/CardWidget/CardWidget.js +98 -0
- package/build/CardWidget/CardWidget.js.map +1 -0
- package/build/CardWidget/CardWidget.mjs +94 -0
- package/build/CardWidget/CardWidget.mjs.map +1 -0
- package/build/CardWidget/CardWidgetAction.js +73 -0
- package/build/CardWidget/CardWidgetAction.js.map +1 -0
- package/build/CardWidget/CardWidgetAction.mjs +68 -0
- package/build/CardWidget/CardWidgetAction.mjs.map +1 -0
- package/build/ListItem/ListItem.js +2 -0
- package/build/ListItem/ListItem.js.map +1 -1
- package/build/ListItem/ListItem.mjs +2 -0
- package/build/ListItem/ListItem.mjs.map +1 -1
- package/build/index.js +4 -0
- package/build/index.js.map +1 -1
- package/build/index.mjs +2 -0
- package/build/index.mjs.map +1 -1
- package/build/main.css +194 -0
- package/build/styles/CardContainer/CardContainer.css +108 -0
- package/build/styles/CardWidget/CardWidget.css +78 -0
- package/build/styles/ListItem/ListItem.css +8 -0
- package/build/styles/main.css +194 -0
- package/build/types/Body/Body.d.ts +2 -2
- package/build/types/CardContainer/CardContainer.d.ts +8 -0
- package/build/types/CardContainer/CardContainer.d.ts.map +1 -0
- package/build/types/CardContainer/CardContainer.types.d.ts +55 -0
- package/build/types/CardContainer/CardContainer.types.d.ts.map +1 -0
- package/build/types/CardContainer/_stories/helpers.d.ts +3 -0
- package/build/types/CardContainer/_stories/helpers.d.ts.map +1 -0
- package/build/types/CardContainer/index.d.ts +3 -0
- package/build/types/CardContainer/index.d.ts.map +1 -0
- package/build/types/CardWidget/CardWidget.d.ts +10 -0
- package/build/types/CardWidget/CardWidget.d.ts.map +1 -0
- package/build/types/CardWidget/CardWidget.types.d.ts +55 -0
- package/build/types/CardWidget/CardWidget.types.d.ts.map +1 -0
- package/build/types/CardWidget/CardWidgetAction.d.ts +54 -0
- package/build/types/CardWidget/CardWidgetAction.d.ts.map +1 -0
- package/build/types/CardWidget/index.d.ts +4 -0
- package/build/types/CardWidget/index.d.ts.map +1 -0
- package/build/types/IconButton/IconButton.d.ts +1 -1
- package/build/types/ListItem/ListItem.d.ts +1 -1
- package/build/types/ListItem/ListItem.d.ts.map +1 -1
- package/build/types/MoneyInput/MoneyInput.d.ts +1 -1
- package/build/types/MoneyInput/MoneyInput.d.ts.map +1 -1
- package/build/types/Title/Title.d.ts +2 -2
- package/build/types/Upload/Steps/UploadImageStep/UploadImageStep.d.ts +1 -1
- package/build/types/UploadInput/UploadItem/UploadItemLink.d.ts +1 -1
- package/build/types/index.d.ts +4 -0
- package/build/types/index.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/CardContainer/CardContainer.css +108 -0
- package/src/CardContainer/CardContainer.less +171 -0
- package/src/CardContainer/CardContainer.test.tsx +150 -0
- package/src/CardContainer/CardContainer.tsx +51 -0
- package/src/CardContainer/CardContainer.types.ts +62 -0
- package/src/CardContainer/_stories/CardContainer.story.tsx +167 -0
- package/src/CardContainer/_stories/CardContainer.test.story.tsx +103 -0
- package/src/CardContainer/_stories/helpers.tsx +50 -0
- package/src/CardContainer/index.ts +6 -0
- package/src/CardWidget/CardWidget.css +78 -0
- package/src/CardWidget/CardWidget.less +107 -0
- package/src/CardWidget/CardWidget.test.tsx +221 -0
- package/src/CardWidget/CardWidget.tsx +78 -0
- package/src/CardWidget/CardWidget.types.ts +63 -0
- package/src/CardWidget/CardWidgetAction.tsx +128 -0
- package/src/CardWidget/_stories/CardWidget.story.tsx +238 -0
- package/src/CardWidget/_stories/CardWidget.test.story.tsx +350 -0
- package/src/CardWidget/index.ts +3 -0
- package/src/ListItem/ListItem.css +8 -0
- package/src/ListItem/ListItem.less +8 -0
- package/src/ListItem/ListItem.test.tsx +20 -0
- package/src/ListItem/ListItem.tsx +3 -1
- package/src/ListItem/_stories/ListItem.layout.test.story.tsx +1 -1
- package/src/ListItem/_stories/ListItem.story.tsx +1 -1
- package/src/Prompt/CriticalBanner/_stories/CriticalBanner.docs.mdx +122 -0
- package/src/Prompt/CriticalBanner/_stories/CriticalBanner.story.tsx +0 -2
- package/src/index.ts +13 -0
- package/src/main.css +194 -0
- package/src/main.less +2 -0
- package/src/FlowNavigation/_stories/FlowNavigation.CriticalBanner.docs.mdx +0 -66
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { Meta, StoryObj } from '@storybook/react-webpack5';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
createSandboxStory,
|
|
5
|
+
globalScope,
|
|
6
|
+
} from '../../../.storybook/components/sandbox/SandboxEditor';
|
|
7
|
+
import Body from '../../Body';
|
|
8
|
+
import Link from '../../Link';
|
|
9
|
+
import CardContainer from '..';
|
|
10
|
+
import type { CardContainerPadding, CardContainerSpotlight } from '..';
|
|
11
|
+
import { CardsList, SampleContent } from './helpers';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* A bounded surface that groups related content. It has no slots of its own: any content passed
|
|
15
|
+
* as children is stacked with no gap, inside the card's padding.
|
|
16
|
+
*
|
|
17
|
+
* For a card with a title, a title link or actions, use `CardWidget`.
|
|
18
|
+
*/
|
|
19
|
+
const meta: Meta<typeof CardContainer> = {
|
|
20
|
+
component: CardContainer,
|
|
21
|
+
title: 'Layouts/CardContainer',
|
|
22
|
+
tags: ['early-access'],
|
|
23
|
+
argTypes: {
|
|
24
|
+
spotlight: {
|
|
25
|
+
control: 'radio',
|
|
26
|
+
options: [undefined, 'active', 'inactive'],
|
|
27
|
+
table: {
|
|
28
|
+
defaultValue: { summary: 'undefined' },
|
|
29
|
+
type: { summary: 'CardContainerSpotlight' },
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
padding: {
|
|
33
|
+
control: 'radio',
|
|
34
|
+
options: ['md', 'none'],
|
|
35
|
+
table: { defaultValue: { summary: "'md'" }, type: { summary: 'CardContainerPadding' } },
|
|
36
|
+
},
|
|
37
|
+
as: {
|
|
38
|
+
control: 'radio',
|
|
39
|
+
options: ['div', 'section', 'article'],
|
|
40
|
+
table: { defaultValue: { summary: "'div'" }, type: { summary: 'string' } },
|
|
41
|
+
},
|
|
42
|
+
interactive: {
|
|
43
|
+
control: 'boolean',
|
|
44
|
+
table: { defaultValue: { summary: 'false' }, type: { summary: 'boolean' } },
|
|
45
|
+
},
|
|
46
|
+
children: {
|
|
47
|
+
control: false,
|
|
48
|
+
table: { type: { summary: 'ReactNode' } },
|
|
49
|
+
},
|
|
50
|
+
className: { control: 'text', table: { type: { summary: 'string' } } },
|
|
51
|
+
'data-testid': { control: 'text', table: { type: { summary: 'string' } } },
|
|
52
|
+
},
|
|
53
|
+
args: {
|
|
54
|
+
spotlight: 'active',
|
|
55
|
+
padding: 'md',
|
|
56
|
+
as: 'div',
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export default meta;
|
|
61
|
+
|
|
62
|
+
type Story = StoryObj<typeof CardContainer>;
|
|
63
|
+
|
|
64
|
+
/** Experiment with the card's props. */
|
|
65
|
+
export const Playground: Story = {
|
|
66
|
+
render: (args) => (
|
|
67
|
+
<CardContainer {...args}>
|
|
68
|
+
<SampleContent />
|
|
69
|
+
</CardContainer>
|
|
70
|
+
),
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export const Sandbox = createSandboxStory({
|
|
74
|
+
code: `<CardContainer spotlight="active">
|
|
75
|
+
<Body>Your virtual card is ready to use.</Body>
|
|
76
|
+
<Body>Spend in 40+ currencies with the real exchange rate.</Body>
|
|
77
|
+
</CardContainer>`,
|
|
78
|
+
scope: { ...globalScope, CardContainer },
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* `spotlight` matches `ListItem`'s: `active` is raised, for live content, and `inactive` is
|
|
83
|
+
* recessed with a dashed stroke, for a feature not set up yet. Without a spotlight the card is
|
|
84
|
+
* flat.
|
|
85
|
+
*/
|
|
86
|
+
export const Spotlight: Story = {
|
|
87
|
+
parameters: { controls: { disable: true } },
|
|
88
|
+
render: () => (
|
|
89
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '24px', maxWidth: '480px' }}>
|
|
90
|
+
{([undefined, 'active', 'inactive'] as (CardContainerSpotlight | undefined)[]).map(
|
|
91
|
+
(spotlight) => (
|
|
92
|
+
<CardContainer key={spotlight ?? 'none'} spotlight={spotlight}>
|
|
93
|
+
<Body>Spotlight: {spotlight ?? 'not set'}</Body>
|
|
94
|
+
</CardContainer>
|
|
95
|
+
),
|
|
96
|
+
)}
|
|
97
|
+
</div>
|
|
98
|
+
),
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* `padding="md"` insets the content by 8px, and by 16px from the `md` breakpoint up.
|
|
103
|
+
* `padding="none"` lets content such as images or charts run to the card's edges, clipped to its
|
|
104
|
+
* rounded corners. The grey boxes fill the content area, so their edges show where the padding
|
|
105
|
+
* ends.
|
|
106
|
+
*
|
|
107
|
+
* Focus rings are clipped at the edges too, so keep `none` for non-interactive content, and lists
|
|
108
|
+
* on `md`.
|
|
109
|
+
*/
|
|
110
|
+
export const Padding: Story = {
|
|
111
|
+
parameters: { controls: { disable: true } },
|
|
112
|
+
render: () => (
|
|
113
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '24px', maxWidth: '480px' }}>
|
|
114
|
+
{(['md', 'none'] as CardContainerPadding[]).map((padding) =>
|
|
115
|
+
([undefined, 'active', 'inactive'] as (CardContainerSpotlight | undefined)[]).map(
|
|
116
|
+
(spotlight) => (
|
|
117
|
+
<CardContainer
|
|
118
|
+
key={`${padding}-${spotlight ?? 'none'}`}
|
|
119
|
+
padding={padding}
|
|
120
|
+
spotlight={spotlight}
|
|
121
|
+
>
|
|
122
|
+
<div
|
|
123
|
+
style={{ height: '120px', background: 'var(--color-background-screen-active)' }}
|
|
124
|
+
/>
|
|
125
|
+
</CardContainer>
|
|
126
|
+
),
|
|
127
|
+
),
|
|
128
|
+
)}
|
|
129
|
+
</div>
|
|
130
|
+
),
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* `interactive` shows the card as hovered or pressed while a direct child link or button is, and
|
|
135
|
+
* as hovered while that child has keyboard focus. The card's padding, and a link nested deeper
|
|
136
|
+
* such as inside text, don't trigger the states.
|
|
137
|
+
*/
|
|
138
|
+
export const Interactive: Story = {
|
|
139
|
+
parameters: { controls: { disable: true } },
|
|
140
|
+
render: () => (
|
|
141
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '24px', maxWidth: '480px' }}>
|
|
142
|
+
{([undefined, 'active', 'inactive'] as (CardContainerSpotlight | undefined)[]).map(
|
|
143
|
+
(spotlight) => (
|
|
144
|
+
<CardContainer key={spotlight ?? 'none'} spotlight={spotlight} interactive>
|
|
145
|
+
<Link href="#cards">Direct child link, spotlight: {spotlight ?? 'not set'}</Link>
|
|
146
|
+
</CardContainer>
|
|
147
|
+
),
|
|
148
|
+
)}
|
|
149
|
+
<CardContainer spotlight="active" interactive>
|
|
150
|
+
<Body>
|
|
151
|
+
A <Link href="#details">nested link</Link> isn't a direct child, so this card has no
|
|
152
|
+
states.
|
|
153
|
+
</Body>
|
|
154
|
+
</CardContainer>
|
|
155
|
+
</div>
|
|
156
|
+
),
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
/** A `List` placed directly in the card. */
|
|
160
|
+
export const WithList: Story = {
|
|
161
|
+
parameters: { controls: { disable: true } },
|
|
162
|
+
render: () => (
|
|
163
|
+
<CardContainer>
|
|
164
|
+
<CardsList />
|
|
165
|
+
</CardContainer>
|
|
166
|
+
),
|
|
167
|
+
};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { Meta, StoryObj } from '@storybook/react-webpack5';
|
|
2
|
+
import { useTheme } from '@wise/components-theming';
|
|
3
|
+
|
|
4
|
+
import { useRtlDiffI18n, withRTLDiff } from '../../../.storybook/decorators/RtlDiffDecorator';
|
|
5
|
+
import { withVariantConfig } from '../../../.storybook/helpers';
|
|
6
|
+
import Body from '../../Body';
|
|
7
|
+
import Title from '../../Title';
|
|
8
|
+
import CardContainer from '..';
|
|
9
|
+
import type { CardContainerProps, CardContainerSpotlight } from '..';
|
|
10
|
+
|
|
11
|
+
const meta: Meta<typeof CardContainer> = {
|
|
12
|
+
component: CardContainer,
|
|
13
|
+
tags: ['!autodocs', '!manifest'],
|
|
14
|
+
title: 'Layouts/CardContainer/Tests',
|
|
15
|
+
};
|
|
16
|
+
export default meta;
|
|
17
|
+
type Story = StoryObj<CardContainerProps>;
|
|
18
|
+
|
|
19
|
+
const SPOTLIGHTS: (CardContainerSpotlight | undefined)[] = [undefined, 'active', 'inactive'];
|
|
20
|
+
|
|
21
|
+
const ThemeTitle = () => {
|
|
22
|
+
const { theme, screenMode } = useTheme();
|
|
23
|
+
return (
|
|
24
|
+
<Title type="title-subsection">
|
|
25
|
+
{theme} / {screenMode}
|
|
26
|
+
</Title>
|
|
27
|
+
);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/** Fills the content area, so a padding-none card's corners show the clipping. */
|
|
31
|
+
const FullBleedContent = () => (
|
|
32
|
+
<div style={{ height: '80px', background: 'var(--color-background-screen-active)' }} />
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Every spotlight against every theme, with and without padding. Without padding, the content
|
|
37
|
+
* should be clipped to the rounded corners, and the `inactive` dashed stroke should stay whole.
|
|
38
|
+
*
|
|
39
|
+
* The `active` spotlight's resting shadow is only applied in the light `personal`, `business` and
|
|
40
|
+
* `platform` themes; every `dark`, `bright-green` and `forest-green` column should show a flat
|
|
41
|
+
* surface.
|
|
42
|
+
*/
|
|
43
|
+
export const AllVariants: Story = {
|
|
44
|
+
...withVariantConfig([
|
|
45
|
+
'default',
|
|
46
|
+
'dark',
|
|
47
|
+
'bright-green',
|
|
48
|
+
'forest-green',
|
|
49
|
+
'business',
|
|
50
|
+
'business--bright-green',
|
|
51
|
+
'business--forest-green',
|
|
52
|
+
'platform',
|
|
53
|
+
'platform--forest-green',
|
|
54
|
+
]),
|
|
55
|
+
render: () => (
|
|
56
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '24px', maxWidth: '420px' }}>
|
|
57
|
+
<ThemeTitle />
|
|
58
|
+
{SPOTLIGHTS.map((spotlight) => (
|
|
59
|
+
<CardContainer key={spotlight ?? 'none'} spotlight={spotlight}>
|
|
60
|
+
<Body>Spotlight: {spotlight ?? 'not set'}</Body>
|
|
61
|
+
<Body>Second line, to show the content stacks flush.</Body>
|
|
62
|
+
</CardContainer>
|
|
63
|
+
))}
|
|
64
|
+
{SPOTLIGHTS.map((spotlight) => (
|
|
65
|
+
<CardContainer key={`none-${spotlight ?? 'none'}`} spotlight={spotlight} padding="none">
|
|
66
|
+
<FullBleedContent />
|
|
67
|
+
</CardContainer>
|
|
68
|
+
))}
|
|
69
|
+
</div>
|
|
70
|
+
),
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* The inactive spotlight's dashed stroke and the padding should mirror in RTL. Without padding,
|
|
75
|
+
* the text should start at the inline-start edge.
|
|
76
|
+
*/
|
|
77
|
+
export const RTLDiff: Story = {
|
|
78
|
+
render: () => {
|
|
79
|
+
const i18n = useRtlDiffI18n();
|
|
80
|
+
return (
|
|
81
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '24px', width: 420 }}>
|
|
82
|
+
{SPOTLIGHTS.map((spotlight) => (
|
|
83
|
+
<CardContainer key={spotlight ?? 'none'} spotlight={spotlight}>
|
|
84
|
+
<Body>{i18n.content}</Body>
|
|
85
|
+
</CardContainer>
|
|
86
|
+
))}
|
|
87
|
+
{SPOTLIGHTS.map((spotlight) => (
|
|
88
|
+
<CardContainer key={`none-${spotlight ?? 'none'}`} spotlight={spotlight} padding="none">
|
|
89
|
+
<Body>{i18n.content}</Body>
|
|
90
|
+
</CardContainer>
|
|
91
|
+
))}
|
|
92
|
+
</div>
|
|
93
|
+
);
|
|
94
|
+
},
|
|
95
|
+
...withRTLDiff({
|
|
96
|
+
i18n: {
|
|
97
|
+
content: {
|
|
98
|
+
en: 'Your virtual card is ready to use.',
|
|
99
|
+
ar: 'بطاقتك الافتراضية جاهزة للاستخدام.',
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
}),
|
|
103
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { Card, Jar, Travel } from '@transferwise/icons';
|
|
2
|
+
|
|
3
|
+
import Body from '../../Body';
|
|
4
|
+
import List from '../../List';
|
|
5
|
+
import ListItem from '../../ListItem';
|
|
6
|
+
|
|
7
|
+
export const SampleContent = () => (
|
|
8
|
+
<>
|
|
9
|
+
<Body>Your virtual card is ready to use.</Body>
|
|
10
|
+
<Body>Spend in 40+ currencies with the real exchange rate.</Body>
|
|
11
|
+
</>
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
export const CardsList = () => (
|
|
15
|
+
<List>
|
|
16
|
+
<ListItem
|
|
17
|
+
title="•••• 1582"
|
|
18
|
+
subtitle="Current account"
|
|
19
|
+
valueTitle="£0 spent"
|
|
20
|
+
media={
|
|
21
|
+
<ListItem.AvatarView>
|
|
22
|
+
<Card />
|
|
23
|
+
</ListItem.AvatarView>
|
|
24
|
+
}
|
|
25
|
+
control={<ListItem.Navigation href="#card-1582" />}
|
|
26
|
+
/>
|
|
27
|
+
<ListItem
|
|
28
|
+
title="•••• 9982"
|
|
29
|
+
subtitle="Jar"
|
|
30
|
+
valueTitle="£410 / £500 day"
|
|
31
|
+
media={
|
|
32
|
+
<ListItem.AvatarView>
|
|
33
|
+
<Jar />
|
|
34
|
+
</ListItem.AvatarView>
|
|
35
|
+
}
|
|
36
|
+
control={<ListItem.Navigation href="#card-9982" />}
|
|
37
|
+
/>
|
|
38
|
+
<ListItem
|
|
39
|
+
title="•••• 5507"
|
|
40
|
+
subtitle="Travel"
|
|
41
|
+
valueTitle="£200 / £500 day"
|
|
42
|
+
media={
|
|
43
|
+
<ListItem.AvatarView>
|
|
44
|
+
<Travel />
|
|
45
|
+
</ListItem.AvatarView>
|
|
46
|
+
}
|
|
47
|
+
control={<ListItem.Navigation href="#card-5507" />}
|
|
48
|
+
/>
|
|
49
|
+
</List>
|
|
50
|
+
);
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
.wds-card-widget {
|
|
2
|
+
--CardWidget-inset: var(--size-8);
|
|
3
|
+
--CardWidget-header-spacing: var(--size-8);
|
|
4
|
+
--CardWidget-actions-gap: var(--size-8);
|
|
5
|
+
}
|
|
6
|
+
.wds-card-widget__header {
|
|
7
|
+
align-items: center;
|
|
8
|
+
-moz-column-gap: var(--size-24);
|
|
9
|
+
column-gap: var(--size-24);
|
|
10
|
+
container-type: inline-size;
|
|
11
|
+
display: flex;
|
|
12
|
+
flex-wrap: wrap;
|
|
13
|
+
padding-inline-start: var(--CardWidget-inset);
|
|
14
|
+
row-gap: var(--size-8);
|
|
15
|
+
}
|
|
16
|
+
.wds-card-widget__header + .wds-card-widget__content {
|
|
17
|
+
margin-block-start: var(--CardWidget-header-spacing);
|
|
18
|
+
}
|
|
19
|
+
.wds-card-widget__title {
|
|
20
|
+
align-items: center;
|
|
21
|
+
color: var(--color-content-secondary);
|
|
22
|
+
display: grid;
|
|
23
|
+
flex: 1 1 0;
|
|
24
|
+
margin: 0;
|
|
25
|
+
min-block-size: var(--size-32);
|
|
26
|
+
}
|
|
27
|
+
@container (width <= 360px) {
|
|
28
|
+
.wds-card-widget__title {
|
|
29
|
+
flex-basis: auto;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
.wds-card-widget__title-link.np-link {
|
|
33
|
+
color: inherit;
|
|
34
|
+
display: inline;
|
|
35
|
+
font: inherit;
|
|
36
|
+
justify-self: start;
|
|
37
|
+
letter-spacing: inherit;
|
|
38
|
+
text-align: start;
|
|
39
|
+
-webkit-text-decoration: none;
|
|
40
|
+
text-decoration: none;
|
|
41
|
+
}
|
|
42
|
+
.wds-card-widget__title-link.np-link:hover,
|
|
43
|
+
.wds-card-widget__title-link.np-link:focus-visible {
|
|
44
|
+
-webkit-text-decoration: underline;
|
|
45
|
+
text-decoration: underline;
|
|
46
|
+
}
|
|
47
|
+
.wds-card-widget__title-link.np-link .tw-icon {
|
|
48
|
+
display: inline-block;
|
|
49
|
+
vertical-align: text-bottom;
|
|
50
|
+
}
|
|
51
|
+
.wds-card-widget__title-link.np-link .wds-card-widget__title-chevron {
|
|
52
|
+
white-space: nowrap;
|
|
53
|
+
}
|
|
54
|
+
.wds-card-widget__title-link.np-link .wds-card-widget__title-chevron .tw-icon {
|
|
55
|
+
margin: 0;
|
|
56
|
+
}
|
|
57
|
+
.wds-card-widget__actions {
|
|
58
|
+
align-self: start;
|
|
59
|
+
align-items: center;
|
|
60
|
+
display: flex;
|
|
61
|
+
flex: none;
|
|
62
|
+
gap: var(--CardWidget-actions-gap);
|
|
63
|
+
}
|
|
64
|
+
.wds-card-widget--with-header .wds-card-widget__content {
|
|
65
|
+
padding-inline: var(--CardWidget-inset);
|
|
66
|
+
}
|
|
67
|
+
.wds-card-widget:has(.wds-card-widget__title-link:hover, .wds-card-widget__title-link:focus-visible).wds-card-container--active {
|
|
68
|
+
--CardContainer-border-color: color-mix(in srgb, var(--color-border-neutral) 75%, transparent);
|
|
69
|
+
}
|
|
70
|
+
.wds-card-widget:has(.wds-card-widget__title-link:hover, .wds-card-widget__title-link:focus-visible).wds-card-container--inactive {
|
|
71
|
+
--CardContainer-background-color: color-mix(in srgb, var(--color-background-neutral-hover) 25%, transparent);
|
|
72
|
+
}
|
|
73
|
+
.wds-card-widget:has(.wds-card-widget__title-link:active).wds-card-container--active {
|
|
74
|
+
--CardContainer-border-color: var(--color-border-neutral);
|
|
75
|
+
}
|
|
76
|
+
.wds-card-widget:has(.wds-card-widget__title-link:active).wds-card-container--inactive {
|
|
77
|
+
--CardContainer-background-color: color-mix(in srgb, var(--color-background-neutral-active) 25%, transparent);
|
|
78
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
@import (reference) "../CardContainer/CardContainer.less";
|
|
2
|
+
|
|
3
|
+
// Above this header width a long title wraps beside the action corner. At or below it, the title
|
|
4
|
+
// only shares a line with the actions while it fits on one line, so it keeps room to read.
|
|
5
|
+
@wds-card-widget-actions-stack-width: 360px;
|
|
6
|
+
|
|
7
|
+
.wds-card-widget {
|
|
8
|
+
--CardWidget-inset: var(--size-8);
|
|
9
|
+
--CardWidget-header-spacing: var(--size-8);
|
|
10
|
+
--CardWidget-actions-gap: var(--size-8);
|
|
11
|
+
|
|
12
|
+
// The header's title row is at least as tall as the 32px action buttons, so cards side by side
|
|
13
|
+
// line up their titles and content whether or not their headers have actions, or have stacked
|
|
14
|
+
// them. The actions wrap under the title when they don't fit beside it.
|
|
15
|
+
&__header {
|
|
16
|
+
align-items: center;
|
|
17
|
+
column-gap: var(--size-24);
|
|
18
|
+
container-type: inline-size;
|
|
19
|
+
display: flex;
|
|
20
|
+
flex-wrap: wrap;
|
|
21
|
+
padding-inline-start: var(--CardWidget-inset);
|
|
22
|
+
row-gap: var(--size-8);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// On the content rather than the header, as an inactive card's decorative border comes after the
|
|
26
|
+
// header, so a header-only widget's header isn't its last child.
|
|
27
|
+
&__header + &__content {
|
|
28
|
+
margin-top: var(--CardWidget-header-spacing);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
&__title {
|
|
32
|
+
align-items: center;
|
|
33
|
+
color: var(--color-content-secondary);
|
|
34
|
+
display: grid;
|
|
35
|
+
flex: 1 1 0;
|
|
36
|
+
margin: 0;
|
|
37
|
+
min-height: var(--size-32);
|
|
38
|
+
|
|
39
|
+
@container (width <= @wds-card-widget-actions-stack-width) {
|
|
40
|
+
flex-basis: auto;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// The title is a `Link`, but keeps the title's typography and colour rather than the link
|
|
45
|
+
// text style, and flows inline so the chevron can follow its last word.
|
|
46
|
+
&__title-link.np-link {
|
|
47
|
+
color: inherit;
|
|
48
|
+
display: inline;
|
|
49
|
+
font: inherit;
|
|
50
|
+
// The title is a grid, which would stretch the link to the full width up to the actions, so
|
|
51
|
+
// its hover and click area would reach past the text.
|
|
52
|
+
justify-self: start;
|
|
53
|
+
letter-spacing: inherit;
|
|
54
|
+
// `Link` renders a button when there is no `href`, which would otherwise centre the title.
|
|
55
|
+
text-align: start;
|
|
56
|
+
text-decoration: none;
|
|
57
|
+
|
|
58
|
+
&:hover,
|
|
59
|
+
&:focus-visible {
|
|
60
|
+
text-decoration: underline;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Inline, so the icons follow the title's last word: the chevron, and the new-tab icon `Link`
|
|
64
|
+
// adds for `target="_blank"`, which `Link` itself lays out as a flex box. Nested so it
|
|
65
|
+
// outweighs `Link`'s own `.np-link .tw-icon`, which loads later.
|
|
66
|
+
.tw-icon {
|
|
67
|
+
display: inline-block;
|
|
68
|
+
vertical-align: text-bottom;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.wds-card-widget__title-chevron {
|
|
72
|
+
white-space: nowrap;
|
|
73
|
+
|
|
74
|
+
.tw-icon {
|
|
75
|
+
margin: 0;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Beside the title, the growing title pushes the actions to the inline end. When they wrap onto
|
|
81
|
+
// their own row under the title, they start at the inline start, under it.
|
|
82
|
+
&__actions {
|
|
83
|
+
// Stays in the top corner when a long title wraps onto more lines.
|
|
84
|
+
align-self: start;
|
|
85
|
+
align-items: center;
|
|
86
|
+
display: flex;
|
|
87
|
+
flex: none;
|
|
88
|
+
gap: var(--CardWidget-actions-gap);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// With a header, the content is inset further from both inline edges.
|
|
92
|
+
&--with-header &__content {
|
|
93
|
+
padding-inline: var(--CardWidget-inset);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// The card's hover and pressed states follow the title link alone, as it leads to the widget's
|
|
97
|
+
// own screen. Hovering or pressing the rest of the card, or its actions, leaves it at rest.
|
|
98
|
+
// Keyboard focus on the link shows the hover state too, so keyboard users see the card react.
|
|
99
|
+
&:has(.wds-card-widget__title-link:hover, .wds-card-widget__title-link:focus-visible) {
|
|
100
|
+
.wds-card-container-hover-state();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// After the hover rule, so pressed wins while the pointer is down.
|
|
104
|
+
&:has(.wds-card-widget__title-link:active) {
|
|
105
|
+
.wds-card-container-pressed-state();
|
|
106
|
+
}
|
|
107
|
+
}
|