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.
Files changed (158) hide show
  1. package/README.md +4 -2
  2. package/dist/components/Accordion/Accordion.Heading.d.ts +4 -4
  3. package/dist/components/Accordion/Accordion.Panel.d.ts +2 -2
  4. package/dist/components/Accordion/Accordion.d.ts +1 -1
  5. package/dist/components/Accordion/Accordion.stories.d.ts +57 -0
  6. package/dist/components/Accordion/index.d.ts +2 -0
  7. package/dist/components/Avatar/Avatar.stories.d.ts +107 -1
  8. package/dist/components/Button/Button.d.ts +1 -0
  9. package/dist/components/Calendar/index.d.ts +1 -1
  10. package/dist/components/Datepicker/Datepicker.d.ts +1 -1
  11. package/dist/components/Datepicker/Datepicker.stories.d.ts +4 -3
  12. package/dist/components/Datepicker/Datepicker.types.d.ts +4 -5
  13. package/dist/components/Datepicker/subcomponents/CustomDatepicker.d.ts +4 -1
  14. package/dist/components/Datepicker/subcomponents/DatepickerInput.d.ts +15 -2
  15. package/dist/components/Datepicker/subcomponents/Panel.d.ts +1 -1
  16. package/dist/components/Datepicker/subcomponents/VisibleField.d.ts +6 -1
  17. package/dist/components/Datepicker/subcomponents/index.d.ts +0 -1
  18. package/dist/components/Datepicker/utils/index.d.ts +0 -1
  19. package/dist/components/Dialog/BaseDialog.d.ts +2 -1
  20. package/dist/components/Dialog/Dialog.d.ts +2 -0
  21. package/dist/components/Header/Header.d.ts +4 -1
  22. package/dist/components/Header/Header.stories.d.ts +40 -0
  23. package/dist/components/Main/Main.d.ts +21 -0
  24. package/dist/components/Main/Main.stories.d.ts +15 -0
  25. package/dist/components/Main/index.d.ts +2 -0
  26. package/dist/components/NativeDatepicker/NativeDatepicker.d.ts +3 -0
  27. package/dist/components/NativeDatepicker/NativeDatepicker.stories.d.ts +36 -0
  28. package/dist/components/NativeDatepicker/NativeDatepicker.types.d.ts +10 -0
  29. package/dist/components/NativeDatepicker/index.d.ts +2 -0
  30. package/dist/components/{Datepicker → NativeDatepicker}/utils/dateToLocaleISOString/dateToLocaleISOString.d.ts +1 -1
  31. package/dist/components/NativeDatepicker/utils/dateToLocaleISOString/dateToLocaleISOString.test.d.ts +1 -0
  32. package/dist/components/NativeDatepicker/utils/index.d.ts +1 -0
  33. package/dist/components/Select/Select.stories.d.ts +154 -2
  34. package/dist/components/Select/Select.types.d.ts +51 -22
  35. package/dist/components/Select/subcomponents/CustomOption.d.ts +1 -1
  36. package/dist/components/Select/subcomponents/CustomSelect.d.ts +3 -2
  37. package/dist/components/Select/subcomponents/FilterInput.d.ts +14 -0
  38. package/dist/components/Select/subcomponents/NativeSelect.d.ts +5 -1
  39. package/dist/components/Select/subcomponents/VisibleField.d.ts +3 -1
  40. package/dist/components/Select/subcomponents/index.d.ts +1 -0
  41. package/dist/components/WeekPicker/WeekPicker.d.ts +2 -2
  42. package/dist/components/WeekPicker/WeekPicker.stories.d.ts +41 -0
  43. package/dist/components/WeekPicker/WeekPicker.types.d.ts +16 -0
  44. package/dist/components/WeekPicker/index.d.ts +1 -0
  45. package/dist/components/WeekPicker/subcomponents/CustomDatepicker.d.ts +1 -1
  46. package/dist/components/index.d.ts +8 -0
  47. package/dist/hooks/useFocusTrap.d.ts +2 -1
  48. package/dist/index.d.ts +1 -0
  49. package/dist/index.js +4366 -3768
  50. package/dist/utils/__tests__/announce.test.d.ts +1 -0
  51. package/dist/utils/announce.d.ts +6 -0
  52. package/dist/utils/index.d.ts +1 -0
  53. package/lib/components/Accordion/Accordion.Heading.tsx +27 -8
  54. package/lib/components/Accordion/Accordion.Panel.tsx +11 -3
  55. package/lib/components/Accordion/Accordion.stories.tsx +139 -0
  56. package/lib/components/Accordion/Accordion.tsx +10 -8
  57. package/lib/components/Accordion/__tests__/__snapshots__/Accordion.test.tsx.snap +7 -7
  58. package/lib/components/Accordion/index.ts +2 -0
  59. package/lib/components/Alert/Alert.stories.tsx +1 -1
  60. package/lib/components/Avatar/Avatar.mdx +117 -0
  61. package/lib/components/Avatar/Avatar.stories.tsx +110 -2
  62. package/lib/components/Blanket/Blanket.stories.tsx +1 -1
  63. package/lib/components/Button/Button.stories.tsx +1 -1
  64. package/lib/components/Button/Button.tsx +1 -0
  65. package/lib/components/Calendar/Calendar.stories.tsx +12 -32
  66. package/lib/components/Calendar/__tests__/Calendar.test.tsx +23 -15
  67. package/lib/components/Calendar/index.ts +1 -5
  68. package/lib/components/Calendar/subcomponents/AcademicWeeks.tsx +2 -1
  69. package/lib/components/Calendar/subcomponents/ColumnHeading.tsx +5 -1
  70. package/lib/components/Calendar/subcomponents/EventDot.tsx +2 -1
  71. package/lib/components/Calendar/subcomponents/index.ts +1 -1
  72. package/lib/components/Calendar/utils/getDatesForCalendarGrid/getDatesForCalendarGrid.ts +43 -11
  73. package/lib/components/Calendar/utils/normaliseMonth/normaliseMonth.test.ts +5 -5
  74. package/lib/components/Datepicker/Datepicker.lld.md +108 -0
  75. package/lib/components/Datepicker/Datepicker.stories.tsx +44 -5
  76. package/lib/components/Datepicker/Datepicker.tsx +14 -36
  77. package/lib/components/Datepicker/Datepicker.types.ts +5 -14
  78. package/lib/components/Datepicker/__tests__/Datepicker.test.tsx +150 -8
  79. package/lib/components/Datepicker/__tests__/__snapshots__/Datepicker.test.tsx.snap +10 -4
  80. package/lib/components/Datepicker/subcomponents/CustomDatepicker.tsx +39 -5
  81. package/lib/components/Datepicker/subcomponents/DatepickerInput.tsx +30 -17
  82. package/lib/components/Datepicker/subcomponents/Panel.tsx +6 -2
  83. package/lib/components/Datepicker/subcomponents/VisibleField.tsx +40 -3
  84. package/lib/components/Datepicker/subcomponents/index.ts +0 -1
  85. package/lib/components/Datepicker/utils/index.ts +0 -1
  86. package/lib/components/Dialog/BaseDialog.tsx +11 -0
  87. package/lib/components/Dialog/Dialog.tsx +8 -1
  88. package/lib/components/Dialog/DialogBody.tsx +5 -1
  89. package/lib/components/Dialog/DialogHeader.tsx +2 -1
  90. package/lib/components/Divider/Divider.stories.tsx +1 -1
  91. package/lib/components/Field/ErrorText.tsx +1 -0
  92. package/lib/components/Field/Field.stories.tsx +1 -1
  93. package/lib/components/Field/__tests__/Field.test.tsx +13 -0
  94. package/lib/components/FileInput/FileInput.stories.tsx +1 -1
  95. package/lib/components/Footer/Footer.stories.tsx +1 -1
  96. package/lib/components/Footer/__tests__/__snapshots__/Footer.test.tsx.snap +3 -3
  97. package/lib/components/Header/Header.mdx +52 -0
  98. package/lib/components/Header/Header.stories.tsx +98 -0
  99. package/lib/components/Header/Header.tsx +51 -6
  100. package/lib/components/Header/__tests__/Header.test.tsx +17 -1
  101. package/lib/components/Heading/Heading.stories.tsx +1 -1
  102. package/lib/components/Icon/Icon.stories.tsx +1 -1
  103. package/lib/components/IconButton/IconButton.stories.tsx +1 -1
  104. package/lib/components/Input/Input.stories.tsx +1 -1
  105. package/lib/components/Label/Label.stories.tsx +1 -1
  106. package/lib/components/Main/Main.stories.tsx +36 -0
  107. package/lib/components/Main/Main.tsx +46 -0
  108. package/lib/components/Main/__tests__/Main.test.tsx +80 -0
  109. package/lib/components/Main/__tests__/__snapshots__/Main.test.tsx.snap +33 -0
  110. package/lib/components/Main/index.ts +2 -0
  111. package/lib/components/NativeDatepicker/NativeDatepicker.stories.tsx +100 -0
  112. package/lib/components/{Datepicker/subcomponents → NativeDatepicker}/NativeDatepicker.tsx +14 -15
  113. package/lib/components/NativeDatepicker/NativeDatepicker.types.ts +19 -0
  114. package/lib/components/NativeDatepicker/index.ts +2 -0
  115. package/lib/components/{Datepicker → NativeDatepicker}/utils/dateToLocaleISOString/dateToLocaleISOString.ts +1 -1
  116. package/lib/components/NativeDatepicker/utils/index.ts +1 -0
  117. package/lib/components/Pagination/PaginationControls.tsx +55 -12
  118. package/lib/components/Pagination/PaginationInfo.tsx +5 -1
  119. package/lib/components/Paragraph/Paragraph.stories.tsx +1 -1
  120. package/lib/components/Search/Search.stories.tsx +1 -1
  121. package/lib/components/Search/Search.tsx +4 -1
  122. package/lib/components/Search/__tests__/Search.test.tsx +19 -1
  123. package/lib/components/Select/Select.mdx +169 -0
  124. package/lib/components/Select/Select.stories.tsx +191 -43
  125. package/lib/components/Select/Select.tsx +36 -12
  126. package/lib/components/Select/Select.types.ts +66 -48
  127. package/lib/components/Select/__tests__/Select.test.tsx +448 -7
  128. package/lib/components/Select/__tests__/__snapshots__/Select.test.tsx.snap +1 -1
  129. package/lib/components/Select/subcomponents/CustomOption.tsx +2 -1
  130. package/lib/components/Select/subcomponents/CustomSelect.tsx +303 -33
  131. package/lib/components/Select/subcomponents/FilterInput.tsx +80 -0
  132. package/lib/components/Select/subcomponents/NativeSelect.tsx +13 -1
  133. package/lib/components/Select/subcomponents/VisibleField.tsx +11 -3
  134. package/lib/components/Select/subcomponents/index.tsx +1 -0
  135. package/lib/components/Snackbar/Snackbar.stories.tsx +1 -1
  136. package/lib/components/Spinner/Spinner.stories.tsx +1 -1
  137. package/lib/components/Textarea/Textarea.stories.tsx +1 -1
  138. package/lib/components/Timepicker/Timepicker.tsx +4 -0
  139. package/lib/components/Timepicker/__tests__/__snapshots__/Timepicker.test.tsx.snap +2 -2
  140. package/lib/components/Toggle/Toggle.stories.tsx +1 -1
  141. package/lib/components/Tooltip/Tooltip.stories.tsx +1 -1
  142. package/lib/components/WeekPicker/WeekPicker.stories.tsx +147 -0
  143. package/lib/components/WeekPicker/WeekPicker.tsx +2 -2
  144. package/lib/components/WeekPicker/WeekPicker.types.ts +21 -0
  145. package/lib/components/WeekPicker/index.ts +1 -0
  146. package/lib/components/WeekPicker/subcomponents/CustomDatepicker.tsx +1 -1
  147. package/lib/components/common/Common.mdx +1 -1
  148. package/lib/components/index.ts +11 -2
  149. package/lib/hooks/useFocusTrap.ts +40 -4
  150. package/lib/index.ts +1 -0
  151. package/lib/utils/__tests__/announce.test.ts +121 -0
  152. package/lib/utils/announce.ts +134 -0
  153. package/lib/utils/index.ts +1 -0
  154. package/package.json +3 -6
  155. package/dist/components/Datepicker/subcomponents/NativeDatepicker.d.ts +0 -6
  156. package/lib/components/Accordion/Accordion.stories.tsx.NOT_READY +0 -93
  157. /package/dist/components/{Datepicker/utils/dateToLocaleISOString/dateToLocaleISOString.test.d.ts → Main/__tests__/Main.test.d.ts} +0 -0
  158. /package/lib/components/{Datepicker → NativeDatepicker}/utils/dateToLocaleISOString/dateToLocaleISOString.test.ts +0 -0
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Announces a message to screen readers.
3
+ */
4
+ declare const announce: (message: string, force?: boolean) => void;
5
+ export declare const __resetForTesting: () => void;
6
+ export default announce;
@@ -0,0 +1 @@
1
+ export { default as announce } from './announce';
@@ -1,14 +1,13 @@
1
- import React, { HTMLAttributes, JSX } from 'react';
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
- extends HTMLAttributes<HTMLElement> {
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
- useAccordionContext();
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: ${theme.color.interaction.blue70};
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
- AccordionContextProps | undefined
23
- >(undefined);
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
- max-width: 600px;
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-1czwoas"
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-11x7r0n"
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-1czwoas"
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-11x7r0n"
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
  >
@@ -1,2 +1,4 @@
1
1
  export { default } from './Accordion';
2
2
  export type { AccordionProps } from './Accordion';
3
+ export type { AccordionHeadingProps } from './Accordion.Heading';
4
+ export type { AccordionPanelProps } from './Accordion.Panel';
@@ -21,7 +21,7 @@ const commonMarginPropsInfo = {
21
21
  };
22
22
 
23
23
  const meta = {
24
- title: 'Components/Ready to use/Alert',
24
+ title: 'Components/Alert',
25
25
  component: Alert,
26
26
  parameters: {
27
27
  layout: 'centered',
@@ -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/Ready to use/Avatar',
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
  };
@@ -3,7 +3,7 @@ import type { Meta, StoryObj } from '@storybook/react';
3
3
  import Blanket from './Blanket';
4
4
 
5
5
  const meta = {
6
- title: 'Components/Ready to use/Blanket',
6
+ title: 'Components/Blanket',
7
7
  component: Blanket,
8
8
  parameters: {
9
9
  layout: 'fullscreen',
@@ -24,7 +24,7 @@ const commonMarginPropsInfo = {
24
24
  };
25
25
 
26
26
  const meta = {
27
- title: 'Components/Ready to use/Button',
27
+ title: 'Components/Button',
28
28
  component: Button,
29
29
  parameters: {
30
30
  layout: 'centered',
@@ -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'>;