@transferwise/components 46.160.7 → 46.161.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/Tag/Tag.js +45 -0
- package/build/Tag/Tag.js.map +1 -0
- package/build/Tag/Tag.mjs +41 -0
- package/build/Tag/Tag.mjs.map +1 -0
- package/build/index.js +2 -0
- package/build/index.js.map +1 -1
- package/build/index.mjs +1 -0
- package/build/index.mjs.map +1 -1
- package/build/main.css +853 -0
- package/build/styles/Inputs/Input/Input.css +88 -0
- package/build/styles/Inputs/TextArea/TextArea.css +88 -0
- package/build/styles/Popover/Popover.css +1 -0
- package/build/styles/Tag/Tag.css +30 -0
- package/build/styles/_deprecated/Select/Select.css +29 -0
- package/build/styles/css/accordion.css +30 -0
- package/build/styles/css/button-groups.css +29 -0
- package/build/styles/css/buttons.css +29 -0
- package/build/styles/css/decision.css +30 -0
- package/build/styles/css/dropdowns.css +59 -0
- package/build/styles/css/droppable.css +29 -0
- package/build/styles/css/footer.css +63 -0
- package/build/styles/css/input-groups.css +189 -0
- package/build/styles/css/list-group.css +1 -0
- package/build/styles/css/navbar.css +59 -0
- package/build/styles/css/neptune-addons.css +3 -0
- package/build/styles/css/neptune-core.css +185 -0
- package/build/styles/css/neptune.css +705 -0
- package/build/styles/css/popovers.css +59 -0
- package/build/styles/css/tooltip.css +29 -0
- package/build/styles/main.css +853 -0
- package/build/styles/styles/less/accordion.css +30 -0
- package/build/styles/styles/less/button-groups.css +29 -0
- package/build/styles/styles/less/buttons.css +29 -0
- package/build/styles/styles/less/decision.css +30 -0
- package/build/styles/styles/less/dropdowns.css +59 -0
- package/build/styles/styles/less/droppable.css +29 -0
- package/build/styles/styles/less/footer.css +63 -0
- package/build/styles/styles/less/forms/bootstrap-forms.css +116 -0
- package/build/styles/styles/less/input-groups.css +189 -0
- package/build/styles/styles/less/list-group.css +1 -0
- package/build/styles/styles/less/navbar.css +59 -0
- package/build/styles/styles/less/neptune-addons.css +3 -0
- package/build/styles/styles/less/neptune-core.css +185 -0
- package/build/styles/styles/less/neptune.css +705 -0
- package/build/styles/styles/less/popovers.css +59 -0
- package/build/styles/styles/less/tooltip.css +29 -0
- package/build/types/Tag/Tag.d.ts +24 -0
- package/build/types/Tag/Tag.d.ts.map +1 -0
- package/build/types/Tag/Tag.types.d.ts +39 -0
- package/build/types/Tag/Tag.types.d.ts.map +1 -0
- package/build/types/Tag/index.d.ts +3 -0
- package/build/types/Tag/index.d.ts.map +1 -0
- package/build/types/index.d.ts +2 -0
- package/build/types/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/Body/_stories/Body.test.story.tsx +81 -0
- package/src/Inputs/Input/Input.css +88 -0
- package/src/Inputs/TextArea/TextArea.css +88 -0
- package/src/Popover/Popover.css +1 -0
- package/src/Tag/Tag.css +30 -0
- package/src/Tag/Tag.less +31 -0
- package/src/Tag/Tag.test.tsx +70 -0
- package/src/Tag/Tag.tsx +71 -0
- package/src/Tag/Tag.types.ts +46 -0
- package/src/Tag/_stories/Tag.story.tsx +245 -0
- package/src/Tag/_stories/Tag.test.story.tsx +121 -0
- package/src/Tag/index.ts +2 -0
- package/src/_deprecated/Card/_stories/Card.test.story.tsx +1 -1
- package/src/_deprecated/CriticalBanner/_stories/CriticalCommsBanner.test.story.tsx +1 -1
- package/src/_deprecated/InlineAlert/_stories/InlineAlert.test.story.tsx +1 -1
- package/src/_deprecated/OverlayHeader/_stories/OverlayHeader.test.story.tsx +1 -1
- package/src/_deprecated/Select/Select.css +29 -0
- package/src/_deprecated/Select/_stories/Select.rtl.test.story.tsx +1 -1
- package/src/_deprecated/Select/_stories/Select.test.story.tsx +1 -1
- package/src/index.ts +2 -0
- package/src/main.css +853 -0
- package/src/main.less +1 -0
- package/src/styles/less/accordion.css +30 -0
- package/src/styles/less/addons/_spacing-utilities.less +2 -0
- package/src/styles/less/button-groups.css +29 -0
- package/src/styles/less/buttons.css +29 -0
- package/src/styles/less/core/_typography.less +12 -0
- package/src/styles/less/decision.css +30 -0
- package/src/styles/less/dropdowns.css +59 -0
- package/src/styles/less/droppable.css +29 -0
- package/src/styles/less/footer.css +63 -0
- package/src/styles/less/forms/bootstrap-forms.css +116 -0
- package/src/styles/less/input-groups.css +189 -0
- package/src/styles/less/list-group.css +1 -0
- package/src/styles/less/navbar.css +59 -0
- package/src/styles/less/neptune-addons.css +3 -0
- package/src/styles/less/neptune-core.css +185 -0
- package/src/styles/less/neptune.css +705 -0
- package/src/styles/less/popovers.css +59 -0
- package/src/styles/less/tooltip.css +29 -0
- package/src/test-utils/assets/card-payment-bakery.webp +0 -0
package/src/Tag/Tag.less
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
.wds-tag {
|
|
2
|
+
display: inline-block;
|
|
3
|
+
max-width: 240px;
|
|
4
|
+
min-width: 0;
|
|
5
|
+
overflow: hidden;
|
|
6
|
+
text-overflow: ellipsis;
|
|
7
|
+
vertical-align: middle;
|
|
8
|
+
white-space: nowrap;
|
|
9
|
+
width: fit-content;
|
|
10
|
+
|
|
11
|
+
&--small {
|
|
12
|
+
border-radius: 6px;
|
|
13
|
+
font-size: var(--font-size-12);
|
|
14
|
+
padding-block: 1px;
|
|
15
|
+
padding-inline: var(--size-4);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
&--large {
|
|
19
|
+
border-radius: 8px;
|
|
20
|
+
padding-block: 1px;
|
|
21
|
+
padding-inline: 6px;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
&.wds-tag--proposition {
|
|
25
|
+
color: var(--color-sentiment-interactive-primary);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.wds-tag-theme {
|
|
30
|
+
display: contents;
|
|
31
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { ThemeProvider } from '@wise/components-theming';
|
|
2
|
+
|
|
3
|
+
import { render, screen } from '../test-utils';
|
|
4
|
+
import Tag from './Tag';
|
|
5
|
+
|
|
6
|
+
describe('Tag', () => {
|
|
7
|
+
it('renders the label as a span', () => {
|
|
8
|
+
render(<Tag label="Tag copy" data-testid="tag" />);
|
|
9
|
+
const tag = screen.getByTestId('tag');
|
|
10
|
+
|
|
11
|
+
expect(tag.tagName).toBe('SPAN');
|
|
12
|
+
expect(tag).toHaveTextContent('Tag copy');
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('defaults to a small, neutral tag', () => {
|
|
16
|
+
render(<Tag label="Tag copy" data-testid="tag" />);
|
|
17
|
+
|
|
18
|
+
expect(screen.getByTestId('tag')).toHaveClass('wds-tag', 'wds-tag--small', 'wds-tag--neutral');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('renders the size as a class', () => {
|
|
22
|
+
render(<Tag label="Tag copy" size="large" data-testid="tag" />);
|
|
23
|
+
|
|
24
|
+
expect(screen.getByTestId('tag')).toHaveClass('wds-tag--large');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('renders the sentiment as a class', () => {
|
|
28
|
+
render(<Tag label="Tag copy" sentiment="success" data-testid="tag" />);
|
|
29
|
+
|
|
30
|
+
expect(screen.getByTestId('tag')).toHaveClass('wds-tag--success');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('follows the surrounding screen mode', () => {
|
|
34
|
+
render(
|
|
35
|
+
<ThemeProvider theme="personal" screenMode="dark">
|
|
36
|
+
<Tag label="Tag copy" data-testid="tag" />
|
|
37
|
+
</ThemeProvider>,
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
expect(screen.getByTestId('tag')).toHaveClass('wds-sentiment-neutral-dark-base');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('uses the dark elevated surface when overMedia is set in a light screen mode', () => {
|
|
44
|
+
render(
|
|
45
|
+
<ThemeProvider theme="personal" screenMode="light">
|
|
46
|
+
<Tag label="Tag copy" data-testid="tag" overMedia />
|
|
47
|
+
</ThemeProvider>,
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
expect(screen.getByTestId('tag')).toHaveClass('wds-sentiment-neutral-dark-elevated');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('keeps the surrounding brand theme when overMedia is set', () => {
|
|
54
|
+
render(
|
|
55
|
+
<ThemeProvider theme="business" screenMode="light">
|
|
56
|
+
<Tag label="Tag copy" data-testid="tag" overMedia />
|
|
57
|
+
</ThemeProvider>,
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
expect(screen.getByTestId('tag')).toHaveClass('np-theme-business');
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('applies a custom className and id', () => {
|
|
64
|
+
render(<Tag label="Tag copy" className="custom-class" id="custom-id" data-testid="tag" />);
|
|
65
|
+
const tag = screen.getByTestId('tag');
|
|
66
|
+
|
|
67
|
+
expect(tag).toHaveClass('custom-class');
|
|
68
|
+
expect(tag).toHaveAttribute('id', 'custom-id');
|
|
69
|
+
});
|
|
70
|
+
});
|
package/src/Tag/Tag.tsx
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { forwardRef } from 'react';
|
|
2
|
+
import { clsx } from 'clsx';
|
|
3
|
+
import { ThemeProvider, useTheme } from '@wise/components-theming';
|
|
4
|
+
|
|
5
|
+
import SentimentSurface from '../SentimentSurface';
|
|
6
|
+
import { TagProps } from './Tag.types';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Tag is a small, non-interactive label that categorises or highlights a piece of content.
|
|
10
|
+
*
|
|
11
|
+
* @param {string} label - The text of the tag, kept to a word or two.
|
|
12
|
+
* @param {Sentiment} [sentiment='neutral'] - Optional prop to set the sentiment type (negative, warning, neutral, success, proposition).
|
|
13
|
+
* @param {TagSize} [size='small'] - Optional prop to set the size of the tag (small or large).
|
|
14
|
+
* @param {Emphasis} [emphasis='base'] - Optional prop to specify the emphasis level (base or elevated).
|
|
15
|
+
* @param {boolean} [overMedia=false] - If true, renders the tag for use over media, keeping the colours it uses in dark screen modes.
|
|
16
|
+
* @param {string} [className] - Additional CSS classes to apply.
|
|
17
|
+
* @param {string} [id] - Unique identifier for the component.
|
|
18
|
+
* @param {string} [data-testid] - A unique string that appears as data attribute `data-testid` in the rendered code.
|
|
19
|
+
*
|
|
20
|
+
* @component
|
|
21
|
+
* @example
|
|
22
|
+
* ```tsx
|
|
23
|
+
* <Tag sentiment="success" label="Verified" />
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* @see {@link https://storybook.wise.design/?path=/docs/tag--docs|Storybook Wise Design}
|
|
27
|
+
*/
|
|
28
|
+
const Tag = forwardRef<HTMLSpanElement, TagProps>(function Tag(
|
|
29
|
+
{
|
|
30
|
+
label,
|
|
31
|
+
sentiment = 'neutral',
|
|
32
|
+
size = 'small',
|
|
33
|
+
emphasis = 'base',
|
|
34
|
+
overMedia = false,
|
|
35
|
+
className,
|
|
36
|
+
id,
|
|
37
|
+
'data-testid': dataTestId,
|
|
38
|
+
},
|
|
39
|
+
ref,
|
|
40
|
+
) {
|
|
41
|
+
const { theme, screenMode } = useTheme();
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<ThemeProvider
|
|
45
|
+
theme={theme}
|
|
46
|
+
screenMode={overMedia ? 'dark' : screenMode}
|
|
47
|
+
className="wds-tag-theme"
|
|
48
|
+
>
|
|
49
|
+
<SentimentSurface
|
|
50
|
+
// @ts-expect-error - SentimentSurface forwardRef types don't expose ref in props
|
|
51
|
+
ref={ref}
|
|
52
|
+
as="span"
|
|
53
|
+
sentiment={sentiment}
|
|
54
|
+
emphasis={overMedia ? 'elevated' : emphasis}
|
|
55
|
+
id={id}
|
|
56
|
+
data-testid={dataTestId}
|
|
57
|
+
className={clsx(
|
|
58
|
+
'wds-tag',
|
|
59
|
+
`wds-tag--${size}`,
|
|
60
|
+
`wds-tag--${sentiment}`,
|
|
61
|
+
'np-text-body-default-bold',
|
|
62
|
+
className,
|
|
63
|
+
)}
|
|
64
|
+
>
|
|
65
|
+
{label}
|
|
66
|
+
</SentimentSurface>
|
|
67
|
+
</ThemeProvider>
|
|
68
|
+
);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
export default Tag;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { Emphasis, Sentiment } from '../SentimentSurface';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Sizes for the Tag component
|
|
5
|
+
*/
|
|
6
|
+
export type TagSize = 'small' | 'large';
|
|
7
|
+
|
|
8
|
+
export interface TagProps {
|
|
9
|
+
/**
|
|
10
|
+
* The text of the tag, kept to a word or two
|
|
11
|
+
*/
|
|
12
|
+
label: string;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The sentiment type that determines the colour scheme
|
|
16
|
+
* @default 'neutral'
|
|
17
|
+
*/
|
|
18
|
+
sentiment?: Sentiment;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The size of the tag
|
|
22
|
+
* @default 'small'
|
|
23
|
+
*/
|
|
24
|
+
size?: TagSize;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The emphasis level affecting background and text contrast
|
|
28
|
+
* @default 'base'
|
|
29
|
+
*/
|
|
30
|
+
emphasis?: Emphasis;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* If true, renders the tag for use over media such as a photo or a video, keeping the colours
|
|
34
|
+
* it uses in dark screen modes so that it stays legible over unpredictable artwork
|
|
35
|
+
* @default false
|
|
36
|
+
*/
|
|
37
|
+
overMedia?: boolean;
|
|
38
|
+
|
|
39
|
+
className?: string;
|
|
40
|
+
id?: string;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* A unique string that appears as data attribute `data-testid` in the rendered code, serving as a hook for automated tests
|
|
44
|
+
*/
|
|
45
|
+
'data-testid'?: string;
|
|
46
|
+
}
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import { Fragment } from 'react';
|
|
2
|
+
import type { ReactElement, ReactNode } from 'react';
|
|
3
|
+
import type { Meta, StoryObj } from '@storybook/react-webpack5';
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
createSandboxStory,
|
|
7
|
+
globalScope,
|
|
8
|
+
} from '../../../.storybook/components/sandbox/SandboxEditor';
|
|
9
|
+
import Tag from '..';
|
|
10
|
+
import type { TagProps } from '..';
|
|
11
|
+
|
|
12
|
+
const withComponentGrid = (Story: () => ReactElement) => (
|
|
13
|
+
<div
|
|
14
|
+
style={{
|
|
15
|
+
display: 'flex',
|
|
16
|
+
flexWrap: 'wrap',
|
|
17
|
+
alignItems: 'center',
|
|
18
|
+
gap: '0.5rem',
|
|
19
|
+
}}
|
|
20
|
+
>
|
|
21
|
+
<Story />
|
|
22
|
+
</div>
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
const SENTIMENTS = ['neutral', 'negative', 'warning', 'success', 'proposition'] as const;
|
|
26
|
+
const SIZES = ['small', 'large'] as const;
|
|
27
|
+
const EMPHASES = ['base', 'elevated'] as const;
|
|
28
|
+
const PHOTO = '../card-payment-bakery.webp';
|
|
29
|
+
|
|
30
|
+
const Label = ({ children }: { children: ReactNode }) => (
|
|
31
|
+
<span
|
|
32
|
+
className="np-text-body-default"
|
|
33
|
+
style={{
|
|
34
|
+
color: 'var(--color-content-secondary)',
|
|
35
|
+
textTransform: 'capitalize',
|
|
36
|
+
whiteSpace: 'nowrap',
|
|
37
|
+
}}
|
|
38
|
+
>
|
|
39
|
+
{children}
|
|
40
|
+
</span>
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Tag is a small, non-interactive label that categorises or highlights a piece of content,
|
|
45
|
+
* such as a status, a category, or a promotion.
|
|
46
|
+
*
|
|
47
|
+
* Tags are decorative labels, not controls — if the user needs to act on the tag, use `Chips`
|
|
48
|
+
* instead.
|
|
49
|
+
*/
|
|
50
|
+
export default {
|
|
51
|
+
title: 'Prompts/Tag',
|
|
52
|
+
component: Tag,
|
|
53
|
+
tags: ['early-access'],
|
|
54
|
+
args: {
|
|
55
|
+
label: 'Tag copy',
|
|
56
|
+
},
|
|
57
|
+
argTypes: {
|
|
58
|
+
label: {
|
|
59
|
+
control: 'text',
|
|
60
|
+
table: {
|
|
61
|
+
type: { summary: 'string' },
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
sentiment: {
|
|
65
|
+
control: 'select',
|
|
66
|
+
options: SENTIMENTS,
|
|
67
|
+
},
|
|
68
|
+
size: {
|
|
69
|
+
control: 'inline-radio',
|
|
70
|
+
options: SIZES,
|
|
71
|
+
},
|
|
72
|
+
emphasis: {
|
|
73
|
+
control: 'inline-radio',
|
|
74
|
+
options: EMPHASES,
|
|
75
|
+
},
|
|
76
|
+
overMedia: {
|
|
77
|
+
control: 'boolean',
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
} satisfies Meta<TagProps>;
|
|
81
|
+
|
|
82
|
+
type Story = StoryObj<TagProps>;
|
|
83
|
+
|
|
84
|
+
export const Playground: Story = {};
|
|
85
|
+
|
|
86
|
+
export const Sandbox = createSandboxStory({
|
|
87
|
+
code: `<div className="d-flex align-items-center" style={{ gap: '8px' }}>
|
|
88
|
+
<Tag label="New" />
|
|
89
|
+
<Tag label="Verified" sentiment="success" emphasis="elevated" />
|
|
90
|
+
<Tag label="Action needed" sentiment="warning" size="large" />
|
|
91
|
+
</div>`,
|
|
92
|
+
scope: globalScope,
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Every tag the component can render. Each cell sets its own `sentiment`, `emphasis` and
|
|
97
|
+
* `overMedia`; the `size` and text controls apply to the whole table.
|
|
98
|
+
*/
|
|
99
|
+
export const Overview: Story = {
|
|
100
|
+
args: {
|
|
101
|
+
size: 'small',
|
|
102
|
+
},
|
|
103
|
+
render: (args) => (
|
|
104
|
+
<div
|
|
105
|
+
style={{
|
|
106
|
+
alignItems: 'center',
|
|
107
|
+
columnGap: '2rem',
|
|
108
|
+
display: 'grid',
|
|
109
|
+
gridTemplateColumns: 'repeat(4, max-content)',
|
|
110
|
+
justifyItems: 'start',
|
|
111
|
+
rowGap: '0.75rem',
|
|
112
|
+
}}
|
|
113
|
+
>
|
|
114
|
+
<span />
|
|
115
|
+
<Label>Base</Label>
|
|
116
|
+
<Label>Elevated</Label>
|
|
117
|
+
<Label>Over media</Label>
|
|
118
|
+
|
|
119
|
+
{SENTIMENTS.map((sentiment) => (
|
|
120
|
+
<Fragment key={sentiment}>
|
|
121
|
+
<Label>{sentiment}</Label>
|
|
122
|
+
<Tag {...args} sentiment={sentiment} />
|
|
123
|
+
<Tag {...args} sentiment={sentiment} emphasis="elevated" />
|
|
124
|
+
<Tag {...args} sentiment={sentiment} overMedia />
|
|
125
|
+
</Fragment>
|
|
126
|
+
))}
|
|
127
|
+
</div>
|
|
128
|
+
),
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Tags come in two sizes. Use `small` alongside body text and `large` when the tag sits next to
|
|
133
|
+
* headings or other prominent content.
|
|
134
|
+
*/
|
|
135
|
+
export const Sizes: Story = {
|
|
136
|
+
decorators: [withComponentGrid],
|
|
137
|
+
render: (args) => (
|
|
138
|
+
<>
|
|
139
|
+
<Tag {...args} size="small" />
|
|
140
|
+
<Tag {...args} size="large" />
|
|
141
|
+
</>
|
|
142
|
+
),
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* The `sentiment` prop conveys the meaning of the tag:
|
|
147
|
+
*
|
|
148
|
+
* - `neutral` (default): general categorisation
|
|
149
|
+
* - `negative`: errors and failures
|
|
150
|
+
* - `warning`: something needs attention
|
|
151
|
+
* - `success`: completed or verified states
|
|
152
|
+
* - `proposition`: promotions and new features
|
|
153
|
+
*/
|
|
154
|
+
export const Sentiments: Story = {
|
|
155
|
+
decorators: [withComponentGrid],
|
|
156
|
+
render: (args) => (
|
|
157
|
+
<>
|
|
158
|
+
{SENTIMENTS.map((sentiment) => (
|
|
159
|
+
<Tag key={sentiment} {...args} sentiment={sentiment} />
|
|
160
|
+
))}
|
|
161
|
+
</>
|
|
162
|
+
),
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Set `emphasis` to `elevated` to make the tag stand out from the content around it. Use it
|
|
167
|
+
* sparingly — when every tag is emphasised, none of them are.
|
|
168
|
+
*/
|
|
169
|
+
export const Emphasis: Story = {
|
|
170
|
+
decorators: [withComponentGrid],
|
|
171
|
+
render: (args) => (
|
|
172
|
+
<>
|
|
173
|
+
{SENTIMENTS.map((sentiment) => (
|
|
174
|
+
<Tag key={sentiment} {...args} sentiment={sentiment} emphasis="elevated" />
|
|
175
|
+
))}
|
|
176
|
+
</>
|
|
177
|
+
),
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Set `overMedia` when the tag sits on top of a photo, a video or any other rich backdrop. The
|
|
182
|
+
* tag then keeps the colours it uses in dark screen modes, regardless of the current one, so that
|
|
183
|
+
* it stays legible over unpredictable artwork. Tags over media are always elevated.
|
|
184
|
+
*/
|
|
185
|
+
export const OverMedia: Story = {
|
|
186
|
+
decorators: [
|
|
187
|
+
(Story: () => ReactElement) => (
|
|
188
|
+
<div
|
|
189
|
+
style={{
|
|
190
|
+
alignItems: 'flex-start',
|
|
191
|
+
backgroundImage: `url(${PHOTO})`,
|
|
192
|
+
backgroundPosition: 'center',
|
|
193
|
+
backgroundSize: 'cover',
|
|
194
|
+
borderRadius: 'var(--radius-medium)',
|
|
195
|
+
display: 'flex',
|
|
196
|
+
flexWrap: 'wrap',
|
|
197
|
+
gap: '0.5rem',
|
|
198
|
+
height: '160px',
|
|
199
|
+
padding: '1rem',
|
|
200
|
+
width: '100%',
|
|
201
|
+
}}
|
|
202
|
+
>
|
|
203
|
+
<Story />
|
|
204
|
+
</div>
|
|
205
|
+
),
|
|
206
|
+
],
|
|
207
|
+
render: (args) => (
|
|
208
|
+
<>
|
|
209
|
+
{SENTIMENTS.map((sentiment) => (
|
|
210
|
+
<Tag key={sentiment} {...args} sentiment={sentiment} overMedia />
|
|
211
|
+
))}
|
|
212
|
+
</>
|
|
213
|
+
),
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Tag copy never wraps. It truncates with an ellipsis, either at the tag's own 240px maximum
|
|
218
|
+
* width or at the width of a narrower parent, so keep the copy to a word or two.
|
|
219
|
+
*/
|
|
220
|
+
export const LongText: Story = {
|
|
221
|
+
decorators: [
|
|
222
|
+
(Story: () => ReactElement) => (
|
|
223
|
+
<div
|
|
224
|
+
style={{
|
|
225
|
+
alignItems: 'flex-start',
|
|
226
|
+
display: 'flex',
|
|
227
|
+
flexDirection: 'column',
|
|
228
|
+
gap: '0.5rem',
|
|
229
|
+
}}
|
|
230
|
+
>
|
|
231
|
+
<Story />
|
|
232
|
+
</div>
|
|
233
|
+
),
|
|
234
|
+
],
|
|
235
|
+
render: (args) => (
|
|
236
|
+
<>
|
|
237
|
+
<Tag {...args} label="A tag with very long text" />
|
|
238
|
+
|
|
239
|
+
<Tag {...args} label="A tag with very very very very very very long text" />
|
|
240
|
+
<div style={{ display: 'flex', width: '100px' }}>
|
|
241
|
+
<Tag {...args} label="A tag in a narrow container with long text" />
|
|
242
|
+
</div>
|
|
243
|
+
</>
|
|
244
|
+
),
|
|
245
|
+
};
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import type { ReactElement } from 'react';
|
|
2
|
+
import type { Meta, StoryObj } from '@storybook/react-webpack5';
|
|
3
|
+
|
|
4
|
+
import { withRTLDiff } from '../../../.storybook/decorators/RtlDiffDecorator';
|
|
5
|
+
import { allModes } from '../../../.storybook/modes';
|
|
6
|
+
import Tag from '..';
|
|
7
|
+
import type { TagProps } from '..';
|
|
8
|
+
|
|
9
|
+
const SENTIMENTS = ['neutral', 'negative', 'warning', 'success', 'proposition'] as const;
|
|
10
|
+
|
|
11
|
+
const withMatrixGrid = (Story: () => ReactElement) => (
|
|
12
|
+
<div
|
|
13
|
+
style={{
|
|
14
|
+
alignItems: 'center',
|
|
15
|
+
display: 'grid',
|
|
16
|
+
gap: '0.5rem',
|
|
17
|
+
gridTemplateColumns: 'repeat(5, max-content)',
|
|
18
|
+
justifyItems: 'start',
|
|
19
|
+
}}
|
|
20
|
+
>
|
|
21
|
+
<Story />
|
|
22
|
+
</div>
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
const meta = {
|
|
26
|
+
title: 'Prompts/Tag/Tests',
|
|
27
|
+
component: Tag,
|
|
28
|
+
tags: ['!autodocs', '!manifest'],
|
|
29
|
+
args: {
|
|
30
|
+
label: 'Tag copy',
|
|
31
|
+
},
|
|
32
|
+
} satisfies Meta<TagProps>;
|
|
33
|
+
|
|
34
|
+
export default meta;
|
|
35
|
+
|
|
36
|
+
type Story = StoryObj<TagProps>;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Every combination of size, emphasis and sentiment, across all themes.
|
|
40
|
+
*/
|
|
41
|
+
export const AllThemesAndSentiments: Story = {
|
|
42
|
+
render: () => (
|
|
43
|
+
<>
|
|
44
|
+
{(['small', 'large'] as const).map((size) =>
|
|
45
|
+
(['base', 'elevated'] as const).map((emphasis) =>
|
|
46
|
+
SENTIMENTS.map((sentiment) => (
|
|
47
|
+
<Tag
|
|
48
|
+
key={`${size}-${emphasis}-${sentiment}`}
|
|
49
|
+
label={`${size} ${sentiment}`}
|
|
50
|
+
size={size}
|
|
51
|
+
sentiment={sentiment}
|
|
52
|
+
emphasis={emphasis}
|
|
53
|
+
/>
|
|
54
|
+
)),
|
|
55
|
+
),
|
|
56
|
+
)}
|
|
57
|
+
</>
|
|
58
|
+
),
|
|
59
|
+
decorators: [withMatrixGrid],
|
|
60
|
+
parameters: {
|
|
61
|
+
padding: '16px',
|
|
62
|
+
variants: ['default', 'dark', 'bright-green', 'forest-green'],
|
|
63
|
+
chromatic: {
|
|
64
|
+
dark: allModes.dark,
|
|
65
|
+
brightGreen: allModes.brightGreen,
|
|
66
|
+
forestGreen: allModes.forestGreen,
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* The tag's horizontal padding is asymmetric and long copy truncates, so both need to mirror.
|
|
73
|
+
*/
|
|
74
|
+
export const RTLDiff: Story = {
|
|
75
|
+
render: () => (
|
|
76
|
+
<div
|
|
77
|
+
style={{
|
|
78
|
+
alignItems: 'flex-start',
|
|
79
|
+
display: 'flex',
|
|
80
|
+
flexDirection: 'column',
|
|
81
|
+
gap: '0.5rem',
|
|
82
|
+
}}
|
|
83
|
+
>
|
|
84
|
+
<Tag label="Tag copy" />
|
|
85
|
+
<Tag label="Tag copy" size="large" sentiment="success" emphasis="elevated" />
|
|
86
|
+
<Tag label="A tag with copy far too long to fit within its maximum width" />
|
|
87
|
+
</div>
|
|
88
|
+
),
|
|
89
|
+
...withRTLDiff(),
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* `overMedia` tags keep their dark screen mode colours in every theme.
|
|
94
|
+
*/
|
|
95
|
+
export const OverMediaAcrossThemes: Story = {
|
|
96
|
+
render: () => (
|
|
97
|
+
<>
|
|
98
|
+
{(['small', 'large'] as const).map((size) =>
|
|
99
|
+
SENTIMENTS.map((sentiment) => (
|
|
100
|
+
<Tag
|
|
101
|
+
key={`${size}-${sentiment}`}
|
|
102
|
+
label={`${size} ${sentiment}`}
|
|
103
|
+
size={size}
|
|
104
|
+
sentiment={sentiment}
|
|
105
|
+
overMedia
|
|
106
|
+
/>
|
|
107
|
+
)),
|
|
108
|
+
)}
|
|
109
|
+
</>
|
|
110
|
+
),
|
|
111
|
+
decorators: [withMatrixGrid],
|
|
112
|
+
parameters: {
|
|
113
|
+
padding: '16px',
|
|
114
|
+
variants: ['default', 'dark', 'bright-green', 'forest-green'],
|
|
115
|
+
chromatic: {
|
|
116
|
+
dark: allModes.dark,
|
|
117
|
+
brightGreen: allModes.brightGreen,
|
|
118
|
+
forestGreen: allModes.forestGreen,
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
};
|
package/src/Tag/index.ts
ADDED
|
@@ -7,7 +7,7 @@ import { withRTLDiff } from '../../../../.storybook/decorators/RtlDiffDecorator'
|
|
|
7
7
|
export default {
|
|
8
8
|
component: CriticalCommsBanner,
|
|
9
9
|
title: 'Prompts/CriticalCommsBanner/Tests',
|
|
10
|
-
tags: ['!autodocs', 'deprecated'],
|
|
10
|
+
tags: ['!autodocs', 'deprecated', 'deprecated'],
|
|
11
11
|
} satisfies Meta<typeof CriticalCommsBanner>;
|
|
12
12
|
|
|
13
13
|
type Story = StoryObj<typeof CriticalCommsBanner>;
|
|
@@ -6,7 +6,7 @@ import InlineAlert from '..';
|
|
|
6
6
|
export default {
|
|
7
7
|
component: InlineAlert,
|
|
8
8
|
title: 'Prompts/InlineAlert/Tests',
|
|
9
|
-
tags: ['!autodocs', '!manifest'],
|
|
9
|
+
tags: ['!autodocs', '!manifest', 'deprecated'],
|
|
10
10
|
} satisfies Meta<typeof InlineAlert>;
|
|
11
11
|
|
|
12
12
|
type Story = StoryObj<typeof InlineAlert>;
|
|
@@ -9,7 +9,7 @@ import OverlayHeader from '..';
|
|
|
9
9
|
export default {
|
|
10
10
|
component: OverlayHeader,
|
|
11
11
|
title: 'Navigation/OverlayHeader/Tests',
|
|
12
|
-
tags: ['!autodocs', '!manifest'],
|
|
12
|
+
tags: ['!autodocs', '!manifest', 'deprecated'],
|
|
13
13
|
} satisfies Meta<typeof OverlayHeader>;
|
|
14
14
|
|
|
15
15
|
type Story = StoryObj<typeof OverlayHeader>;
|
|
@@ -152,11 +152,40 @@
|
|
|
152
152
|
letter-spacing: -0.006em;
|
|
153
153
|
font-weight: 400;
|
|
154
154
|
font-weight: var(--font-weight-regular);
|
|
155
|
+
word-wrap: break-word;
|
|
155
156
|
}
|
|
156
157
|
.np-theme-personal .np-dropdown-menu > li > a {
|
|
157
158
|
border-radius: 10px;
|
|
158
159
|
border-radius: var(--radius-small);
|
|
159
160
|
}
|
|
161
|
+
@supports (hyphenate-limit-chars: 1) {
|
|
162
|
+
.np-dropdown-menu > li > a {
|
|
163
|
+
hyphens: auto;
|
|
164
|
+
hyphenate-limit-chars: 7 3;
|
|
165
|
+
}
|
|
166
|
+
@media (min-width: 840px) {
|
|
167
|
+
.np-dropdown-menu > li > a {
|
|
168
|
+
hyphenate-limit-chars: 8 3;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
@media (min-width: 1160px) {
|
|
172
|
+
.np-dropdown-menu > li > a {
|
|
173
|
+
hyphenate-limit-chars: 10 4 3;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
@supports (not (hyphenate-limit-chars: 1)) and (-webkit-hyphenate-limit-before: 1) {
|
|
178
|
+
.np-dropdown-menu > li > a {
|
|
179
|
+
hyphens: auto;
|
|
180
|
+
-webkit-hyphenate-limit-before: 3;
|
|
181
|
+
-webkit-hyphenate-limit-after: 3;
|
|
182
|
+
}
|
|
183
|
+
@media (min-width: 1160px) {
|
|
184
|
+
.np-dropdown-menu > li > a {
|
|
185
|
+
-webkit-hyphenate-limit-before: 4;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
160
189
|
.np-dropdown-menu > li:first-child > a {
|
|
161
190
|
border-start-end-radius: 3px;
|
|
162
191
|
border-start-start-radius: 3px;
|
|
@@ -8,7 +8,7 @@ import Select from '..';
|
|
|
8
8
|
export default {
|
|
9
9
|
component: Select,
|
|
10
10
|
title: 'Forms/Select/Tests/RTL Diff',
|
|
11
|
-
tags: ['!autodocs', '!manifest'],
|
|
11
|
+
tags: ['!autodocs', '!manifest', 'deprecated'],
|
|
12
12
|
} satisfies Meta<typeof Select>;
|
|
13
13
|
|
|
14
14
|
type Story = StoryObj<typeof Select>;
|
|
@@ -8,7 +8,7 @@ import Select, { SelectOptionItem } from '../Select';
|
|
|
8
8
|
const meta = {
|
|
9
9
|
title: 'Forms/Select/Tests',
|
|
10
10
|
component: Select,
|
|
11
|
-
tags: ['!autodocs', '!manifest'],
|
|
11
|
+
tags: ['!autodocs', '!manifest', 'deprecated'],
|
|
12
12
|
} satisfies Meta<typeof Select>;
|
|
13
13
|
export default meta;
|
|
14
14
|
|