@arbor-education/design-system.components 0.3.5 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -0
- package/dist/components/banner/Banner.d.ts +19 -0
- package/dist/components/banner/Banner.d.ts.map +1 -0
- package/dist/components/banner/Banner.js +33 -0
- package/dist/components/banner/Banner.js.map +1 -0
- package/dist/components/banner/Banner.stories.d.ts +72 -0
- package/dist/components/banner/Banner.stories.d.ts.map +1 -0
- package/dist/components/banner/Banner.stories.js +84 -0
- package/dist/components/banner/Banner.stories.js.map +1 -0
- package/dist/components/banner/Banner.test.d.ts +2 -0
- package/dist/components/banner/Banner.test.d.ts.map +1 -0
- package/dist/components/banner/Banner.test.js +72 -0
- package/dist/components/banner/Banner.test.js.map +1 -0
- package/dist/components/editableText/EditableText.d.ts +10 -0
- package/dist/components/editableText/EditableText.d.ts.map +1 -0
- package/dist/components/editableText/EditableText.js +36 -0
- package/dist/components/editableText/EditableText.js.map +1 -0
- package/dist/components/editableText/EditableText.stories.d.ts +44 -0
- package/dist/components/editableText/EditableText.stories.d.ts.map +1 -0
- package/dist/components/editableText/EditableText.stories.js +94 -0
- package/dist/components/editableText/EditableText.stories.js.map +1 -0
- package/dist/components/editableText/EditableText.test.d.ts +2 -0
- package/dist/components/editableText/EditableText.test.d.ts.map +1 -0
- package/dist/components/editableText/EditableText.test.js +187 -0
- package/dist/components/editableText/EditableText.test.js.map +1 -0
- package/dist/components/heading/Heading.stories.d.ts +26 -0
- package/dist/components/heading/Heading.stories.d.ts.map +1 -1
- package/dist/components/heading/Heading.stories.js +35 -0
- package/dist/components/heading/Heading.stories.js.map +1 -1
- package/dist/components/progress/Progress.d.ts +6 -0
- package/dist/components/progress/Progress.d.ts.map +1 -0
- package/dist/components/progress/Progress.js +9 -0
- package/dist/components/progress/Progress.js.map +1 -0
- package/dist/components/progress/Progress.stories.d.ts +324 -0
- package/dist/components/progress/Progress.stories.d.ts.map +1 -0
- package/dist/components/progress/Progress.stories.js +77 -0
- package/dist/components/progress/Progress.stories.js.map +1 -0
- package/dist/components/progress/Progress.test.d.ts +2 -0
- package/dist/components/progress/Progress.test.d.ts.map +1 -0
- package/dist/components/progress/Progress.test.js +77 -0
- package/dist/components/progress/Progress.test.js.map +1 -0
- package/dist/components/toast/Toast.d.ts +10 -0
- package/dist/components/toast/Toast.d.ts.map +1 -0
- package/dist/components/toast/Toast.js +20 -0
- package/dist/components/toast/Toast.js.map +1 -0
- package/dist/components/toast/Toast.stories.d.ts +12 -0
- package/dist/components/toast/Toast.stories.d.ts.map +1 -0
- package/dist/components/toast/Toast.stories.js +73 -0
- package/dist/components/toast/Toast.stories.js.map +1 -0
- package/dist/components/toast/Toast.test.d.ts +2 -0
- package/dist/components/toast/Toast.test.d.ts.map +1 -0
- package/dist/components/toast/Toast.test.js +87 -0
- package/dist/components/toast/Toast.test.js.map +1 -0
- package/dist/components/toast/ToastViewport.d.ts +3 -0
- package/dist/components/toast/ToastViewport.d.ts.map +1 -0
- package/dist/components/toast/ToastViewport.js +5 -0
- package/dist/components/toast/ToastViewport.js.map +1 -0
- package/dist/index.css +202 -56
- package/dist/index.css.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
- package/setupTestRuntime.ts +7 -0
- package/src/components/banner/Banner.stories.tsx +96 -0
- package/src/components/banner/Banner.test.tsx +86 -0
- package/src/components/banner/Banner.tsx +81 -0
- package/src/components/banner/banner.scss +67 -0
- package/src/components/button/button.scss +1 -5
- package/src/components/card/card.scss +0 -3
- package/src/components/dropdown/dropdown.scss +0 -3
- package/src/components/editableText/EditableText.stories.tsx +136 -0
- package/src/components/editableText/EditableText.test.tsx +242 -0
- package/src/components/editableText/EditableText.tsx +73 -0
- package/src/components/editableText/editableText.scss +54 -0
- package/src/components/formField/fieldset/fieldset.scss +0 -2
- package/src/components/formField/formField.scss +0 -2
- package/src/components/formField/inputs/checkbox/checkboxInput.scss +0 -2
- package/src/components/formField/inputs/input.scss +0 -3
- package/src/components/formField/inputs/radio/radioButtonInput.scss +0 -2
- package/src/components/formField/inputs/selectDropdown/selectDropdown.scss +0 -1
- package/src/components/formField/label/label.scss +0 -2
- package/src/components/heading/Heading.stories.tsx +58 -0
- package/src/components/heading/heading.scss +4 -4
- package/src/components/modal/modal.scss +0 -3
- package/src/components/pill/pill.scss +0 -3
- package/src/components/progress/Progress.stories.tsx +90 -0
- package/src/components/progress/Progress.test.tsx +88 -0
- package/src/components/progress/Progress.tsx +16 -0
- package/src/components/progress/progress.scss +13 -0
- package/src/components/searchBar/searchBar.scss +0 -3
- package/src/components/table/columnFilters/columnFilters.scss +0 -6
- package/src/components/table/pagination/pagination.scss +0 -4
- package/src/components/tabs/tabs.scss +0 -2
- package/src/components/tag/tag.scss +0 -3
- package/src/components/toast/Toast.stories.tsx +113 -0
- package/src/components/toast/Toast.test.tsx +126 -0
- package/src/components/toast/Toast.tsx +35 -0
- package/src/components/toast/ToastViewport.tsx +6 -0
- package/src/components/toast/toast.scss +79 -0
- package/src/components/tooltip/tooltip.scss +0 -3
- package/src/global.scss +9 -1
- package/src/index.scss +4 -0
- package/src/index.ts +4 -0
- package/src/tokens.scss +2 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { expect, test, describe } from 'vitest';
|
|
2
|
+
import { Progress } from './Progress';
|
|
3
|
+
import { render } from '@testing-library/react';
|
|
4
|
+
import '@testing-library/jest-dom/vitest';
|
|
5
|
+
|
|
6
|
+
describe('Progress component', () => {
|
|
7
|
+
test('renders progress bar with default values', () => {
|
|
8
|
+
const { container } = render(<Progress />);
|
|
9
|
+
const progressRoot = container.querySelector('.ds-progress');
|
|
10
|
+
expect(progressRoot).toBeInTheDocument();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
test('renders progress indicator', () => {
|
|
14
|
+
const { container } = render(<Progress value={50} />);
|
|
15
|
+
const indicator = container.querySelector('.ds-progress__indicator');
|
|
16
|
+
expect(indicator).toBeInTheDocument();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test('applies correct transform style for 0% progress', () => {
|
|
20
|
+
const { container } = render(<Progress value={0} max={100} />);
|
|
21
|
+
const indicator = container.querySelector('.ds-progress__indicator') as HTMLElement;
|
|
22
|
+
expect(indicator).toHaveStyle({ transform: 'translateX(-100%)' });
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test('applies correct transform style for 50% progress', () => {
|
|
26
|
+
const { container } = render(<Progress value={50} max={100} />);
|
|
27
|
+
const indicator = container.querySelector('.ds-progress__indicator') as HTMLElement;
|
|
28
|
+
expect(indicator).toHaveStyle({ transform: 'translateX(-50%)' });
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('applies correct transform style for 100% progress', () => {
|
|
32
|
+
const { container } = render(<Progress value={100} max={100} />);
|
|
33
|
+
const indicator = container.querySelector('.ds-progress__indicator') as HTMLElement;
|
|
34
|
+
expect(indicator).toHaveStyle({ transform: 'translateX(-0%)' });
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('applies correct transform style for 25% progress', () => {
|
|
38
|
+
const { container } = render(<Progress value={25} max={100} />);
|
|
39
|
+
const indicator = container.querySelector('.ds-progress__indicator') as HTMLElement;
|
|
40
|
+
expect(indicator).toHaveStyle({ transform: 'translateX(-75%)' });
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('applies correct transform style for 75% progress', () => {
|
|
44
|
+
const { container } = render(<Progress value={75} max={100} />);
|
|
45
|
+
const indicator = container.querySelector('.ds-progress__indicator') as HTMLElement;
|
|
46
|
+
expect(indicator).toHaveStyle({ transform: 'translateX(-25%)' });
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('handles custom max value correctly', () => {
|
|
50
|
+
const { container } = render(<Progress value={30} max={200} />);
|
|
51
|
+
const indicator = container.querySelector('.ds-progress__indicator') as HTMLElement;
|
|
52
|
+
// 30/200 = 15% progress, so translateX should be -85%
|
|
53
|
+
expect(indicator).toHaveStyle({ transform: 'translateX(-85%)' });
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('calculates percentage correctly with custom max', () => {
|
|
57
|
+
const { container } = render(<Progress value={25} max={50} />);
|
|
58
|
+
const indicator = container.querySelector('.ds-progress__indicator') as HTMLElement;
|
|
59
|
+
// 25/50 = 50% progress, so translateX should be -50%
|
|
60
|
+
expect(indicator).toHaveStyle({ transform: 'translateX(-50%)' });
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test('passes through value and max props to Radix component', () => {
|
|
64
|
+
const { container } = render(<Progress value={60} max={100} />);
|
|
65
|
+
const progressRoot = container.querySelector('.ds-progress');
|
|
66
|
+
expect(progressRoot).toHaveAttribute('data-value', '60');
|
|
67
|
+
expect(progressRoot).toHaveAttribute('data-max', '100');
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('uses default value of 0 when not provided', () => {
|
|
71
|
+
const { container } = render(<Progress max={100} />);
|
|
72
|
+
const progressRoot = container.querySelector('.ds-progress');
|
|
73
|
+
expect(progressRoot).toHaveAttribute('data-value', '0');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test('uses default max of 100 when not provided', () => {
|
|
77
|
+
const { container } = render(<Progress value={50} />);
|
|
78
|
+
const progressRoot = container.querySelector('.ds-progress');
|
|
79
|
+
expect(progressRoot).toHaveAttribute('data-max', '100');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test('applies additional props to root element', () => {
|
|
83
|
+
const { container } = render(<Progress value={50} data-testid="custom-progress" />);
|
|
84
|
+
const progressRoot = container.querySelector('[data-testid="custom-progress"]');
|
|
85
|
+
expect(progressRoot).toBeInTheDocument();
|
|
86
|
+
expect(progressRoot).toHaveClass('ds-progress');
|
|
87
|
+
});
|
|
88
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import classNames from 'classnames';
|
|
2
|
+
import { Progress as RadixProgress } from 'radix-ui';
|
|
3
|
+
|
|
4
|
+
export type ProgressProps = RadixProgress.ProgressProps & {
|
|
5
|
+
indicatorClassName?: string;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export const Progress = (props: ProgressProps) => {
|
|
9
|
+
const { value = 0, max = 100, className = '', indicatorClassName = '', ...rest } = props;
|
|
10
|
+
const percentage = (Number(value) / max) * 100;
|
|
11
|
+
return (
|
|
12
|
+
<RadixProgress.Root value={value} max={max} {...rest} className={classNames('ds-progress', className)}>
|
|
13
|
+
<RadixProgress.Indicator style={{ transform: `translateX(-${100 - percentage}%)` }} className={classNames('ds-progress__indicator', indicatorClassName)} />
|
|
14
|
+
</RadixProgress.Root>
|
|
15
|
+
);
|
|
16
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
.ds-progress {
|
|
2
|
+
width: 100%;
|
|
3
|
+
height: var(--spacing-small);
|
|
4
|
+
background-color: var(--color-grey-100);
|
|
5
|
+
overflow: hidden;
|
|
6
|
+
border-radius: var(--border-radius-large);
|
|
7
|
+
|
|
8
|
+
&__indicator {
|
|
9
|
+
height: 100%;
|
|
10
|
+
background-color: var(--color-brand-600);
|
|
11
|
+
border-radius: var(--border-radius-large);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -43,9 +43,6 @@
|
|
|
43
43
|
border: var(--border-weight) solid var(--color-grey-200);
|
|
44
44
|
border-radius: var(--border-radius-small);
|
|
45
45
|
color: var(--color-grey-900);
|
|
46
|
-
font-family: var(--font-family-standard);
|
|
47
|
-
font-size: var(--font-size-2-13);
|
|
48
|
-
font-weight: var(--font-weight-regular);
|
|
49
46
|
padding: 0 var(--spacing-small);
|
|
50
47
|
justify-content: space-between;
|
|
51
48
|
}
|
|
@@ -59,9 +56,6 @@
|
|
|
59
56
|
border: var(--border-weight) solid var(--color-grey-200);
|
|
60
57
|
border-radius: var(--border-radius-small);
|
|
61
58
|
color: var(--color-grey-900);
|
|
62
|
-
font-family: var(--font-family-standard);
|
|
63
|
-
font-size: var(--font-size-2-13);
|
|
64
|
-
font-weight: var(--font-weight-regular);
|
|
65
59
|
box-sizing: border-box;
|
|
66
60
|
|
|
67
61
|
&::-webkit-calendar-picker-indicator {
|
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
list-style: none;
|
|
5
5
|
padding: 0;
|
|
6
6
|
margin: 0;
|
|
7
|
-
font-family: var(--type-body-p-family);
|
|
8
7
|
align-items: center;
|
|
9
8
|
justify-content: flex-start;
|
|
10
9
|
gap: var(--tabs-spacing-horizontal-gap);
|
|
@@ -21,7 +20,6 @@
|
|
|
21
20
|
.ds-tabs-item__tab {
|
|
22
21
|
min-width: var(--size-control-xlarge);
|
|
23
22
|
position: relative;
|
|
24
|
-
font-family: var(--type-body-p-family);
|
|
25
23
|
padding: var(--tabs-tab-item-spacing-vertical) var(--tabs-tab-item-spacing-horizontal);
|
|
26
24
|
cursor: pointer;
|
|
27
25
|
display: flex;
|
|
@@ -8,10 +8,7 @@
|
|
|
8
8
|
width: fit-content;
|
|
9
9
|
|
|
10
10
|
/* typography/body/p1-reg */
|
|
11
|
-
font-family: var(--type-body-p-family, Inter);
|
|
12
|
-
font-size: var(--type-body-p-size);
|
|
13
11
|
font-style: normal;
|
|
14
|
-
font-weight: var(--type-body-p-weight);
|
|
15
12
|
line-height: 150%; /* 19.5px */
|
|
16
13
|
|
|
17
14
|
&--neutral {
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
+
import { Toast, type ToastProps } from './Toast';
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import { Button } from 'Components/button/Button';
|
|
5
|
+
|
|
6
|
+
const meta: Meta<typeof Toast> = {
|
|
7
|
+
tags: ['autodocs'],
|
|
8
|
+
title: 'Components/Toast',
|
|
9
|
+
component: Toast,
|
|
10
|
+
parameters: {
|
|
11
|
+
docs: {
|
|
12
|
+
description: {
|
|
13
|
+
component: 'Toasts must have a Toast.Provider somewhere in its parent tree. A Toast.Viewport is also required inside the same provider as it is the element the toasts are rendered into. This uses the Radix Toast component, see https://www.radix-ui.com/primitives/docs/components/toast for more information.',
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
argTypes: {
|
|
18
|
+
variant: {
|
|
19
|
+
control: 'select',
|
|
20
|
+
options: ['information', 'success', 'warning', 'danger'],
|
|
21
|
+
description: 'Toast variant',
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
type Story = StoryObj<typeof Toast>;
|
|
27
|
+
|
|
28
|
+
const ToastWrapper = (args: ToastProps) => {
|
|
29
|
+
const [open, setOpen] = useState(false);
|
|
30
|
+
return (
|
|
31
|
+
<Toast.Provider>
|
|
32
|
+
<Button onClick={() => setOpen(true)}>Open Toast</Button>
|
|
33
|
+
<Toast {...args} open={open} onOpenChange={setOpen} />
|
|
34
|
+
<Toast.Viewport />
|
|
35
|
+
</Toast.Provider>
|
|
36
|
+
);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export const Information: Story = {
|
|
40
|
+
args: {
|
|
41
|
+
variant: 'information',
|
|
42
|
+
children: 'This is an information message',
|
|
43
|
+
},
|
|
44
|
+
render: ToastWrapper,
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export const Success: Story = {
|
|
48
|
+
args: {
|
|
49
|
+
variant: 'success',
|
|
50
|
+
children: 'Your changes have been saved successfully!',
|
|
51
|
+
},
|
|
52
|
+
render: ToastWrapper,
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export const Warning: Story = {
|
|
56
|
+
args: {
|
|
57
|
+
variant: 'warning',
|
|
58
|
+
children: 'Please review your changes before proceeding',
|
|
59
|
+
},
|
|
60
|
+
render: ToastWrapper,
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export const Danger: Story = {
|
|
64
|
+
args: {
|
|
65
|
+
variant: 'danger',
|
|
66
|
+
children: 'An error occurred while processing your request',
|
|
67
|
+
},
|
|
68
|
+
render: ToastWrapper,
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export const LongContent: Story = {
|
|
72
|
+
args: {
|
|
73
|
+
variant: 'information',
|
|
74
|
+
children:
|
|
75
|
+
'This is a much longer toast message that demonstrates how the component handles extended content. It should wrap appropriately and maintain readability.',
|
|
76
|
+
},
|
|
77
|
+
render: ToastWrapper,
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export const MultipleToasts: Story = {
|
|
81
|
+
render: () => {
|
|
82
|
+
const [infoOpen, setInfoOpen] = useState(false);
|
|
83
|
+
const [successOpen, setSuccessOpen] = useState(false);
|
|
84
|
+
const [warningOpen, setWarningOpen] = useState(false);
|
|
85
|
+
const [dangerOpen, setDangerOpen] = useState(false);
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
<Toast.Provider>
|
|
89
|
+
<div style={{ display: 'flex', gap: '8px', flexWrap: 'wrap' }}>
|
|
90
|
+
<Button onClick={() => setInfoOpen(true)}>Open Info</Button>
|
|
91
|
+
<Button onClick={() => setSuccessOpen(true)}>Open Success</Button>
|
|
92
|
+
<Button onClick={() => setWarningOpen(true)}>Open Warning</Button>
|
|
93
|
+
<Button onClick={() => setDangerOpen(true)}>Open Danger</Button>
|
|
94
|
+
</div>
|
|
95
|
+
<Toast variant="information" open={infoOpen} onOpenChange={setInfoOpen}>
|
|
96
|
+
Information message
|
|
97
|
+
</Toast>
|
|
98
|
+
<Toast variant="success" open={successOpen} onOpenChange={setSuccessOpen}>
|
|
99
|
+
Success message
|
|
100
|
+
</Toast>
|
|
101
|
+
<Toast variant="warning" open={warningOpen} onOpenChange={setWarningOpen}>
|
|
102
|
+
Warning message
|
|
103
|
+
</Toast>
|
|
104
|
+
<Toast variant="danger" open={dangerOpen} onOpenChange={setDangerOpen}>
|
|
105
|
+
Error message
|
|
106
|
+
</Toast>
|
|
107
|
+
<Toast.Viewport />
|
|
108
|
+
</Toast.Provider>
|
|
109
|
+
);
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
export default meta;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { render, screen } from '@testing-library/react';
|
|
2
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
3
|
+
import { Toast } from './Toast';
|
|
4
|
+
import userEvent from '@testing-library/user-event';
|
|
5
|
+
import '@testing-library/jest-dom/vitest';
|
|
6
|
+
|
|
7
|
+
describe('Toast', () => {
|
|
8
|
+
const renderToast = (props = {}) => {
|
|
9
|
+
return render(
|
|
10
|
+
<Toast.Provider>
|
|
11
|
+
<Toast open={true} {...props}>
|
|
12
|
+
Test message
|
|
13
|
+
</Toast>
|
|
14
|
+
<Toast.Viewport />
|
|
15
|
+
</Toast.Provider>,
|
|
16
|
+
);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
describe('rendering', () => {
|
|
20
|
+
it('renders the toast with content', () => {
|
|
21
|
+
renderToast();
|
|
22
|
+
expect(screen.getByText('Test message')).toBeInTheDocument();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('renders with information variant by default', () => {
|
|
26
|
+
const { container } = renderToast();
|
|
27
|
+
const toast = container.querySelector('.ds-toast--information');
|
|
28
|
+
expect(toast).toBeInTheDocument();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('renders with the correct class for each variant', () => {
|
|
32
|
+
const variants = ['information', 'danger', 'success', 'warning'] as const;
|
|
33
|
+
|
|
34
|
+
variants.forEach((variant) => {
|
|
35
|
+
const { container, unmount } = renderToast({ variant });
|
|
36
|
+
const toast = container.querySelector(`.ds-toast--${variant}`);
|
|
37
|
+
expect(toast).toBeInTheDocument();
|
|
38
|
+
unmount();
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe('icons', () => {
|
|
44
|
+
it('renders info icon for information variant', () => {
|
|
45
|
+
const { container } = renderToast({ variant: 'information' });
|
|
46
|
+
expect(container.querySelector('.ds-icon-info')).toBeInTheDocument();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('renders circle-alert icon for danger variant', () => {
|
|
50
|
+
const { container } = renderToast({ variant: 'danger' });
|
|
51
|
+
expect(container.querySelector('.ds-icon-circle-alert')).toBeInTheDocument();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('renders circle-check icon for success variant', () => {
|
|
55
|
+
const { container } = renderToast({ variant: 'success' });
|
|
56
|
+
expect(container.querySelector('.ds-icon-circle-check')).toBeInTheDocument();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('renders triangle-alert icon for warning variant', () => {
|
|
60
|
+
const { container } = renderToast({ variant: 'warning' });
|
|
61
|
+
expect(container.querySelector('.ds-icon-triangle-alert')).toBeInTheDocument();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('renders close icon with screen reader text', () => {
|
|
65
|
+
renderToast();
|
|
66
|
+
expect(screen.getByText('Close')).toBeInTheDocument();
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
describe('interactions', () => {
|
|
71
|
+
it('calls onOpenChange when close button is clicked', async () => {
|
|
72
|
+
const user = userEvent.setup();
|
|
73
|
+
const onOpenChange = vi.fn();
|
|
74
|
+
|
|
75
|
+
render(
|
|
76
|
+
<Toast.Provider>
|
|
77
|
+
<Toast open={true} onOpenChange={onOpenChange}>
|
|
78
|
+
Test message
|
|
79
|
+
</Toast>
|
|
80
|
+
<Toast.Viewport />
|
|
81
|
+
</Toast.Provider>,
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
const closeButton = screen.getByRole('button');
|
|
85
|
+
await user.click(closeButton);
|
|
86
|
+
|
|
87
|
+
expect(onOpenChange).toHaveBeenCalled();
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe('Provider and Viewport', () => {
|
|
92
|
+
it('exposes Provider component', () => {
|
|
93
|
+
expect(Toast.Provider).toBeDefined();
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('exposes Viewport component', () => {
|
|
97
|
+
expect(Toast.Viewport).toBeDefined();
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
describe('custom props', () => {
|
|
102
|
+
it('passes through additional props to Root', () => {
|
|
103
|
+
const { container } = renderToast({
|
|
104
|
+
'data-testid': 'custom-toast',
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
const toast = container.querySelector('[data-testid="custom-toast"]');
|
|
108
|
+
expect(toast).toBeInTheDocument();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('applies custom className alongside default classes', () => {
|
|
112
|
+
renderToast();
|
|
113
|
+
const { container } = render(
|
|
114
|
+
<Toast.Provider>
|
|
115
|
+
<Toast open={true} variant="success">
|
|
116
|
+
Success message
|
|
117
|
+
</Toast>
|
|
118
|
+
<Toast.Viewport />
|
|
119
|
+
</Toast.Provider>,
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
const toast = container.querySelector('.ds-toast.ds-toast--success');
|
|
123
|
+
expect(toast).toBeInTheDocument();
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import classNames from 'classnames';
|
|
2
|
+
import { Toast as RadixToast } from 'radix-ui';
|
|
3
|
+
import { ToastViewport } from './ToastViewport';
|
|
4
|
+
import { Icon } from 'Components/icon/Icon';
|
|
5
|
+
|
|
6
|
+
export type ToastProps = RadixToast.ToastProps & {
|
|
7
|
+
variant?: 'information' | 'danger' | 'success' | 'warning';
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export const Toast = (props: ToastProps) => {
|
|
11
|
+
const { children, variant = 'information', ...rest } = props;
|
|
12
|
+
const classes = classNames('ds-toast', `ds-toast--${variant}`);
|
|
13
|
+
|
|
14
|
+
const iconName = {
|
|
15
|
+
information: 'info',
|
|
16
|
+
danger: 'circle-alert',
|
|
17
|
+
success: 'circle-check',
|
|
18
|
+
warning: 'triangle-alert',
|
|
19
|
+
} as const;
|
|
20
|
+
|
|
21
|
+
const icon = iconName[variant];
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<RadixToast.Root {...rest} className={classes}>
|
|
25
|
+
<Icon className="ds-toast__icon" name={icon} />
|
|
26
|
+
<RadixToast.Title className="ds-toast__title">{children}</RadixToast.Title>
|
|
27
|
+
<RadixToast.Close className="ds-toast__close ds-button ds-button--secondary ds-button--icon-only ds-button--borderless">
|
|
28
|
+
<Icon name="x" screenReaderText="Close" />
|
|
29
|
+
</RadixToast.Close>
|
|
30
|
+
</RadixToast.Root>
|
|
31
|
+
);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
Toast.Provider = RadixToast.Provider;
|
|
35
|
+
Toast.Viewport = ToastViewport;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
.ds-toast__viewport {
|
|
2
|
+
margin: 0;
|
|
3
|
+
padding: 0;
|
|
4
|
+
list-style: none;
|
|
5
|
+
top: 0;
|
|
6
|
+
right: 0;
|
|
7
|
+
position: fixed;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.ds-toast {
|
|
11
|
+
display: flex;
|
|
12
|
+
align-items: center;
|
|
13
|
+
justify-content: space-between;
|
|
14
|
+
border: var(--border-weight) solid;
|
|
15
|
+
gap: var(--toast-spacing-gap);
|
|
16
|
+
padding: var(--toast-spacing-padding);
|
|
17
|
+
border-radius: var(--toast-radius);
|
|
18
|
+
box-shadow: var(--shadow-small);
|
|
19
|
+
font-family: var(--type-body-bold-family);
|
|
20
|
+
font-style: normal;
|
|
21
|
+
font-weight: var(--type-body-bold-weight);
|
|
22
|
+
line-height: var(--line-height-default);
|
|
23
|
+
max-width: var(--toast-max-width);
|
|
24
|
+
|
|
25
|
+
&__icon {
|
|
26
|
+
flex-shrink: 0;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
&__close {
|
|
30
|
+
flex-shrink: 0;
|
|
31
|
+
background-color: transparent;
|
|
32
|
+
border: none;
|
|
33
|
+
color: currentColor;
|
|
34
|
+
padding: 0;
|
|
35
|
+
margin: 0;
|
|
36
|
+
cursor: pointer;
|
|
37
|
+
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
&--information {
|
|
41
|
+
border-color: var(--toast-info-color-border);
|
|
42
|
+
background-color: var(--toast-info-color-background);
|
|
43
|
+
color: var(--toast-info-color-text);
|
|
44
|
+
|
|
45
|
+
.ds-toast__close:hover {
|
|
46
|
+
background: var(--toast-info-icon-hover-color-background);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
&--danger {
|
|
51
|
+
border-color: var(--toast-destructive-color-border);
|
|
52
|
+
background-color: var(--toast-destructive-color-background);
|
|
53
|
+
color: var(--toast-destructive-color-text);
|
|
54
|
+
|
|
55
|
+
.ds-toast__close:hover {
|
|
56
|
+
background: var(--toast-destructive-icon-hover-color-background);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
&--success {
|
|
61
|
+
border-color: var(--toast-success-color-border);
|
|
62
|
+
background-color: var(--toast-success-color-background);
|
|
63
|
+
color: var(--toast-success-color-text);
|
|
64
|
+
|
|
65
|
+
.ds-toast__close:hover {
|
|
66
|
+
background: var(--toast-success-icon-hover-color-background);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
&--warning {
|
|
71
|
+
border-color: var(--toast-warning-color-border);
|
|
72
|
+
background-color: var(--toast-warning-color-background);
|
|
73
|
+
color: var(--toast-warning-color-text);
|
|
74
|
+
|
|
75
|
+
.ds-toast__close:hover {
|
|
76
|
+
background: var(--toast-warning-icon-hover-color-background);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -4,9 +4,6 @@
|
|
|
4
4
|
background: var(--tooltip-color-background);
|
|
5
5
|
padding: var(--tooltip-spacing-vertical) var(--tooltip-spacing-horizontal);
|
|
6
6
|
border-radius: var(--border-radius-small);
|
|
7
|
-
font-size: var(--type-body-p-size);
|
|
8
|
-
font-family: var(--type-body-p-family);
|
|
9
|
-
font-weight: var(--type-body-p-weight);
|
|
10
7
|
|
|
11
8
|
&-arrow {
|
|
12
9
|
fill: var(--tooltip-color-background);
|
package/src/global.scss
CHANGED
|
@@ -24,5 +24,13 @@
|
|
|
24
24
|
-webkit-font-smoothing: antialiased;
|
|
25
25
|
-moz-osx-font-smoothing: grayscale;
|
|
26
26
|
text-rendering: optimizeLegibility;
|
|
27
|
-
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
body {
|
|
30
|
+
*[class^='ds-'], *[class*=' ds-'] {
|
|
31
|
+
font-family: var(--font-family-standard);
|
|
32
|
+
font-size: var(--type-body-p-size);
|
|
33
|
+
font-weight: var(--font-weight-regular);
|
|
34
|
+
box-sizing: border-box;
|
|
35
|
+
}
|
|
28
36
|
}
|
package/src/index.scss
CHANGED
|
@@ -28,4 +28,8 @@
|
|
|
28
28
|
@use "components/modal/modal.scss";
|
|
29
29
|
@use "components/modal/modalManager/modalManager.scss";
|
|
30
30
|
@use "components/table/columnFilters/columnFilters.scss";
|
|
31
|
+
@use "components/banner/banner.scss";
|
|
32
|
+
@use "components/editableText/editableText.scss";
|
|
33
|
+
@use "components/progress/progress.scss";
|
|
34
|
+
@use "components/toast/toast.scss";
|
|
31
35
|
@import "https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap";
|
package/src/index.ts
CHANGED
|
@@ -26,3 +26,7 @@ export { Modal } from 'Components/modal/Modal';
|
|
|
26
26
|
export { ModalManager } from 'Components/modal/modalManager/ModalManager';
|
|
27
27
|
export { DSDefaultColDef } from 'Components/table/DSDefaultColDef';
|
|
28
28
|
export { DefaultCellRenderer } from 'Components/table/cellRenderers/DefaultCellRenderer';
|
|
29
|
+
export { Banner, type BannerProps, BANNER_LEVEL } from 'Components/banner/Banner';
|
|
30
|
+
export { EditableText } from 'Components/editableText/EditableText';
|
|
31
|
+
export { Progress } from 'Components/progress/Progress';
|
|
32
|
+
export { Toast } from 'Components/toast/Toast';
|
package/src/tokens.scss
CHANGED
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
--size-control-medium: 3rem;
|
|
26
26
|
--size-control-large: 4rem;
|
|
27
27
|
--size-control-xlarge: 5rem;
|
|
28
|
+
--shadow-small: 0 4px 12px 0 rgba(32, 32, 32, 0.08);
|
|
28
29
|
--page-sign-in-color-background: #fffcf8;
|
|
29
30
|
--color-grey-100: #efefef;
|
|
30
31
|
--color-grey-200: #dfdfdf;
|
|
@@ -334,6 +335,7 @@
|
|
|
334
335
|
--modal-radius: var(--border-radius-small);
|
|
335
336
|
--modal-color-background: var(--color-grey-050);
|
|
336
337
|
--modal-min-width: 37.5rem;
|
|
338
|
+
--toast-max-width: 360px;
|
|
337
339
|
--toast-info-icon-hover-color-background: var(--color-semantic-info-100);
|
|
338
340
|
--toast-info-icon-color-icon: var(--color-semantic-info-800);
|
|
339
341
|
--toast-info-color-text: var(--color-semantic-info-800);
|