uikit-react-public 0.14.21 → 0.17.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -2
- package/dist/components/Accordion/Accordion.Heading.d.ts +4 -4
- package/dist/components/Accordion/Accordion.Panel.d.ts +2 -2
- package/dist/components/Accordion/Accordion.d.ts +1 -1
- package/dist/components/Accordion/Accordion.stories.d.ts +57 -0
- package/dist/components/Accordion/index.d.ts +2 -0
- package/dist/components/Avatar/Avatar.stories.d.ts +107 -1
- package/dist/components/Button/Button.d.ts +1 -0
- package/dist/components/Calendar/index.d.ts +1 -1
- package/dist/components/Datepicker/Datepicker.d.ts +1 -1
- package/dist/components/Datepicker/Datepicker.stories.d.ts +4 -3
- package/dist/components/Datepicker/Datepicker.types.d.ts +4 -5
- package/dist/components/Datepicker/subcomponents/CustomDatepicker.d.ts +4 -1
- package/dist/components/Datepicker/subcomponents/DatepickerInput.d.ts +15 -2
- package/dist/components/Datepicker/subcomponents/Panel.d.ts +1 -1
- package/dist/components/Datepicker/subcomponents/VisibleField.d.ts +6 -1
- package/dist/components/Datepicker/subcomponents/index.d.ts +0 -1
- package/dist/components/Datepicker/utils/index.d.ts +0 -1
- package/dist/components/Dialog/BaseDialog.d.ts +2 -1
- package/dist/components/Dialog/Dialog.d.ts +2 -0
- package/dist/components/Header/Header.d.ts +4 -1
- package/dist/components/Header/Header.stories.d.ts +40 -0
- package/dist/components/Main/Main.d.ts +21 -0
- package/dist/components/Main/Main.stories.d.ts +15 -0
- package/dist/components/Main/index.d.ts +2 -0
- package/dist/components/NativeDatepicker/NativeDatepicker.d.ts +3 -0
- package/dist/components/NativeDatepicker/NativeDatepicker.stories.d.ts +36 -0
- package/dist/components/NativeDatepicker/NativeDatepicker.types.d.ts +10 -0
- package/dist/components/NativeDatepicker/index.d.ts +2 -0
- package/dist/components/{Datepicker → NativeDatepicker}/utils/dateToLocaleISOString/dateToLocaleISOString.d.ts +1 -1
- package/dist/components/NativeDatepicker/utils/dateToLocaleISOString/dateToLocaleISOString.test.d.ts +1 -0
- package/dist/components/NativeDatepicker/utils/index.d.ts +1 -0
- package/dist/components/Select/Select.stories.d.ts +154 -2
- package/dist/components/Select/Select.types.d.ts +51 -22
- package/dist/components/Select/subcomponents/CustomOption.d.ts +1 -1
- package/dist/components/Select/subcomponents/CustomSelect.d.ts +3 -2
- package/dist/components/Select/subcomponents/FilterInput.d.ts +14 -0
- package/dist/components/Select/subcomponents/NativeSelect.d.ts +5 -1
- package/dist/components/Select/subcomponents/VisibleField.d.ts +3 -1
- package/dist/components/Select/subcomponents/index.d.ts +1 -0
- package/dist/components/WeekPicker/WeekPicker.d.ts +2 -2
- package/dist/components/WeekPicker/WeekPicker.stories.d.ts +41 -0
- package/dist/components/WeekPicker/WeekPicker.types.d.ts +16 -0
- package/dist/components/WeekPicker/index.d.ts +1 -0
- package/dist/components/WeekPicker/subcomponents/CustomDatepicker.d.ts +1 -1
- package/dist/components/index.d.ts +8 -0
- package/dist/hooks/useFocusTrap.d.ts +2 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +4366 -3768
- package/dist/utils/__tests__/announce.test.d.ts +1 -0
- package/dist/utils/announce.d.ts +6 -0
- package/dist/utils/index.d.ts +1 -0
- package/lib/components/Accordion/Accordion.Heading.tsx +27 -8
- package/lib/components/Accordion/Accordion.Panel.tsx +11 -3
- package/lib/components/Accordion/Accordion.stories.tsx +139 -0
- package/lib/components/Accordion/Accordion.tsx +10 -8
- package/lib/components/Accordion/__tests__/__snapshots__/Accordion.test.tsx.snap +7 -7
- package/lib/components/Accordion/index.ts +2 -0
- package/lib/components/Alert/Alert.stories.tsx +1 -1
- package/lib/components/Avatar/Avatar.mdx +117 -0
- package/lib/components/Avatar/Avatar.stories.tsx +110 -2
- package/lib/components/Blanket/Blanket.stories.tsx +1 -1
- package/lib/components/Button/Button.stories.tsx +1 -1
- package/lib/components/Button/Button.tsx +1 -0
- package/lib/components/Calendar/Calendar.stories.tsx +12 -32
- package/lib/components/Calendar/__tests__/Calendar.test.tsx +23 -15
- package/lib/components/Calendar/index.ts +1 -5
- package/lib/components/Calendar/subcomponents/AcademicWeeks.tsx +2 -1
- package/lib/components/Calendar/subcomponents/ColumnHeading.tsx +5 -1
- package/lib/components/Calendar/subcomponents/EventDot.tsx +2 -1
- package/lib/components/Calendar/subcomponents/index.ts +1 -1
- package/lib/components/Calendar/utils/getDatesForCalendarGrid/getDatesForCalendarGrid.ts +43 -11
- package/lib/components/Calendar/utils/normaliseMonth/normaliseMonth.test.ts +5 -5
- package/lib/components/Datepicker/Datepicker.lld.md +108 -0
- package/lib/components/Datepicker/Datepicker.stories.tsx +44 -5
- package/lib/components/Datepicker/Datepicker.tsx +14 -36
- package/lib/components/Datepicker/Datepicker.types.ts +5 -14
- package/lib/components/Datepicker/__tests__/Datepicker.test.tsx +150 -8
- package/lib/components/Datepicker/__tests__/__snapshots__/Datepicker.test.tsx.snap +10 -4
- package/lib/components/Datepicker/subcomponents/CustomDatepicker.tsx +39 -5
- package/lib/components/Datepicker/subcomponents/DatepickerInput.tsx +30 -17
- package/lib/components/Datepicker/subcomponents/Panel.tsx +6 -2
- package/lib/components/Datepicker/subcomponents/VisibleField.tsx +40 -3
- package/lib/components/Datepicker/subcomponents/index.ts +0 -1
- package/lib/components/Datepicker/utils/index.ts +0 -1
- package/lib/components/Dialog/BaseDialog.tsx +11 -0
- package/lib/components/Dialog/Dialog.tsx +8 -1
- package/lib/components/Dialog/DialogBody.tsx +5 -1
- package/lib/components/Dialog/DialogHeader.tsx +2 -1
- package/lib/components/Divider/Divider.stories.tsx +1 -1
- package/lib/components/Field/ErrorText.tsx +1 -0
- package/lib/components/Field/Field.stories.tsx +1 -1
- package/lib/components/Field/__tests__/Field.test.tsx +13 -0
- package/lib/components/FileInput/FileInput.stories.tsx +1 -1
- package/lib/components/Footer/Footer.stories.tsx +1 -1
- package/lib/components/Footer/__tests__/__snapshots__/Footer.test.tsx.snap +3 -3
- package/lib/components/Header/Header.mdx +52 -0
- package/lib/components/Header/Header.stories.tsx +98 -0
- package/lib/components/Header/Header.tsx +51 -6
- package/lib/components/Header/__tests__/Header.test.tsx +17 -1
- package/lib/components/Heading/Heading.stories.tsx +1 -1
- package/lib/components/Icon/Icon.stories.tsx +1 -1
- package/lib/components/IconButton/IconButton.stories.tsx +1 -1
- package/lib/components/Input/Input.stories.tsx +1 -1
- package/lib/components/Label/Label.stories.tsx +1 -1
- package/lib/components/Main/Main.stories.tsx +36 -0
- package/lib/components/Main/Main.tsx +46 -0
- package/lib/components/Main/__tests__/Main.test.tsx +80 -0
- package/lib/components/Main/__tests__/__snapshots__/Main.test.tsx.snap +33 -0
- package/lib/components/Main/index.ts +2 -0
- package/lib/components/NativeDatepicker/NativeDatepicker.stories.tsx +100 -0
- package/lib/components/{Datepicker/subcomponents → NativeDatepicker}/NativeDatepicker.tsx +14 -15
- package/lib/components/NativeDatepicker/NativeDatepicker.types.ts +19 -0
- package/lib/components/NativeDatepicker/index.ts +2 -0
- package/lib/components/{Datepicker → NativeDatepicker}/utils/dateToLocaleISOString/dateToLocaleISOString.ts +1 -1
- package/lib/components/NativeDatepicker/utils/index.ts +1 -0
- package/lib/components/Pagination/PaginationControls.tsx +55 -12
- package/lib/components/Pagination/PaginationInfo.tsx +5 -1
- package/lib/components/Paragraph/Paragraph.stories.tsx +1 -1
- package/lib/components/Search/Search.stories.tsx +1 -1
- package/lib/components/Search/Search.tsx +4 -1
- package/lib/components/Search/__tests__/Search.test.tsx +19 -1
- package/lib/components/Select/Select.mdx +169 -0
- package/lib/components/Select/Select.stories.tsx +191 -43
- package/lib/components/Select/Select.tsx +36 -12
- package/lib/components/Select/Select.types.ts +66 -48
- package/lib/components/Select/__tests__/Select.test.tsx +448 -7
- package/lib/components/Select/__tests__/__snapshots__/Select.test.tsx.snap +1 -1
- package/lib/components/Select/subcomponents/CustomOption.tsx +2 -1
- package/lib/components/Select/subcomponents/CustomSelect.tsx +303 -33
- package/lib/components/Select/subcomponents/FilterInput.tsx +80 -0
- package/lib/components/Select/subcomponents/NativeSelect.tsx +13 -1
- package/lib/components/Select/subcomponents/VisibleField.tsx +11 -3
- package/lib/components/Select/subcomponents/index.tsx +1 -0
- package/lib/components/Snackbar/Snackbar.stories.tsx +1 -1
- package/lib/components/Spinner/Spinner.stories.tsx +1 -1
- package/lib/components/Textarea/Textarea.stories.tsx +1 -1
- package/lib/components/Timepicker/Timepicker.tsx +4 -0
- package/lib/components/Timepicker/__tests__/__snapshots__/Timepicker.test.tsx.snap +2 -2
- package/lib/components/Toggle/Toggle.stories.tsx +1 -1
- package/lib/components/Tooltip/Tooltip.stories.tsx +1 -1
- package/lib/components/WeekPicker/WeekPicker.stories.tsx +147 -0
- package/lib/components/WeekPicker/WeekPicker.tsx +2 -2
- package/lib/components/WeekPicker/WeekPicker.types.ts +21 -0
- package/lib/components/WeekPicker/index.ts +1 -0
- package/lib/components/WeekPicker/subcomponents/CustomDatepicker.tsx +1 -1
- package/lib/components/common/Common.mdx +1 -1
- package/lib/components/index.ts +11 -2
- package/lib/hooks/useFocusTrap.ts +40 -4
- package/lib/index.ts +1 -0
- package/lib/utils/__tests__/announce.test.ts +121 -0
- package/lib/utils/announce.ts +134 -0
- package/lib/utils/index.ts +1 -0
- package/package.json +3 -6
- package/dist/components/Datepicker/subcomponents/NativeDatepicker.d.ts +0 -6
- package/lib/components/Accordion/Accordion.stories.tsx.NOT_READY +0 -93
- /package/dist/components/{Datepicker/utils/dateToLocaleISOString/dateToLocaleISOString.test.d.ts → Main/__tests__/Main.test.d.ts} +0 -0
- /package/lib/components/{Datepicker → NativeDatepicker}/utils/dateToLocaleISOString/dateToLocaleISOString.test.ts +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as announce } from './announce';
|
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
import React, { HTMLAttributes
|
|
1
|
+
import React, { HTMLAttributes } from 'react';
|
|
2
2
|
import { css, cx } from '@emotion/css';
|
|
3
3
|
import useTheme from '../../theme/useTheme';
|
|
4
4
|
import Icon from '../Icon/Icon';
|
|
5
5
|
import { useAccordionContext } from './Accordion';
|
|
6
6
|
|
|
7
|
-
export const NAME = 'ucl-accordion__heading';
|
|
7
|
+
export const NAME = 'ucl-uikit-accordion__heading';
|
|
8
8
|
|
|
9
|
-
interface AccordionHeadingProps
|
|
10
|
-
|
|
11
|
-
as?: keyof JSX.IntrinsicElements;
|
|
9
|
+
export interface AccordionHeadingProps extends HTMLAttributes<HTMLElement> {
|
|
10
|
+
as?: keyof HTMLElementTagNameMap;
|
|
12
11
|
testId?: string;
|
|
13
12
|
}
|
|
14
13
|
|
|
@@ -19,13 +18,21 @@ const AccordionHeading: React.FC<AccordionHeadingProps> = ({
|
|
|
19
18
|
className,
|
|
20
19
|
}) => {
|
|
21
20
|
const [theme] = useTheme();
|
|
22
|
-
const { isOpen, toggleAccordion, disabled, size } =
|
|
23
|
-
|
|
21
|
+
const { isOpen, toggleAccordion, disabled, size } = useAccordionContext();
|
|
22
|
+
|
|
23
|
+
const handleKeyDown: React.KeyboardEventHandler<HTMLElement> = (e) => {
|
|
24
|
+
if (disabled) return;
|
|
25
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
26
|
+
e.preventDefault();
|
|
27
|
+
toggleAccordion();
|
|
28
|
+
}
|
|
29
|
+
};
|
|
24
30
|
|
|
25
31
|
const headingStyle = cx(
|
|
26
32
|
NAME,
|
|
27
33
|
css`
|
|
28
34
|
display: flex;
|
|
35
|
+
position: relative;
|
|
29
36
|
justify-content: space-between;
|
|
30
37
|
align-items: center;
|
|
31
38
|
padding: ${theme.padding.p16};
|
|
@@ -35,6 +42,14 @@ const AccordionHeading: React.FC<AccordionHeadingProps> = ({
|
|
|
35
42
|
transition:
|
|
36
43
|
background-color 0.3s ease,
|
|
37
44
|
box-shadow 0.3s ease;
|
|
45
|
+
margin: 0;
|
|
46
|
+
border: none;
|
|
47
|
+
outline: none;
|
|
48
|
+
text-decoration: none;
|
|
49
|
+
box-sizing: border-box;
|
|
50
|
+
width: 100%;
|
|
51
|
+
font-size: inherit;
|
|
52
|
+
line-height: inherit;
|
|
38
53
|
|
|
39
54
|
&:hover {
|
|
40
55
|
background-color: ${!disabled
|
|
@@ -45,6 +60,7 @@ const AccordionHeading: React.FC<AccordionHeadingProps> = ({
|
|
|
45
60
|
&:focus-visible {
|
|
46
61
|
outline: none;
|
|
47
62
|
box-shadow: ${theme.boxShadow.focus};
|
|
63
|
+
z-index: 20;
|
|
48
64
|
}
|
|
49
65
|
`,
|
|
50
66
|
className
|
|
@@ -54,6 +70,7 @@ const AccordionHeading: React.FC<AccordionHeadingProps> = ({
|
|
|
54
70
|
<Component
|
|
55
71
|
className={headingStyle}
|
|
56
72
|
data-testid={testId}
|
|
73
|
+
onKeyDown={handleKeyDown}
|
|
57
74
|
onClick={!disabled ? toggleAccordion : undefined}
|
|
58
75
|
role='button'
|
|
59
76
|
tabIndex={0}
|
|
@@ -65,7 +82,9 @@ const AccordionHeading: React.FC<AccordionHeadingProps> = ({
|
|
|
65
82
|
className={css`
|
|
66
83
|
transform: rotate(${isOpen ? '180deg' : '0deg'});
|
|
67
84
|
transition: transform 0.15s ease;
|
|
68
|
-
color: ${
|
|
85
|
+
color: ${disabled
|
|
86
|
+
? theme.color.text.disabled
|
|
87
|
+
: theme.color.interaction.blue70};
|
|
69
88
|
`}
|
|
70
89
|
/>
|
|
71
90
|
</Component>
|
|
@@ -3,10 +3,9 @@ import { css, cx } from '@emotion/css';
|
|
|
3
3
|
import useTheme from '../../theme/useTheme';
|
|
4
4
|
import { useAccordionContext } from './Accordion';
|
|
5
5
|
|
|
6
|
-
export const NAME = 'ucl-accordion__panel';
|
|
6
|
+
export const NAME = 'ucl-uikit-accordion__panel';
|
|
7
7
|
|
|
8
|
-
interface AccordionPanelProps
|
|
9
|
-
extends HTMLAttributes<HTMLDivElement> {
|
|
8
|
+
export interface AccordionPanelProps extends HTMLAttributes<HTMLDivElement> {
|
|
10
9
|
testId?: string;
|
|
11
10
|
}
|
|
12
11
|
|
|
@@ -24,12 +23,21 @@ const AccordionPanel: React.FC<AccordionPanelProps> = ({
|
|
|
24
23
|
NAME,
|
|
25
24
|
|
|
26
25
|
css`
|
|
26
|
+
position: relative;
|
|
27
|
+
width: 100%;
|
|
28
|
+
max-width: 100%;
|
|
29
|
+
min-width: 0;
|
|
30
|
+
flex-shrink: 1;
|
|
27
31
|
padding: ${theme.padding.p16};
|
|
28
32
|
font-family: ${theme.font.family.primary};
|
|
29
33
|
font-weight: ${theme.font.weight.regular};
|
|
30
34
|
background-color: ${theme.color.neutral.white};
|
|
31
35
|
font-size: ${theme.font.size.f16};
|
|
32
36
|
color: ${theme.color.text.primary};
|
|
37
|
+
box-sizing: border-box;
|
|
38
|
+
overflow: hidden;
|
|
39
|
+
overflow-wrap: break-word;
|
|
40
|
+
word-break: break-word;
|
|
33
41
|
`,
|
|
34
42
|
className
|
|
35
43
|
);
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import Accordion from './Accordion';
|
|
3
|
+
|
|
4
|
+
const meta = {
|
|
5
|
+
title: 'Components/Accordion',
|
|
6
|
+
component: Accordion,
|
|
7
|
+
parameters: {
|
|
8
|
+
layout: 'centered',
|
|
9
|
+
},
|
|
10
|
+
argTypes: {
|
|
11
|
+
size: {
|
|
12
|
+
options: ['small', 'medium'],
|
|
13
|
+
control: { type: 'select' },
|
|
14
|
+
type: 'string',
|
|
15
|
+
},
|
|
16
|
+
disabled: {
|
|
17
|
+
control: { type: 'boolean' },
|
|
18
|
+
type: 'boolean',
|
|
19
|
+
},
|
|
20
|
+
isOpen: {
|
|
21
|
+
control: { type: 'boolean' },
|
|
22
|
+
type: 'boolean',
|
|
23
|
+
},
|
|
24
|
+
testId: {
|
|
25
|
+
control: { type: 'text' },
|
|
26
|
+
type: 'string',
|
|
27
|
+
},
|
|
28
|
+
className: {
|
|
29
|
+
control: { type: 'text' },
|
|
30
|
+
type: 'string',
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
args: {
|
|
34
|
+
isOpen: false,
|
|
35
|
+
disabled: false,
|
|
36
|
+
size: 'medium',
|
|
37
|
+
},
|
|
38
|
+
tags: ['autodocs'],
|
|
39
|
+
} satisfies Meta<typeof Accordion>;
|
|
40
|
+
|
|
41
|
+
export default meta;
|
|
42
|
+
|
|
43
|
+
type Story = StoryObj<typeof meta>;
|
|
44
|
+
|
|
45
|
+
export const Default: Story = {
|
|
46
|
+
args: {
|
|
47
|
+
isOpen: false,
|
|
48
|
+
disabled: false,
|
|
49
|
+
size: 'medium',
|
|
50
|
+
},
|
|
51
|
+
render: (args) => (
|
|
52
|
+
<Accordion {...args}>
|
|
53
|
+
<Accordion.Heading>Heading</Accordion.Heading>
|
|
54
|
+
<Accordion.Panel>Panel Content</Accordion.Panel>
|
|
55
|
+
</Accordion>
|
|
56
|
+
),
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export const Disabled: Story = {
|
|
60
|
+
args: {
|
|
61
|
+
isOpen: false,
|
|
62
|
+
disabled: true,
|
|
63
|
+
size: 'medium',
|
|
64
|
+
},
|
|
65
|
+
render: (args) => (
|
|
66
|
+
<Accordion {...args}>
|
|
67
|
+
<Accordion.Heading>Heading</Accordion.Heading>
|
|
68
|
+
<Accordion.Panel>Panel Content</Accordion.Panel>
|
|
69
|
+
</Accordion>
|
|
70
|
+
),
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export const SmallSize: Story = {
|
|
74
|
+
args: {
|
|
75
|
+
isOpen: false,
|
|
76
|
+
disabled: false,
|
|
77
|
+
size: 'small',
|
|
78
|
+
},
|
|
79
|
+
render: (args) => (
|
|
80
|
+
<Accordion {...args}>
|
|
81
|
+
<Accordion.Heading>Heading</Accordion.Heading>
|
|
82
|
+
<Accordion.Panel>Panel Content</Accordion.Panel>
|
|
83
|
+
</Accordion>
|
|
84
|
+
),
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
export const CustomHeadingElement: Story = {
|
|
88
|
+
args: {
|
|
89
|
+
isOpen: false,
|
|
90
|
+
disabled: false,
|
|
91
|
+
size: 'medium',
|
|
92
|
+
},
|
|
93
|
+
render: (args) => (
|
|
94
|
+
<Accordion {...args}>
|
|
95
|
+
<Accordion.Heading as='h2'>Custom Heading as H2</Accordion.Heading>
|
|
96
|
+
<Accordion.Panel>
|
|
97
|
+
<p>This is the content of the accordion.</p>
|
|
98
|
+
</Accordion.Panel>
|
|
99
|
+
</Accordion>
|
|
100
|
+
),
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
export const MultipleAccordions: Story = {
|
|
104
|
+
args: {
|
|
105
|
+
isOpen: false,
|
|
106
|
+
disabled: false,
|
|
107
|
+
size: 'medium',
|
|
108
|
+
},
|
|
109
|
+
render: (args) => (
|
|
110
|
+
<div style={{ width: '100%', maxWidth: 660 }}>
|
|
111
|
+
<Accordion {...args}>
|
|
112
|
+
<Accordion.Heading>Heading</Accordion.Heading>
|
|
113
|
+
<Accordion.Panel>
|
|
114
|
+
<p>
|
|
115
|
+
This accordion reveals additional details. For enquiries, email
|
|
116
|
+
<a href='mailto:admissions@ucl.ac.uk'> admissions@ucl.ac.uk</a>.
|
|
117
|
+
</p>
|
|
118
|
+
<p>
|
|
119
|
+
You can also visit our FAQs for more information and guidance on
|
|
120
|
+
next steps.
|
|
121
|
+
</p>
|
|
122
|
+
</Accordion.Panel>
|
|
123
|
+
</Accordion>
|
|
124
|
+
<Accordion {...args}>
|
|
125
|
+
<Accordion.Heading>Heading</Accordion.Heading>
|
|
126
|
+
<Accordion.Panel>
|
|
127
|
+
<p>
|
|
128
|
+
This accordion reveals additional details. For enquiries, email
|
|
129
|
+
<a href='mailto:admissions@ucl.ac.uk'> admissions@ucl.ac.uk</a>.
|
|
130
|
+
</p>
|
|
131
|
+
<p>
|
|
132
|
+
You can also visit our FAQs for more information and guidance on
|
|
133
|
+
next steps.
|
|
134
|
+
</p>
|
|
135
|
+
</Accordion.Panel>
|
|
136
|
+
</Accordion>
|
|
137
|
+
</div>
|
|
138
|
+
),
|
|
139
|
+
};
|
|
@@ -9,7 +9,7 @@ import AccordionPanel from './Accordion.Panel';
|
|
|
9
9
|
import { css, cx } from '@emotion/css';
|
|
10
10
|
import useTheme from '../../theme/useTheme';
|
|
11
11
|
|
|
12
|
-
export const NAME = 'ucl-accordion';
|
|
12
|
+
export const NAME = 'ucl-uikit-accordion';
|
|
13
13
|
|
|
14
14
|
interface AccordionContextProps {
|
|
15
15
|
isOpen: boolean;
|
|
@@ -18,9 +18,9 @@ interface AccordionContextProps {
|
|
|
18
18
|
size: 'small' | 'medium';
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
const AccordionContext = createContext<
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
const AccordionContext = createContext<AccordionContextProps | undefined>(
|
|
22
|
+
undefined
|
|
23
|
+
);
|
|
24
24
|
|
|
25
25
|
export const useAccordionContext = () => {
|
|
26
26
|
const context = useContext(AccordionContext);
|
|
@@ -32,8 +32,7 @@ export const useAccordionContext = () => {
|
|
|
32
32
|
return context;
|
|
33
33
|
};
|
|
34
34
|
|
|
35
|
-
export interface AccordionProps
|
|
36
|
-
extends HTMLAttributes<HTMLDivElement> {
|
|
35
|
+
export interface AccordionProps extends HTMLAttributes<HTMLDivElement> {
|
|
37
36
|
testId?: string;
|
|
38
37
|
size?: 'small' | 'medium';
|
|
39
38
|
disabled?: boolean;
|
|
@@ -63,11 +62,15 @@ const Accordion: React.FC<AccordionProps> & {
|
|
|
63
62
|
const accordionStyle = cx(
|
|
64
63
|
NAME,
|
|
65
64
|
css`
|
|
66
|
-
|
|
65
|
+
width: 660px;
|
|
66
|
+
min-width: 288px;
|
|
67
|
+
max-width: 660px;
|
|
68
|
+
box-sizing: border-box;
|
|
67
69
|
border: 1px solid ${theme.color.neutral.grey10};
|
|
68
70
|
background-color: ${theme.color.neutral.white};
|
|
69
71
|
font-family: ${theme.font.family.primary};
|
|
70
72
|
font-size: ${theme.font.size.f16};
|
|
73
|
+
flex-shrink: 1;
|
|
71
74
|
`,
|
|
72
75
|
size === 'small' &&
|
|
73
76
|
css`
|
|
@@ -83,7 +86,6 @@ const Accordion: React.FC<AccordionProps> & {
|
|
|
83
86
|
pointer-events: none;
|
|
84
87
|
background-color: ${theme.color.link.disabled};
|
|
85
88
|
color: ${theme.color.text.muted};
|
|
86
|
-
// border-color: ${theme.color.text.muted};
|
|
87
89
|
`,
|
|
88
90
|
className
|
|
89
91
|
);
|
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
exports[`Accordion > snapshot: no props 1`] = `
|
|
4
4
|
<div
|
|
5
5
|
aria-disabled="false"
|
|
6
|
-
class="ucl-accordion css-
|
|
7
|
-
data-testid="ucl-accordion"
|
|
6
|
+
class="ucl-uikit-accordion css-rs0koa"
|
|
7
|
+
data-testid="ucl-uikit-accordion"
|
|
8
8
|
>
|
|
9
9
|
<div
|
|
10
10
|
aria-expanded="false"
|
|
11
|
-
class="ucl-accordion__heading css-
|
|
12
|
-
data-testid="ucl-accordion__heading"
|
|
11
|
+
class="ucl-uikit-accordion__heading css-1yg3n9"
|
|
12
|
+
data-testid="ucl-uikit-accordion__heading"
|
|
13
13
|
role="button"
|
|
14
14
|
tabindex="0"
|
|
15
15
|
>
|
|
@@ -38,13 +38,13 @@ exports[`Accordion > snapshot: no props 1`] = `
|
|
|
38
38
|
exports[`Accordion > snapshot: testId prop 1`] = `
|
|
39
39
|
<div
|
|
40
40
|
aria-disabled="false"
|
|
41
|
-
class="ucl-accordion css-
|
|
41
|
+
class="ucl-uikit-accordion css-rs0koa"
|
|
42
42
|
data-testid="abc"
|
|
43
43
|
>
|
|
44
44
|
<div
|
|
45
45
|
aria-expanded="false"
|
|
46
|
-
class="ucl-accordion__heading css-
|
|
47
|
-
data-testid="ucl-accordion__heading"
|
|
46
|
+
class="ucl-uikit-accordion__heading css-1yg3n9"
|
|
47
|
+
data-testid="ucl-uikit-accordion__heading"
|
|
48
48
|
role="button"
|
|
49
49
|
tabindex="0"
|
|
50
50
|
>
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import * as AvatarStories from "./Avatar.stories";
|
|
2
|
+
import { Meta, Title, Subtitle, Canvas, Controls, ArgTypes } from "@storybook/blocks";
|
|
3
|
+
|
|
4
|
+
export const usage = {
|
|
5
|
+
image: `<Avatar
|
|
6
|
+
variant="image"
|
|
7
|
+
imageUrl="/sample-avatar-photo.jpg"
|
|
8
|
+
name="Jane Doe"
|
|
9
|
+
/>`,
|
|
10
|
+
initials: `<Avatar variant="initials" name="Beverley Haggis" />`,
|
|
11
|
+
icon: `<Avatar variant="icon" aria-label="Default avatar" />`,
|
|
12
|
+
size: `<Avatar
|
|
13
|
+
variant="image"
|
|
14
|
+
imageUrl="/sample-avatar-photo.jpg"
|
|
15
|
+
name="Avatar size"
|
|
16
|
+
size={72}
|
|
17
|
+
/>`,
|
|
18
|
+
disabled: `<Avatar
|
|
19
|
+
variant="image"
|
|
20
|
+
imageUrl="/sample-avatar-photo.jpg"
|
|
21
|
+
name="Disabled avatar"
|
|
22
|
+
disabled
|
|
23
|
+
/>`,
|
|
24
|
+
fallback: `<Avatar
|
|
25
|
+
variant="image"
|
|
26
|
+
imageUrl="https://example.com/does-not-exist.jpg"
|
|
27
|
+
name="Fallback to initials"
|
|
28
|
+
/>`,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
<Meta of={AvatarStories} />
|
|
32
|
+
<Title />
|
|
33
|
+
<Subtitle>A circular avatar with graceful fallbacks for images, initials, and icons.</Subtitle>
|
|
34
|
+
|
|
35
|
+
Use `<Avatar>` anywhere you need a compact visual identifier for a user or entity. The component renders a button for easy focus styling and clickability, and progressively falls back from an image to initials to icon, based on the props you provide.
|
|
36
|
+
|
|
37
|
+
Key points:
|
|
38
|
+
- `variant="image"` tries to render `imageUrl` first, then initials (from `name`) if the image fails.
|
|
39
|
+
- `variant="initials"` renders initials from `name`; if none are found, it falls back to the icon.
|
|
40
|
+
- `variant="icon"` always renders the default icon.
|
|
41
|
+
- Fixed size options (`48 | 56 | 72 | 80`).
|
|
42
|
+
- Disabled state mutes colors and blocks interaction.
|
|
43
|
+
|
|
44
|
+
## Variants
|
|
45
|
+
|
|
46
|
+
### Image (default)
|
|
47
|
+
Renders a photo when `imageUrl` is available. If the image fails to load, initials are shown instead.
|
|
48
|
+
|
|
49
|
+
<Canvas
|
|
50
|
+
of={AvatarStories.Image}
|
|
51
|
+
sourceState="shown"
|
|
52
|
+
source={{ code: usage.image }}
|
|
53
|
+
/>
|
|
54
|
+
|
|
55
|
+
### Initials
|
|
56
|
+
Pass a `name` to derive initials (up to two characters). Use when you don't have a photo or want to avoid loading images.
|
|
57
|
+
|
|
58
|
+
<Canvas
|
|
59
|
+
of={AvatarStories.Initials}
|
|
60
|
+
sourceState="shown"
|
|
61
|
+
source={{ code: usage.initials }}
|
|
62
|
+
/>
|
|
63
|
+
|
|
64
|
+
### Icon
|
|
65
|
+
Always displays the default avatar icon. Helpful as a generic placeholder.
|
|
66
|
+
|
|
67
|
+
<Canvas
|
|
68
|
+
of={AvatarStories.Icon}
|
|
69
|
+
sourceState="shown"
|
|
70
|
+
source={{ code: usage.icon }}
|
|
71
|
+
/>
|
|
72
|
+
|
|
73
|
+
## Sizes
|
|
74
|
+
Pick from the supported sizes to match your layout. Larger sizes are useful for profile headers; smaller for lists or chips.
|
|
75
|
+
|
|
76
|
+
<Canvas
|
|
77
|
+
of={AvatarStories.Sizes}
|
|
78
|
+
sourceState="shown"
|
|
79
|
+
source={{ code: usage.size }}
|
|
80
|
+
/>
|
|
81
|
+
|
|
82
|
+
## Disabled
|
|
83
|
+
Prevents interaction and applies muted colors while keeping layout intact.
|
|
84
|
+
|
|
85
|
+
<Canvas
|
|
86
|
+
of={AvatarStories.Disabled}
|
|
87
|
+
sourceState="shown"
|
|
88
|
+
source={{ code: usage.disabled }}
|
|
89
|
+
/>
|
|
90
|
+
|
|
91
|
+
## Fallback behaviour
|
|
92
|
+
Image errors are handled gracefully: the component logs an error, then switches to initials. If no initials are available, it renders the icon.
|
|
93
|
+
|
|
94
|
+
<Canvas
|
|
95
|
+
of={AvatarStories.BrokenImageFallback}
|
|
96
|
+
sourceState="shown"
|
|
97
|
+
source={{ code: usage.fallback }}
|
|
98
|
+
/>
|
|
99
|
+
|
|
100
|
+
## Accessibility
|
|
101
|
+
- Under the hood, the avatar is a `<button>`. It can receive focus and `onClick` if provided.
|
|
102
|
+
- Provide a meaningful `aria-label` when no visible text is present (e.g. the icon variant).
|
|
103
|
+
- `alt` text on the image defaults to `name` for screen readers.
|
|
104
|
+
- Sizes remain square circles; focus styles come from the theme for keyboard visibility.
|
|
105
|
+
|
|
106
|
+
## Props
|
|
107
|
+
|
|
108
|
+
Full props specification for `<Avatar>` is below.
|
|
109
|
+
|
|
110
|
+
<Canvas
|
|
111
|
+
of={AvatarStories.Default}
|
|
112
|
+
sourceState="hidden"
|
|
113
|
+
/>
|
|
114
|
+
|
|
115
|
+
You can use these controls to manipulate the `<Avatar>` component above.
|
|
116
|
+
|
|
117
|
+
<Controls of={AvatarStories.Default} />
|
|
@@ -1,10 +1,81 @@
|
|
|
1
|
+
import type { CSSProperties } from 'react';
|
|
1
2
|
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
-
import Avatar from './Avatar';
|
|
3
|
+
import Avatar, { type AvatarProps } from './Avatar';
|
|
3
4
|
import sampleAvatarPhoto from '../../../public/sample-avatar-photo.jpg';
|
|
4
5
|
|
|
6
|
+
type AvatarSize = NonNullable<AvatarProps['size']>;
|
|
7
|
+
const avatarSizes: AvatarSize[] = [48, 56, 72, 80];
|
|
8
|
+
|
|
9
|
+
const avatarRowStyle: CSSProperties = {
|
|
10
|
+
display: 'flex',
|
|
11
|
+
gap: 16,
|
|
12
|
+
alignItems: 'center',
|
|
13
|
+
flexWrap: 'wrap',
|
|
14
|
+
};
|
|
15
|
+
|
|
5
16
|
const meta = {
|
|
6
|
-
title: 'Components/
|
|
17
|
+
title: 'Components/Avatar',
|
|
7
18
|
component: Avatar,
|
|
19
|
+
parameters: {
|
|
20
|
+
layout: 'centered',
|
|
21
|
+
},
|
|
22
|
+
argTypes: {
|
|
23
|
+
variant: {
|
|
24
|
+
description: 'Controls which representation to render',
|
|
25
|
+
options: ['image', 'initials', 'icon'],
|
|
26
|
+
control: { type: 'select' },
|
|
27
|
+
table: { type: { summary: "'image' | 'initials' | 'icon'" } },
|
|
28
|
+
},
|
|
29
|
+
imageUrl: {
|
|
30
|
+
description: 'Image source used when `variant` is `image`',
|
|
31
|
+
control: { type: 'text' },
|
|
32
|
+
table: { type: { summary: 'string' } },
|
|
33
|
+
},
|
|
34
|
+
name: {
|
|
35
|
+
description:
|
|
36
|
+
'Name used for initials and as the fallback alt text for the image',
|
|
37
|
+
control: { type: 'text' },
|
|
38
|
+
table: { type: { summary: 'string' } },
|
|
39
|
+
},
|
|
40
|
+
size: {
|
|
41
|
+
description: 'Diameter of the avatar in pixels',
|
|
42
|
+
options: avatarSizes,
|
|
43
|
+
control: { type: 'inline-radio' },
|
|
44
|
+
table: { type: { summary: avatarSizes.join(' | ') } },
|
|
45
|
+
},
|
|
46
|
+
disabled: {
|
|
47
|
+
description: 'Disable interaction and mute colors',
|
|
48
|
+
control: { type: 'boolean' },
|
|
49
|
+
table: { type: { summary: 'boolean' } },
|
|
50
|
+
},
|
|
51
|
+
testId: {
|
|
52
|
+
description: 'Applied to the button for testing',
|
|
53
|
+
control: { type: 'text' },
|
|
54
|
+
table: { type: { summary: 'string' } },
|
|
55
|
+
},
|
|
56
|
+
className: {
|
|
57
|
+
description: 'Custom className for the root element',
|
|
58
|
+
control: { type: 'text' },
|
|
59
|
+
table: { type: { summary: 'string' } },
|
|
60
|
+
},
|
|
61
|
+
onClick: {
|
|
62
|
+
description: 'Click handler forwarded to the underlying button',
|
|
63
|
+
action: 'clicked',
|
|
64
|
+
table: {
|
|
65
|
+
type: {
|
|
66
|
+
summary: '(event: React.MouseEvent<HTMLButtonElement>) => void',
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
args: {
|
|
72
|
+
variant: 'image',
|
|
73
|
+
imageUrl: sampleAvatarPhoto,
|
|
74
|
+
name: 'Jane Doe',
|
|
75
|
+
size: 56,
|
|
76
|
+
disabled: false,
|
|
77
|
+
},
|
|
78
|
+
tags: ['autodocs'],
|
|
8
79
|
} satisfies Meta<typeof Avatar>;
|
|
9
80
|
|
|
10
81
|
export default meta;
|
|
@@ -29,5 +100,42 @@ export const Initials: Story = {
|
|
|
29
100
|
export const Icon: Story = {
|
|
30
101
|
args: {
|
|
31
102
|
variant: 'icon',
|
|
103
|
+
name: 'Icon avatar',
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
export const Sizes: Story = {
|
|
108
|
+
name: 'Sizes',
|
|
109
|
+
args: {
|
|
110
|
+
imageUrl: sampleAvatarPhoto,
|
|
111
|
+
name: 'Avatar size',
|
|
112
|
+
},
|
|
113
|
+
render: (args) => (
|
|
114
|
+
<div style={avatarRowStyle}>
|
|
115
|
+
{avatarSizes.map((size) => (
|
|
116
|
+
<Avatar
|
|
117
|
+
key={size}
|
|
118
|
+
{...args}
|
|
119
|
+
size={size}
|
|
120
|
+
aria-label={`Avatar ${size}`}
|
|
121
|
+
/>
|
|
122
|
+
))}
|
|
123
|
+
</div>
|
|
124
|
+
),
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
export const Disabled: Story = {
|
|
128
|
+
args: {
|
|
129
|
+
disabled: true,
|
|
130
|
+
name: 'Disabled avatar',
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
export const BrokenImageFallback: Story = {
|
|
135
|
+
name: 'Broken image fallback',
|
|
136
|
+
args: {
|
|
137
|
+
variant: 'image',
|
|
138
|
+
imageUrl: 'https://example.com/does-not-exist.jpg',
|
|
139
|
+
name: 'Fallback to initials',
|
|
32
140
|
},
|
|
33
141
|
};
|
|
@@ -36,6 +36,7 @@ export interface ButtonBaseProps {
|
|
|
36
36
|
export type ButtonProps<C extends ElementType = 'button'> = {
|
|
37
37
|
as?: C;
|
|
38
38
|
ref?: PolymorphicRef<C>;
|
|
39
|
+
className?: string;
|
|
39
40
|
} & ButtonBaseProps &
|
|
40
41
|
MarginProps &
|
|
41
42
|
Omit<ComponentPropsWithRef<C>, keyof ButtonBaseProps | 'as'>;
|