@transferwise/components 0.0.0-experimental-feef95e → 0.0.0-experimental-1bb5459
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/SentimentSurface/SentimentSurface.js +2 -1
- package/build/SentimentSurface/SentimentSurface.js.map +1 -1
- package/build/SentimentSurface/SentimentSurface.mjs +2 -1
- package/build/SentimentSurface/SentimentSurface.mjs.map +1 -1
- package/build/Tag/Tag.js +42 -0
- package/build/Tag/Tag.js.map +1 -0
- package/build/Tag/Tag.mjs +38 -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 +57 -809
- package/build/styles/Inputs/Input/Input.css +0 -84
- package/build/styles/Inputs/TextArea/TextArea.css +0 -84
- package/build/styles/Tag/Tag.css +35 -0
- package/build/styles/_deprecated/Select/Select.css +0 -28
- package/build/styles/css/accordion.css +0 -28
- package/build/styles/css/button-groups.css +0 -28
- package/build/styles/css/buttons.css +0 -28
- package/build/styles/css/decision.css +0 -28
- package/build/styles/css/dropdowns.css +0 -56
- package/build/styles/css/droppable.css +0 -28
- package/build/styles/css/footer.css +0 -61
- package/build/styles/css/input-groups.css +0 -183
- package/build/styles/css/navbar.css +0 -56
- package/build/styles/css/neptune-core.css +0 -179
- package/build/styles/css/neptune.css +0 -675
- package/build/styles/css/popovers.css +0 -56
- package/build/styles/css/tooltip.css +0 -28
- package/build/styles/main.css +57 -809
- package/build/styles/styles/less/accordion.css +0 -28
- package/build/styles/styles/less/button-groups.css +0 -28
- package/build/styles/styles/less/buttons.css +0 -28
- package/build/styles/styles/less/decision.css +0 -28
- package/build/styles/styles/less/dropdowns.css +0 -56
- package/build/styles/styles/less/droppable.css +0 -28
- package/build/styles/styles/less/footer.css +0 -61
- package/build/styles/styles/less/forms/bootstrap-forms.css +0 -112
- package/build/styles/styles/less/input-groups.css +0 -183
- package/build/styles/styles/less/navbar.css +0 -56
- package/build/styles/styles/less/neptune-core.css +0 -179
- package/build/styles/styles/less/neptune.css +0 -675
- package/build/styles/styles/less/popovers.css +0 -56
- package/build/styles/styles/less/tooltip.css +0 -28
- package/build/types/SentimentSurface/SentimentSurface.d.ts +1 -0
- package/build/types/SentimentSurface/SentimentSurface.d.ts.map +1 -1
- package/build/types/SentimentSurface/SentimentSurface.types.d.ts +9 -0
- package/build/types/SentimentSurface/SentimentSurface.types.d.ts.map +1 -1
- package/build/types/SentimentSurface/index.d.ts +1 -1
- package/build/types/SentimentSurface/index.d.ts.map +1 -1
- 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 +3 -1
- package/build/types/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/Inputs/Input/Input.css +0 -84
- package/src/Inputs/TextArea/TextArea.css +0 -84
- package/src/SentimentSurface/SentimentSurface.test.tsx +24 -0
- package/src/SentimentSurface/SentimentSurface.tsx +3 -1
- package/src/SentimentSurface/SentimentSurface.types.ts +11 -0
- package/src/SentimentSurface/_stories/SentimentSurface.story.tsx +10 -0
- package/src/SentimentSurface/index.ts +1 -0
- package/src/Tag/Tag.css +35 -0
- package/src/Tag/Tag.less +35 -0
- package/src/Tag/Tag.test.tsx +72 -0
- package/src/Tag/Tag.tsx +60 -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 +120 -0
- package/src/Tag/index.ts +2 -0
- package/src/_deprecated/Select/Select.css +0 -28
- package/src/index.ts +3 -0
- package/src/main.css +57 -809
- package/src/main.less +1 -0
- package/src/styles/less/accordion.css +0 -28
- package/src/styles/less/button-groups.css +0 -28
- package/src/styles/less/buttons.css +0 -28
- package/src/styles/less/core/_typography.less +0 -12
- package/src/styles/less/decision.css +0 -28
- package/src/styles/less/dropdowns.css +0 -56
- package/src/styles/less/droppable.css +0 -28
- package/src/styles/less/footer.css +0 -61
- package/src/styles/less/forms/bootstrap-forms.css +0 -112
- package/src/styles/less/input-groups.css +0 -183
- package/src/styles/less/navbar.css +0 -56
- package/src/styles/less/neptune-core.css +0 -179
- package/src/styles/less/neptune.css +0 -675
- package/src/styles/less/popovers.css +0 -56
- package/src/styles/less/tooltip.css +0 -28
- package/src/test-utils/assets/card-payment-bakery.webp +0 -0
package/src/Tag/Tag.css
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
.wds-tag {
|
|
2
|
+
align-items: center;
|
|
3
|
+
display: inline-flex;
|
|
4
|
+
gap: 1px;
|
|
5
|
+
max-inline-size: 240px;
|
|
6
|
+
min-inline-size: 0;
|
|
7
|
+
inline-size: -moz-fit-content;
|
|
8
|
+
inline-size: fit-content;
|
|
9
|
+
}
|
|
10
|
+
.wds-tag--small {
|
|
11
|
+
border-radius: 6px;
|
|
12
|
+
padding-block: 1px;
|
|
13
|
+
padding-inline: 3px 4px;
|
|
14
|
+
padding-inline: 3px var(--size-4);
|
|
15
|
+
}
|
|
16
|
+
.wds-tag--small .wds-tag__text {
|
|
17
|
+
font-size: 0.75rem;
|
|
18
|
+
font-size: var(--font-size-12);
|
|
19
|
+
}
|
|
20
|
+
.wds-tag--large {
|
|
21
|
+
border-radius: 8px;
|
|
22
|
+
border-radius: var(--size-8);
|
|
23
|
+
padding-block: 1px;
|
|
24
|
+
padding-inline: 5px 6px;
|
|
25
|
+
padding-inline: var(--size-5) 6px;
|
|
26
|
+
}
|
|
27
|
+
.wds-tag__text {
|
|
28
|
+
overflow: hidden;
|
|
29
|
+
padding-inline-start: 1px;
|
|
30
|
+
text-overflow: ellipsis;
|
|
31
|
+
white-space: nowrap;
|
|
32
|
+
}
|
|
33
|
+
.wds-tag.wds-tag--proposition {
|
|
34
|
+
color: var(--color-sentiment-interactive-primary);
|
|
35
|
+
}
|
package/src/Tag/Tag.less
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
.wds-tag {
|
|
2
|
+
align-items: center;
|
|
3
|
+
display: inline-flex;
|
|
4
|
+
gap: 1px;
|
|
5
|
+
max-width: 240px;
|
|
6
|
+
min-width: 0;
|
|
7
|
+
width: fit-content;
|
|
8
|
+
|
|
9
|
+
&--small {
|
|
10
|
+
border-radius: 6px;
|
|
11
|
+
padding-block: 1px;
|
|
12
|
+
padding-inline: 3px var(--size-4);
|
|
13
|
+
|
|
14
|
+
.wds-tag__text {
|
|
15
|
+
font-size: var(--font-size-12);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
&--large {
|
|
20
|
+
border-radius: var(--size-8);
|
|
21
|
+
padding-block: 1px;
|
|
22
|
+
padding-inline: var(--size-5) 6px;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
&__text {
|
|
26
|
+
overflow: hidden;
|
|
27
|
+
padding-inline-start: 1px;
|
|
28
|
+
text-overflow: ellipsis;
|
|
29
|
+
white-space: nowrap;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
&.wds-tag--proposition {
|
|
33
|
+
color: var(--color-sentiment-interactive-primary);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
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 text as a span', () => {
|
|
8
|
+
render(<Tag data-testid="tag">Tag copy</Tag>);
|
|
9
|
+
const tag = screen.getByTestId('tag');
|
|
10
|
+
|
|
11
|
+
expect(tag.tagName).toBe('SPAN');
|
|
12
|
+
expect(tag).toHaveTextContent('Tag copy');
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('wraps the text in a truncating text wrapper', () => {
|
|
16
|
+
render(<Tag data-testid="tag">Tag copy</Tag>);
|
|
17
|
+
const text = screen.getByText('Tag copy');
|
|
18
|
+
|
|
19
|
+
expect(text).toHaveClass('wds-tag__text');
|
|
20
|
+
expect(text.parentElement).toHaveClass('wds-tag');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('defaults to a small, neutral tag', () => {
|
|
24
|
+
render(<Tag data-testid="tag">Tag copy</Tag>);
|
|
25
|
+
|
|
26
|
+
expect(screen.getByTestId('tag')).toHaveClass('wds-tag', 'wds-tag--small', 'wds-tag--neutral');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('renders the size as a class', () => {
|
|
30
|
+
render(
|
|
31
|
+
<Tag size="large" data-testid="tag">
|
|
32
|
+
Tag copy
|
|
33
|
+
</Tag>,
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
expect(screen.getByTestId('tag')).toHaveClass('wds-tag--large');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('renders the sentiment as a class', () => {
|
|
40
|
+
render(
|
|
41
|
+
<Tag sentiment="success" data-testid="tag">
|
|
42
|
+
Tag copy
|
|
43
|
+
</Tag>,
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
expect(screen.getByTestId('tag')).toHaveClass('wds-tag--success');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('uses the dark elevated surface when overMedia is set in a light screen mode', () => {
|
|
50
|
+
render(
|
|
51
|
+
<ThemeProvider theme="personal" screenMode="light">
|
|
52
|
+
<Tag data-testid="tag" overMedia>
|
|
53
|
+
Tag copy
|
|
54
|
+
</Tag>
|
|
55
|
+
</ThemeProvider>,
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
expect(screen.getByTestId('tag')).toHaveClass('wds-sentiment-neutral-dark-elevated');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('applies a custom className and id', () => {
|
|
62
|
+
render(
|
|
63
|
+
<Tag className="custom-class" id="custom-id" data-testid="tag">
|
|
64
|
+
Tag copy
|
|
65
|
+
</Tag>,
|
|
66
|
+
);
|
|
67
|
+
const tag = screen.getByTestId('tag');
|
|
68
|
+
|
|
69
|
+
expect(tag).toHaveClass('custom-class');
|
|
70
|
+
expect(tag).toHaveAttribute('id', 'custom-id');
|
|
71
|
+
});
|
|
72
|
+
});
|
package/src/Tag/Tag.tsx
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { forwardRef } from 'react';
|
|
2
|
+
import { clsx } from 'clsx';
|
|
3
|
+
|
|
4
|
+
import Body from '../Body';
|
|
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} children - The text of the tag.
|
|
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">Verified</Tag>
|
|
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
|
+
children,
|
|
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
|
+
return (
|
|
42
|
+
<SentimentSurface
|
|
43
|
+
// @ts-expect-error - SentimentSurface forwardRef types don't expose ref in props
|
|
44
|
+
ref={ref}
|
|
45
|
+
as="span"
|
|
46
|
+
sentiment={sentiment}
|
|
47
|
+
emphasis={overMedia ? 'elevated' : emphasis}
|
|
48
|
+
colourScheme={overMedia ? 'dark' : undefined}
|
|
49
|
+
id={id}
|
|
50
|
+
data-testid={dataTestId}
|
|
51
|
+
className={clsx('wds-tag', `wds-tag--${size}`, `wds-tag--${sentiment}`, className)}
|
|
52
|
+
>
|
|
53
|
+
<Body as="span" type="body-default-bold" className="wds-tag__text">
|
|
54
|
+
{children}
|
|
55
|
+
</Body>
|
|
56
|
+
</SentimentSurface>
|
|
57
|
+
);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
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
|
|
11
|
+
*/
|
|
12
|
+
children: 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
|
+
children: 'Tag copy',
|
|
56
|
+
},
|
|
57
|
+
argTypes: {
|
|
58
|
+
children: {
|
|
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>New</Tag>
|
|
89
|
+
<Tag sentiment="success" emphasis="elevated">Verified</Tag>
|
|
90
|
+
<Tag sentiment="warning" size="large">Action needed</Tag>
|
|
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}>A tag with very long text</Tag>
|
|
238
|
+
|
|
239
|
+
<Tag {...args}>A tag with very very very very very very long text</Tag>
|
|
240
|
+
<div style={{ display: 'flex', width: '100px' }}>
|
|
241
|
+
<Tag {...args}>A tag in a narrow container with long text</Tag>
|
|
242
|
+
</div>
|
|
243
|
+
</>
|
|
244
|
+
),
|
|
245
|
+
};
|
|
@@ -0,0 +1,120 @@
|
|
|
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', 'early-access'],
|
|
29
|
+
args: {
|
|
30
|
+
children: '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
|
+
size={size}
|
|
50
|
+
sentiment={sentiment}
|
|
51
|
+
emphasis={emphasis}
|
|
52
|
+
>
|
|
53
|
+
{`${size} ${sentiment}`}
|
|
54
|
+
</Tag>
|
|
55
|
+
)),
|
|
56
|
+
),
|
|
57
|
+
)}
|
|
58
|
+
</>
|
|
59
|
+
),
|
|
60
|
+
decorators: [withMatrixGrid],
|
|
61
|
+
parameters: {
|
|
62
|
+
padding: '16px',
|
|
63
|
+
variants: ['default', 'dark', 'bright-green', 'forest-green'],
|
|
64
|
+
chromatic: {
|
|
65
|
+
dark: allModes.dark,
|
|
66
|
+
brightGreen: allModes.brightGreen,
|
|
67
|
+
forestGreen: allModes.forestGreen,
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* The tag's horizontal padding is asymmetric and long copy truncates, so both need to mirror.
|
|
74
|
+
*/
|
|
75
|
+
export const RTLDiff: Story = {
|
|
76
|
+
render: () => (
|
|
77
|
+
<div
|
|
78
|
+
style={{
|
|
79
|
+
alignItems: 'flex-start',
|
|
80
|
+
display: 'flex',
|
|
81
|
+
flexDirection: 'column',
|
|
82
|
+
gap: '0.5rem',
|
|
83
|
+
}}
|
|
84
|
+
>
|
|
85
|
+
<Tag>Tag copy</Tag>
|
|
86
|
+
<Tag size="large" sentiment="success" emphasis="elevated">
|
|
87
|
+
Tag copy
|
|
88
|
+
</Tag>
|
|
89
|
+
<Tag>A tag with copy far too long to fit within its maximum width</Tag>
|
|
90
|
+
</div>
|
|
91
|
+
),
|
|
92
|
+
...withRTLDiff(),
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* `overMedia` tags keep their dark screen mode colours in every theme.
|
|
97
|
+
*/
|
|
98
|
+
export const OverMediaAcrossThemes: Story = {
|
|
99
|
+
render: () => (
|
|
100
|
+
<>
|
|
101
|
+
{(['small', 'large'] as const).map((size) =>
|
|
102
|
+
SENTIMENTS.map((sentiment) => (
|
|
103
|
+
<Tag key={`${size}-${sentiment}`} size={size} sentiment={sentiment} overMedia>
|
|
104
|
+
{`${size} ${sentiment}`}
|
|
105
|
+
</Tag>
|
|
106
|
+
)),
|
|
107
|
+
)}
|
|
108
|
+
</>
|
|
109
|
+
),
|
|
110
|
+
decorators: [withMatrixGrid],
|
|
111
|
+
parameters: {
|
|
112
|
+
padding: '16px',
|
|
113
|
+
variants: ['default', 'dark', 'bright-green', 'forest-green'],
|
|
114
|
+
chromatic: {
|
|
115
|
+
dark: allModes.dark,
|
|
116
|
+
brightGreen: allModes.brightGreen,
|
|
117
|
+
forestGreen: allModes.forestGreen,
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
};
|
package/src/Tag/index.ts
ADDED
|
@@ -157,34 +157,6 @@
|
|
|
157
157
|
border-radius: 10px;
|
|
158
158
|
border-radius: var(--radius-small);
|
|
159
159
|
}
|
|
160
|
-
@supports (hyphenate-limit-chars: 1) {
|
|
161
|
-
.np-dropdown-menu > li > a {
|
|
162
|
-
hyphens: auto;
|
|
163
|
-
hyphenate-limit-chars: 7 3;
|
|
164
|
-
}
|
|
165
|
-
@media (min-width: 840px) {
|
|
166
|
-
.np-dropdown-menu > li > a {
|
|
167
|
-
hyphenate-limit-chars: 8 3;
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
@media (min-width: 1160px) {
|
|
171
|
-
.np-dropdown-menu > li > a {
|
|
172
|
-
hyphenate-limit-chars: 10 4 3;
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
@supports (not (hyphenate-limit-chars: 1)) and (-webkit-hyphenate-limit-before: 1) {
|
|
177
|
-
.np-dropdown-menu > li > a {
|
|
178
|
-
hyphens: auto;
|
|
179
|
-
-webkit-hyphenate-limit-before: 3;
|
|
180
|
-
-webkit-hyphenate-limit-after: 3;
|
|
181
|
-
}
|
|
182
|
-
@media (min-width: 1160px) {
|
|
183
|
-
.np-dropdown-menu > li > a {
|
|
184
|
-
-webkit-hyphenate-limit-before: 4;
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
160
|
.np-dropdown-menu > li:first-child > a {
|
|
189
161
|
border-start-end-radius: 3px;
|
|
190
162
|
border-start-start-radius: 3px;
|
package/src/index.ts
CHANGED
|
@@ -144,10 +144,12 @@ export type {
|
|
|
144
144
|
SentimentSurfaceProps,
|
|
145
145
|
SentimentSurfaceComponent,
|
|
146
146
|
Sentiment as SurfaceSentiment,
|
|
147
|
+
ColourScheme as SurfaceColourScheme,
|
|
147
148
|
} from './SentimentSurface';
|
|
148
149
|
export type { SwitchProps } from './Switch';
|
|
149
150
|
export type { SwitchOptionProps } from './_deprecated/SwitchOption';
|
|
150
151
|
export type { TabItem, TabsProps, TabProps, TabListProps, TabPanelProps } from './Tabs';
|
|
152
|
+
export type { TagProps, TagSize } from './Tag';
|
|
151
153
|
export type { TextareaWithDisplayFormatProps } from './TextareaWithDisplayFormat';
|
|
152
154
|
export type { TileProps } from './Tile';
|
|
153
155
|
export type { TitleProps } from './Title';
|
|
@@ -291,6 +293,7 @@ export { default as Summary } from './_deprecated/Summary';
|
|
|
291
293
|
export { default as Switch } from './Switch';
|
|
292
294
|
export { default as SwitchOption } from './_deprecated/SwitchOption';
|
|
293
295
|
export { default as Tabs } from './Tabs';
|
|
296
|
+
export { default as Tag } from './Tag';
|
|
294
297
|
export { default as TextareaWithDisplayFormat } from './TextareaWithDisplayFormat';
|
|
295
298
|
export { default as Title } from './Title';
|
|
296
299
|
export { default as Tile } from './Tile';
|