@nypl/design-system-react-components 0.25.3 → 0.25.7

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 (135) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/README.md +47 -11
  3. package/dist/components/Breadcrumbs/Breadcrumbs.d.ts +4 -0
  4. package/dist/components/Breadcrumbs/BreadcrumbsTypes.d.ts +1 -0
  5. package/dist/components/Button/Button.d.ts +9 -6
  6. package/dist/components/Button/ButtonTypes.d.ts +2 -1
  7. package/dist/components/Card/Card.d.ts +0 -25
  8. package/dist/components/Icons/IconSvgs.d.ts +3 -0
  9. package/dist/components/Icons/IconTypes.d.ts +3 -0
  10. package/dist/components/Notification/Notification.d.ts +6 -4
  11. package/dist/components/ProgressIndicator/ProgressIndicator.d.ts +29 -0
  12. package/dist/components/ProgressIndicator/ProgressIndicatorTypes.d.ts +8 -0
  13. package/dist/components/SearchBar/SearchBar.d.ts +3 -3
  14. package/dist/components/Slider/Slider.d.ts +57 -0
  15. package/dist/components/Tabs/Tabs.d.ts +1 -1
  16. package/dist/components/TextInput/TextInput.d.ts +6 -0
  17. package/dist/design-system-react-components.cjs.development.js +1022 -455
  18. package/dist/design-system-react-components.cjs.development.js.map +1 -1
  19. package/dist/design-system-react-components.cjs.production.min.js +1 -1
  20. package/dist/design-system-react-components.cjs.production.min.js.map +1 -1
  21. package/dist/design-system-react-components.esm.js +1015 -444
  22. package/dist/design-system-react-components.esm.js.map +1 -1
  23. package/dist/index.d.ts +8 -3
  24. package/dist/resources.scss +180 -170
  25. package/dist/styles.css +1 -1
  26. package/dist/theme/components/breadcrumb.d.ts +9 -0
  27. package/dist/theme/components/button.d.ts +10 -0
  28. package/dist/theme/components/progressIndicator.d.ts +50 -0
  29. package/dist/theme/components/slider.d.ts +51 -0
  30. package/dist/theme/foundations/breakpoints.d.ts +8 -8
  31. package/dist/theme/foundations/global.d.ts +6 -1
  32. package/package.json +71 -83
  33. package/src/__tests__/setup.ts +2 -2
  34. package/src/components/Accordion/Accordion.stories.mdx +30 -34
  35. package/src/components/Autosuggest/Autosuggest.stories.mdx +3 -3
  36. package/src/components/Autosuggest/Autosuggest.stories.tsx +1 -0
  37. package/src/components/Autosuggest/_Autosuggest.scss +5 -5
  38. package/src/components/Breadcrumbs/Breadcrumbs.stories.mdx +24 -8
  39. package/src/components/Breadcrumbs/Breadcrumbs.test.tsx +33 -0
  40. package/src/components/Breadcrumbs/Breadcrumbs.tsx +5 -1
  41. package/src/components/Breadcrumbs/BreadcrumbsTypes.tsx +1 -0
  42. package/src/components/Breadcrumbs/__snapshots__/Breadcrumbs.test.tsx.snap +297 -1
  43. package/src/components/Button/Button.stories.mdx +8 -6
  44. package/src/components/Button/Button.test.tsx +20 -0
  45. package/src/components/Button/Button.tsx +14 -23
  46. package/src/components/Button/ButtonTypes.tsx +1 -0
  47. package/src/components/Button/__snapshots__/Button.test.tsx.snap +29 -4
  48. package/src/components/Card/Card.stories.mdx +1 -1
  49. package/src/components/Card/Card.tsx +3 -3
  50. package/src/components/CardEdition/CardEdition.stories.tsx +15 -47
  51. package/src/components/CardEdition/_CardEdition.scss +22 -23
  52. package/src/components/Chakra/Box.stories.mdx +14 -15
  53. package/src/components/Chakra/Center.stories.mdx +15 -8
  54. package/src/components/Chakra/Grid.stories.mdx +16 -7
  55. package/src/components/Chakra/Stack.stories.mdx +35 -18
  56. package/src/components/DatePicker/DatePicker.test.tsx +31 -30
  57. package/src/components/DatePicker/DatePicker.tsx +7 -4
  58. package/src/components/DatePicker/_DatePicker.scss +4 -4
  59. package/src/components/DatePicker/__snapshots__/DatePicker.test.tsx.snap +19 -5
  60. package/src/components/Form/Form.stories.mdx +4 -5
  61. package/src/components/Grid/SimpleGrid.stories.mdx +1 -0
  62. package/src/components/Hero/HeroTypes.tsx +1 -0
  63. package/src/components/HorizontalRule/HorizontalRule.stories.mdx +2 -3
  64. package/src/components/Icons/Icon.stories.mdx +2 -3
  65. package/src/components/Icons/IconSvgs.tsx +6 -0
  66. package/src/components/Icons/IconTypes.tsx +3 -0
  67. package/src/components/Image/Image.stories.mdx +2 -3
  68. package/src/components/Input/Input.stories.tsx +20 -64
  69. package/src/components/Input/_Input.scss +23 -14
  70. package/src/components/Modal/Modal.stories.mdx +3 -3
  71. package/src/components/Modal/_Modal.scss +2 -2
  72. package/src/components/Notification/Notification.stories.mdx +1 -1
  73. package/src/components/Notification/Notification.tsx +13 -4
  74. package/src/components/Pagination/Pagination.stories.mdx +3 -2
  75. package/src/components/Pagination/Pagination.stories.tsx +1 -2
  76. package/src/components/ProgressIndicator/ProgressIndicator.stories.mdx +292 -0
  77. package/src/components/ProgressIndicator/ProgressIndicator.test.tsx +225 -0
  78. package/src/components/ProgressIndicator/ProgressIndicator.tsx +126 -0
  79. package/src/components/ProgressIndicator/ProgressIndicatorTypes.ts +8 -0
  80. package/src/components/ProgressIndicator/__snapshots__/ProgressIndicator.test.tsx.snap +357 -0
  81. package/src/components/Radio/__snapshots__/Radio.test.tsx.snap +3 -0
  82. package/src/components/RadioGroup/RadioGroup.stories.mdx +2 -3
  83. package/src/components/RadioGroup/__snapshots__/RadioGroup.test.tsx.snap +4 -0
  84. package/src/components/SearchBar/SearchBar.Test.tsx +66 -21
  85. package/src/components/SearchBar/SearchBar.stories.mdx +110 -11
  86. package/src/components/SearchBar/SearchBar.tsx +15 -5
  87. package/src/components/Select/SelectTypes.tsx +1 -0
  88. package/src/components/Slider/Slider.stories.mdx +529 -0
  89. package/src/components/Slider/Slider.test.tsx +653 -0
  90. package/src/components/Slider/Slider.tsx +303 -0
  91. package/src/components/Slider/__snapshots__/Slider.test.tsx.snap +1946 -0
  92. package/src/components/StyleGuide/Bidirectionality.stories.mdx +1 -1
  93. package/src/components/StyleGuide/Breakpoints.stories.mdx +21 -7
  94. package/src/components/StyleGuide/Buttons.stories.mdx +9 -2
  95. package/src/components/StyleGuide/DesignTokens.stories.mdx +170 -0
  96. package/src/components/Tabs/Tabs.tsx +5 -5
  97. package/src/components/Template/Template.stories.mdx +6 -6
  98. package/src/components/TextInput/TextInput.stories.mdx +1 -1
  99. package/src/components/TextInput/TextInput.tsx +15 -2
  100. package/src/components/TextInput/TextInputTypes.tsx +2 -0
  101. package/src/components/VideoPlayer/VideoPlayer.stories.mdx +2 -3
  102. package/src/docs/Chakra.stories.mdx +2 -2
  103. package/src/index.ts +12 -6
  104. package/src/resources.scss +5 -5
  105. package/src/styles/base/{_02-breakpoints.scss → _01-breakpoints.scss} +9 -10
  106. package/src/styles/base/{_03-mixins.scss → _02-mixins.scss} +16 -5
  107. package/src/styles/base/{_04-base.scss → _03-base.scss} +7 -2
  108. package/src/styles/base/{_05-focus.scss → _04-focus.scss} +0 -15
  109. package/src/styles/base/_place-holder.scss +14 -3
  110. package/src/styles/{03-space → space}/_space-inline.scss +14 -14
  111. package/src/styles/{03-space → space}/_space-inset.scss +10 -10
  112. package/src/styles/space/_space-stack.scss +116 -0
  113. package/src/styles.scss +13 -44
  114. package/src/theme/components/breadcrumb.ts +10 -0
  115. package/src/theme/components/button.ts +10 -2
  116. package/src/theme/components/card.ts +11 -9
  117. package/src/theme/components/datePicker.ts +1 -1
  118. package/src/theme/components/list.ts +2 -2
  119. package/src/theme/components/notification.ts +5 -3
  120. package/src/theme/components/progressIndicator.ts +62 -0
  121. package/src/theme/components/skeletonLoader.ts +1 -1
  122. package/src/theme/components/slider.ts +79 -0
  123. package/src/theme/foundations/breakpoints.ts +8 -8
  124. package/src/theme/foundations/global.ts +6 -1
  125. package/src/theme/index.ts +4 -0
  126. package/src/utils/componentCategories.ts +3 -3
  127. package/src/styles/01-colors/_colors-brand.scss +0 -62
  128. package/src/styles/01-colors/_colors-utility.scss +0 -67
  129. package/src/styles/02-typography/_type-scale.css +0 -11
  130. package/src/styles/02-typography/_type-weight.css +0 -7
  131. package/src/styles/02-typography/_typefaces.css +0 -4
  132. package/src/styles/03-space/_space-stack.scss +0 -116
  133. package/src/styles/03-space/_space.css +0 -30
  134. package/src/styles/base/_card-grid.scss +0 -77
  135. package/src/styles/base/_typography.scss +0 -11
@@ -35,7 +35,7 @@ import { getCategory } from "../../utils/componentCategories";
35
35
  | Component Version | DS Version |
36
36
  | ----------------- | ---------- |
37
37
  | Added | `0.0.10` |
38
- | Latest | `0.25.2` |
38
+ | Latest | `0.25.3` |
39
39
 
40
40
  <Description of={Pagination} />
41
41
 
@@ -60,7 +60,8 @@ named anything but you have to pick up the value in the wrapper component.
60
60
  ```tsx
61
61
  // Example in a search results page.
62
62
  const getPageHref = (selectedPage: number) => {
63
- // This should be updated for your router system, if any.
63
+ // This should be updated for your router system, if any, including the
64
+ // base or origin URL.
64
65
  return `${location.origin}?q=celeste&page=${selectedPage}`;
65
66
  };
66
67
  ```
@@ -23,8 +23,7 @@ export const PaginationGetPageHref: Story<PaginationProps> = (args) => {
23
23
  // Passing this function into `Pagination` makes the URL to change
24
24
  // and refreshes the page.
25
25
  const getPageHref = (selectedPage) => {
26
- const currentStoryId = urlParams.get("id");
27
- return `${location.origin}?path=/story/${currentStoryId}&page=${selectedPage}`;
26
+ return `${location.href}&page=${selectedPage}`;
28
27
  };
29
28
 
30
29
  return (
@@ -0,0 +1,292 @@
1
+ import {
2
+ Meta,
3
+ Story,
4
+ ArgsTable,
5
+ Canvas,
6
+ Description,
7
+ } from "@storybook/addon-docs/blocks";
8
+ import { withDesign } from "storybook-addon-designs";
9
+ import { Box } from "@chakra-ui/react";
10
+
11
+ import ProgressIndicator from "./ProgressIndicator";
12
+ import {
13
+ ProgressIndicatorSizes,
14
+ ProgressIndicatorTypes,
15
+ } from "./ProgressIndicatorTypes";
16
+ import { getCategory } from "../../utils/componentCategories";
17
+ import DSProvider from "../../theme/provider";
18
+ import SimpleGrid from "../Grid/SimpleGrid";
19
+ import { GridGaps } from "../Grid/GridTypes";
20
+
21
+ <Meta
22
+ title={getCategory("ProgressIndicator")}
23
+ component={ProgressIndicator}
24
+ decorators={[withDesign]}
25
+ parameters={{
26
+ design: {
27
+ type: "figma",
28
+ url:
29
+ "https://www.figma.com/file/qShodlfNCJHb8n03IFyApM/Main?node-id=37638%3A23842",
30
+ },
31
+ jest: ["ProgressIndicator.test.tsx"],
32
+ }}
33
+ argTypes={{
34
+ id: { table: { disable: true } },
35
+ }}
36
+ />
37
+
38
+ # ProgressIndicator
39
+
40
+ | Component Version | DS Version |
41
+ | ----------------- | ---------- |
42
+ | Added | `0.25.4` |
43
+ | Latest | `0.25.4` |
44
+
45
+ <Description of={ProgressIndicator} />
46
+
47
+ <Canvas withToolbar>
48
+ <Story
49
+ name="Basic with props"
50
+ args={{
51
+ darkMode: false,
52
+ indicatorType: ProgressIndicatorTypes.Linear,
53
+ isIndeterminate: false,
54
+ labelText: "Progress",
55
+ showLabel: true,
56
+ size: ProgressIndicatorSizes.Default,
57
+ value: 50,
58
+ }}
59
+ >
60
+ {(args) => <ProgressIndicator {...args} />}
61
+ </Story>
62
+ </Canvas>
63
+
64
+ <ArgsTable story="Basic with props" />
65
+
66
+ ## Linear Type
67
+
68
+ Progress bars are preferred in vertically narrow areas such as tables, cards,
69
+ dialogs, etc.
70
+
71
+ <Canvas>
72
+ <Story
73
+ name="Linear Type"
74
+ args={{
75
+ darkMode: false,
76
+ indicatorType: ProgressIndicatorTypes.Linear,
77
+ isIndeterminate: false,
78
+ labelText: "Linear Progress Type",
79
+ showLabel: true,
80
+ size: ProgressIndicatorSizes.Default,
81
+ value: 50,
82
+ }}
83
+ argTypes={{
84
+ indicatorType: { table: { disable: true } },
85
+ }}
86
+ >
87
+ {(args) => <ProgressIndicator {...args} />}
88
+ </Story>
89
+ </Canvas>
90
+
91
+ ## Circular Type
92
+
93
+ The circular progress type is preferred for large content areas and for
94
+ full-screen loading. Set the `indicatorType` prop to
95
+ `ProgressIndicatorTypes.Circular` for this type.
96
+
97
+ <Canvas>
98
+ <Story
99
+ name="Circular Type"
100
+ args={{
101
+ darkMode: false,
102
+ indicatorType: ProgressIndicatorTypes.Circular,
103
+ isIndeterminate: false,
104
+ labelText: "Circular Progress Type",
105
+ showLabel: true,
106
+ size: ProgressIndicatorSizes.Default,
107
+ value: 50,
108
+ }}
109
+ argTypes={{
110
+ indicatorType: { table: { disable: true } },
111
+ }}
112
+ >
113
+ {(args) => <ProgressIndicator {...args} />}
114
+ </Story>
115
+ </Canvas>
116
+
117
+ ### Sizing
118
+
119
+ The starting height for the progress bar is 4px on mobile and 8px for desktop.
120
+ The `size` prop can be used to optionally set the height to 4px for desktop
121
+ through the `ProgressIndicatorSizes.Small` value.
122
+
123
+ `size={ProgressIndicatorSizes.Small}`
124
+
125
+ <Canvas>
126
+ <DSProvider>
127
+ <SimpleGrid columns={1} gap={GridGaps.Medium}>
128
+ <ProgressIndicator labelText="Default 8px size" value={50} />
129
+ <ProgressIndicator
130
+ labelText="Small 4px size"
131
+ size={ProgressIndicatorSizes.Small}
132
+ value={50}
133
+ />
134
+ </SimpleGrid>
135
+ </DSProvider>
136
+ </Canvas>
137
+
138
+ The starting size for the circular progress is 48px and can be made smaller to
139
+ 24px with the `size` prop. The small 24px size can be used for inline local
140
+ changes in content. Note that in the small size, the label text and the
141
+ percentage will not displayed.
142
+
143
+ `size={ProgressIndicatorSizes.Small}`
144
+
145
+ <Canvas>
146
+ <DSProvider>
147
+ <SimpleGrid columns={1} gap={GridGaps.Medium}>
148
+ <ProgressIndicator
149
+ labelText="Default 48px size"
150
+ indicatorType={ProgressIndicatorTypes.Circular}
151
+ value={50}
152
+ />
153
+ <ProgressIndicator
154
+ labelText="Small 24px size"
155
+ indicatorType={ProgressIndicatorTypes.Circular}
156
+ size={ProgressIndicatorSizes.Small}
157
+ value={50}
158
+ />
159
+ </SimpleGrid>
160
+ </DSProvider>
161
+ </Canvas>
162
+
163
+ ### Labels
164
+
165
+ The `labelText` value and the `value` percentage are displayed by default. They
166
+ can be hidden through the `showLabel` prop.
167
+
168
+ `showLabel={false}`
169
+
170
+ Accessibility Note: when `showLabel` is false, the `aria-label` prop is set in
171
+ the progress element to provide a description of the progress for screen readers.
172
+
173
+ <Canvas>
174
+ <DSProvider>
175
+ <SimpleGrid columns={1} gap={GridGaps.Medium}>
176
+ <ProgressIndicator
177
+ labelText="This label will be added through aria-label"
178
+ value={50}
179
+ showLabel={false}
180
+ />
181
+ <ProgressIndicator
182
+ labelText="This label will be added through aria-label"
183
+ indicatorType={ProgressIndicatorTypes.Circular}
184
+ value={50}
185
+ showLabel={false}
186
+ />
187
+ </SimpleGrid>
188
+ </DSProvider>
189
+ </Canvas>
190
+
191
+ ### Indeterminate State
192
+
193
+ When the `isIndeterminate` prop is set to true, the `value` prop is ignored and
194
+ the state is set to an animated indeterminate state. This is often used when the
195
+ exact value or progress of the task is unknown.
196
+
197
+ <Canvas>
198
+ <DSProvider>
199
+ <SimpleGrid columns={1} gap={GridGaps.Medium}>
200
+ <ProgressIndicator
201
+ labelText="Indeterminate state"
202
+ isIndeterminate
203
+ value={50}
204
+ />
205
+ <ProgressIndicator
206
+ labelText="Indeterminate state"
207
+ indicatorType={ProgressIndicatorTypes.Circular}
208
+ isIndeterminate
209
+ value={50}
210
+ />
211
+ </SimpleGrid>
212
+ </DSProvider>
213
+ </Canvas>
214
+
215
+ ### Dark Mode
216
+
217
+ When in "dark mode", set the `darkMode` prop to true. This will render the
218
+ progress bar and the label text in white for better contrast.
219
+
220
+ Note: the background is manually set to better showcase the `darkMode` prop.
221
+
222
+ <Canvas>
223
+ <DSProvider>
224
+ <Box bg="black" w="100%" h="200px" p="20px">
225
+ <SimpleGrid columns={1} gap={GridGaps.Medium}>
226
+ <ProgressIndicator labelText="Dark Mode" value={50} darkMode />
227
+ <ProgressIndicator
228
+ labelText="Dark Mode"
229
+ indicatorType={ProgressIndicatorTypes.Circular}
230
+ value={50}
231
+ darkMode
232
+ />
233
+ </SimpleGrid>
234
+ </Box>
235
+ </DSProvider>
236
+ </Canvas>
237
+
238
+ ### Example
239
+
240
+ In the following example, we are setting the `value` prop based on a timer that
241
+ increases the value every second by 10. Once it reaches 100, it resets to 0.
242
+ This is a very simple example using `React.useState` to manage the state but in
243
+ a real application the value would come from a data source or server.
244
+
245
+ ```tsx
246
+ // Example code
247
+ function ProgressIndicatorExample() {
248
+ const [value, setValue] = React.useState(0);
249
+ React.useEffect(() => {
250
+ const interval = setInterval(() => {
251
+ setValue((value) => (value === 100 ? 0 : value + 10));
252
+ }, 1000);
253
+ return () => clearInterval(interval);
254
+ }, []);
255
+ return (
256
+ <SimpleGrid columns={1} gap={GridGaps.Medium}>
257
+ <ProgressIndicator labelText="Progress example" value={value} />
258
+ <ProgressIndicator
259
+ labelText="Progress example"
260
+ indicatorType={ProgressIndicatorTypes.Circular}
261
+ value={value}
262
+ />
263
+ </SimpleGrid>
264
+ );
265
+ }
266
+ ```
267
+
268
+ export function ProgressIndicatorExample() {
269
+ const [value, setValue] = React.useState(0);
270
+ React.useEffect(() => {
271
+ const interval = setInterval(() => {
272
+ setValue((value) => (value === 100 ? 0 : value + 10));
273
+ }, 1000);
274
+ return () => clearInterval(interval);
275
+ }, []);
276
+ return (
277
+ <SimpleGrid columns={1} gap={GridGaps.Medium}>
278
+ <ProgressIndicator labelText="Progress example" value={value} />
279
+ <ProgressIndicator
280
+ labelText="Progress example"
281
+ indicatorType={ProgressIndicatorTypes.Circular}
282
+ value={value}
283
+ />
284
+ </SimpleGrid>
285
+ );
286
+ }
287
+
288
+ <Canvas>
289
+ <DSProvider>
290
+ <ProgressIndicatorExample />
291
+ </DSProvider>
292
+ </Canvas>
@@ -0,0 +1,225 @@
1
+ import * as React from "react";
2
+ import { render, screen } from "@testing-library/react";
3
+ import { axe } from "jest-axe";
4
+ import renderer from "react-test-renderer";
5
+
6
+ import ProgressIndicator from "./ProgressIndicator";
7
+ import { ProgressIndicatorTypes } from "./ProgressIndicatorTypes";
8
+
9
+ describe("ProgressIndicator Accessibility", () => {
10
+ it("passes axe accessibility for linear and circular types", async () => {
11
+ const linearUtils = render(
12
+ <ProgressIndicator labelText="Linear" value={50} />
13
+ );
14
+ const circularUtils = render(
15
+ <ProgressIndicator
16
+ labelText="Circular"
17
+ value={50}
18
+ indicatorType={ProgressIndicatorTypes.Circular}
19
+ />
20
+ );
21
+ expect(await axe(linearUtils.container)).toHaveNoViolations();
22
+ expect(await axe(circularUtils.container)).toHaveNoViolations();
23
+ });
24
+
25
+ it("passes axe accessibility for linear and circular types without labels", async () => {
26
+ const linearUtils = render(
27
+ <ProgressIndicator labelText="Linear" value={50} showLabel={false} />
28
+ );
29
+ const circularUtils = render(
30
+ <ProgressIndicator
31
+ labelText="Circular"
32
+ value={50}
33
+ indicatorType={ProgressIndicatorTypes.Circular}
34
+ showLabel={false}
35
+ />
36
+ );
37
+ expect(await axe(linearUtils.container)).toHaveNoViolations();
38
+ expect(await axe(circularUtils.container)).toHaveNoViolations();
39
+ });
40
+
41
+ it("passes axe accessibility for linear and circular types for indeterminate state", async () => {
42
+ const linearUtils = render(
43
+ <ProgressIndicator labelText="Linear" value={50} isIndeterminate />
44
+ );
45
+ const circularUtils = render(
46
+ <ProgressIndicator
47
+ labelText="Circular"
48
+ value={50}
49
+ indicatorType={ProgressIndicatorTypes.Circular}
50
+ isIndeterminate
51
+ />
52
+ );
53
+ expect(await axe(linearUtils.container)).toHaveNoViolations();
54
+ expect(await axe(circularUtils.container)).toHaveNoViolations();
55
+ });
56
+
57
+ it("passes axe accessibility for linear and circular types for dark mode", async () => {
58
+ const linearUtils = render(
59
+ <ProgressIndicator labelText="Linear" value={50} darkMode />
60
+ );
61
+ const circularUtils = render(
62
+ <ProgressIndicator
63
+ labelText="Circular"
64
+ value={50}
65
+ indicatorType={ProgressIndicatorTypes.Circular}
66
+ darkMode
67
+ />
68
+ );
69
+ expect(await axe(linearUtils.container)).toHaveNoViolations();
70
+ expect(await axe(circularUtils.container)).toHaveNoViolations();
71
+ });
72
+ });
73
+
74
+ describe("ProgressIndicator", () => {
75
+ it("renders a label and a progressbar for the linear type", () => {
76
+ render(<ProgressIndicator labelText="Linear" value={50} />);
77
+ expect(screen.getByLabelText("Linear")).toBeInTheDocument();
78
+ expect(screen.getByRole("progressbar")).toBeInTheDocument();
79
+ });
80
+
81
+ it("renders a label, a progressbar, and an svg for the circular type", () => {
82
+ const { container } = render(
83
+ <ProgressIndicator
84
+ labelText="Circular"
85
+ indicatorType={ProgressIndicatorTypes.Circular}
86
+ value={50}
87
+ />
88
+ );
89
+ expect(screen.getByLabelText("Circular")).toBeInTheDocument();
90
+ expect(screen.getByRole("progressbar")).toBeInTheDocument();
91
+ expect(container.querySelector("svg")).toBeInTheDocument();
92
+ });
93
+
94
+ it("renders the appropriate aria atttribute when the label is hidden", () => {
95
+ render(
96
+ <ProgressIndicator labelText="Linear" value={50} showLabel={false} />
97
+ );
98
+ expect(screen.getByLabelText("Linear")).toBeInTheDocument();
99
+ expect(screen.getByRole("progressbar")).toHaveAttribute(
100
+ "aria-label",
101
+ "Linear"
102
+ );
103
+ });
104
+
105
+ it("renders the value passed", () => {
106
+ const { rerender } = render(
107
+ <ProgressIndicator labelText="Linear" value={50} />
108
+ );
109
+ expect(screen.getByText("50%")).toBeInTheDocument();
110
+
111
+ rerender(<ProgressIndicator labelText="Linear" value={89} />);
112
+ expect(screen.getByText("89%")).toBeInTheDocument();
113
+
114
+ rerender(<ProgressIndicator labelText="Linear" value={4} />);
115
+ expect(screen.getByText("4%")).toBeInTheDocument();
116
+ });
117
+
118
+ it("logs a warning if a value less than 0 is passed", () => {
119
+ const warn = jest.spyOn(console, "warn");
120
+ render(<ProgressIndicator labelText="Linear" value={-20} />);
121
+
122
+ expect(warn).toHaveBeenCalledWith(
123
+ "ProgressIndicator: pass in a `value` between 0 and 100. Defaulting to 0."
124
+ );
125
+ });
126
+
127
+ it("logs a warning if a value more than 100 is passed", () => {
128
+ const warn = jest.spyOn(console, "warn");
129
+ render(<ProgressIndicator labelText="Linear" value={150} />);
130
+
131
+ expect(warn).toHaveBeenCalledWith(
132
+ "ProgressIndicator: pass in a `value` between 0 and 100. Defaulting to 0."
133
+ );
134
+ });
135
+
136
+ it("Renders the UI snapshot correctly", () => {
137
+ const linearBasic = renderer
138
+ .create(
139
+ <ProgressIndicator id="linearBasic" labelText="Linear" value={50} />
140
+ )
141
+ .toJSON();
142
+ const circularBasic = renderer
143
+ .create(
144
+ <ProgressIndicator
145
+ id="circularBasic"
146
+ labelText="Circular"
147
+ value={50}
148
+ indicatorType={ProgressIndicatorTypes.Circular}
149
+ />
150
+ )
151
+ .toJSON();
152
+ const linearNoLabel = renderer
153
+ .create(
154
+ <ProgressIndicator
155
+ id="linearNoLabel"
156
+ labelText="Linear"
157
+ value={50}
158
+ showLabel={false}
159
+ />
160
+ )
161
+ .toJSON();
162
+ const circularNoLabel = renderer
163
+ .create(
164
+ <ProgressIndicator
165
+ id="circularNoLabel"
166
+ labelText="Circular"
167
+ value={50}
168
+ indicatorType={ProgressIndicatorTypes.Circular}
169
+ showLabel={false}
170
+ />
171
+ )
172
+ .toJSON();
173
+ const linearIndeterminate = renderer
174
+ .create(
175
+ <ProgressIndicator
176
+ id="linearIndeterminate"
177
+ labelText="Linear"
178
+ value={50}
179
+ isIndeterminate
180
+ />
181
+ )
182
+ .toJSON();
183
+ const circularIndeterminate = renderer
184
+ .create(
185
+ <ProgressIndicator
186
+ id="circularIndeterminate"
187
+ labelText="Circular"
188
+ value={50}
189
+ indicatorType={ProgressIndicatorTypes.Circular}
190
+ isIndeterminate
191
+ />
192
+ )
193
+ .toJSON();
194
+ const linearDarkMode = renderer
195
+ .create(
196
+ <ProgressIndicator
197
+ id="linearDarkMode"
198
+ labelText="Linear"
199
+ value={50}
200
+ darkMode
201
+ />
202
+ )
203
+ .toJSON();
204
+ const circularDarkMode = renderer
205
+ .create(
206
+ <ProgressIndicator
207
+ id="circularDarkMode"
208
+ labelText="Circular"
209
+ value={50}
210
+ indicatorType={ProgressIndicatorTypes.Circular}
211
+ darkMode
212
+ />
213
+ )
214
+ .toJSON();
215
+
216
+ expect(linearBasic).toMatchSnapshot();
217
+ expect(circularBasic).toMatchSnapshot();
218
+ expect(linearNoLabel).toMatchSnapshot();
219
+ expect(circularNoLabel).toMatchSnapshot();
220
+ expect(linearIndeterminate).toMatchSnapshot();
221
+ expect(circularIndeterminate).toMatchSnapshot();
222
+ expect(linearDarkMode).toMatchSnapshot();
223
+ expect(circularDarkMode).toMatchSnapshot();
224
+ });
225
+ });
@@ -0,0 +1,126 @@
1
+ import React from "react";
2
+ import {
3
+ Box,
4
+ CircularProgress as ChakraCircularProgress,
5
+ CircularProgressLabel as ChakraCircularProgressLabel,
6
+ Progress as ChakraProgress,
7
+ useMultiStyleConfig,
8
+ } from "@chakra-ui/react";
9
+
10
+ import {
11
+ ProgressIndicatorSizes,
12
+ ProgressIndicatorTypes,
13
+ } from "./ProgressIndicatorTypes";
14
+ import generateUUID from "../../helpers/generateUUID";
15
+ import Label from "../Label/Label";
16
+
17
+ export interface ProgressIndicatorProps {
18
+ /** Flag to render the component in a dark background. */
19
+ darkMode?: boolean;
20
+ /** ID that other components can cross reference for accessibility purposes. */
21
+ id?: string;
22
+ /** Whether the `ProgressIndicator` should be linear or circular. */
23
+ indicatorType?: ProgressIndicatorTypes;
24
+ /** Whether the progress animation should display because the `value` prop is
25
+ * not known. When this is set to `true`, the `value` prop will be ignored. */
26
+ isIndeterminate?: boolean;
27
+ /** The text to display in an HTML `label` element. */
28
+ labelText: string;
29
+ /** Visually show or hide the label text. When set to `false`, then the label
30
+ * text will be added to the parent component as its `aria-label` attribute. */
31
+ showLabel?: boolean;
32
+ /** The size of the linear or circular progress. */
33
+ size?: ProgressIndicatorSizes;
34
+ /** A number between 0 to 100 that defines the progress' value. */
35
+ value?: number;
36
+ }
37
+
38
+ /**
39
+ * A component that displays a progress status for any task that takes a long
40
+ * time to complete or consists of multiple steps. Examples include downloading,
41
+ * uploading, or processing.
42
+ */
43
+ const ProgressIndicator: React.FC<ProgressIndicatorProps> = (
44
+ props: ProgressIndicatorProps
45
+ ) => {
46
+ const {
47
+ darkMode = false,
48
+ id = generateUUID(),
49
+ indicatorType = ProgressIndicatorTypes.Linear,
50
+ isIndeterminate = false,
51
+ labelText,
52
+ showLabel = true,
53
+ size = ProgressIndicatorSizes.Default,
54
+ value = 0,
55
+ } = props;
56
+ const styles = useMultiStyleConfig("ProgressIndicator", {
57
+ darkMode,
58
+ size,
59
+ });
60
+ let finalValue = value;
61
+ if (finalValue < 0 || finalValue > 100) {
62
+ console.warn(
63
+ "ProgressIndicator: pass in a `value` between 0 and 100. Defaulting to 0."
64
+ );
65
+ finalValue = 0;
66
+ }
67
+ const progressProps = {
68
+ id,
69
+ // If the label is visually shown, associate it with the progress indicator.
70
+ // Otherwise, the `aria-label` will be added.
71
+ "aria-label": showLabel ? null : labelText,
72
+ "aria-labelledby": showLabel ? `${id}-label` : null,
73
+ // If `isIndeterminate` is true, then it overrides the `value` prop.
74
+ isIndeterminate: isIndeterminate || null,
75
+ value: isIndeterminate ? null : finalValue,
76
+ };
77
+ const progressComponent = (indicatorType) => {
78
+ // Only display the percentage text for the default size, not in the
79
+ // indeterminate state, and when `showLabel` is true.
80
+ if (indicatorType === ProgressIndicatorTypes.Circular) {
81
+ // For the small size, since the label won't be visible, we need to add
82
+ // it to the parent component's `aria-label` attribute.
83
+ if (size === ProgressIndicatorSizes.Small) {
84
+ progressProps["aria-label"] = labelText;
85
+ }
86
+ return (
87
+ <Box __css={styles.circularContainer}>
88
+ <ChakraCircularProgress {...progressProps} sx={styles.circular}>
89
+ {showLabel &&
90
+ !isIndeterminate &&
91
+ size !== ProgressIndicatorSizes.Small && (
92
+ <ChakraCircularProgressLabel>
93
+ {finalValue}%
94
+ </ChakraCircularProgressLabel>
95
+ )}
96
+ </ChakraCircularProgress>
97
+ {showLabel && size !== ProgressIndicatorSizes.Small && (
98
+ <Label id={`${id}-label`} htmlFor={id}>
99
+ {labelText}
100
+ </Label>
101
+ )}
102
+ </Box>
103
+ );
104
+ }
105
+ // The Linear progress indicator is the default.
106
+ return (
107
+ <>
108
+ {showLabel && (
109
+ <Label id={`${id}-label`} htmlFor={id}>
110
+ {labelText}
111
+ </Label>
112
+ )}
113
+ <Box __css={styles.linearContainer}>
114
+ <ChakraProgress {...progressProps} sx={styles.linear} />
115
+ {showLabel && !isIndeterminate && (
116
+ <Box __css={styles.linearPercentage}>{finalValue}%</Box>
117
+ )}
118
+ </Box>
119
+ </>
120
+ );
121
+ };
122
+
123
+ return <Box __css={styles}>{progressComponent(indicatorType)}</Box>;
124
+ };
125
+
126
+ export default ProgressIndicator;
@@ -0,0 +1,8 @@
1
+ export enum ProgressIndicatorSizes {
2
+ Default = "default",
3
+ Small = "small",
4
+ }
5
+ export enum ProgressIndicatorTypes {
6
+ Circular = "circular",
7
+ Linear = "linear",
8
+ }