@nypl/design-system-react-components 0.25.9 → 0.25.10

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 (78) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/dist/components/DatePicker/DatePicker.d.ts +1 -1
  3. package/dist/components/Fieldset/Fieldset.d.ts +1 -3
  4. package/dist/components/Form/Form.d.ts +13 -12
  5. package/dist/components/Form/FormTypes.d.ts +2 -2
  6. package/dist/components/HorizontalRule/HorizontalRule.d.ts +1 -1
  7. package/dist/components/RadioGroup/RadioGroup.d.ts +3 -3
  8. package/dist/components/SearchBar/SearchBar.d.ts +5 -5
  9. package/dist/components/Table/Table.d.ts +9 -3
  10. package/dist/components/Template/Template.d.ts +23 -4
  11. package/dist/design-system-react-components.cjs.development.js +197 -89
  12. package/dist/design-system-react-components.cjs.development.js.map +1 -1
  13. package/dist/design-system-react-components.cjs.production.min.js +1 -1
  14. package/dist/design-system-react-components.cjs.production.min.js.map +1 -1
  15. package/dist/design-system-react-components.esm.js +186 -91
  16. package/dist/design-system-react-components.esm.js.map +1 -1
  17. package/dist/index.d.ts +4 -4
  18. package/dist/resources.scss +0 -2
  19. package/dist/theme/components/breadcrumb.d.ts +1 -1
  20. package/dist/theme/components/customTable.d.ts +12 -3
  21. package/package.json +40 -36
  22. package/src/components/Accordion/Accordion.stories.mdx +1 -1
  23. package/src/components/Accordion/Accordion.test.tsx +45 -1
  24. package/src/components/Accordion/Accordion.tsx +20 -8
  25. package/src/components/Accordion/__snapshots__/Accordion.test.tsx.snap +243 -0
  26. package/src/components/Breadcrumbs/Breadcrumbs.stories.mdx +13 -2
  27. package/src/components/Breadcrumbs/Breadcrumbs.test.tsx +15 -0
  28. package/src/components/Breadcrumbs/Breadcrumbs.tsx +9 -3
  29. package/src/components/Breadcrumbs/__snapshots__/Breadcrumbs.test.tsx.snap +5 -5
  30. package/src/components/Card/Card.stories.mdx +1 -1
  31. package/src/components/Card/Card.tsx +4 -1
  32. package/src/components/Chakra/Flex.stories.mdx +113 -0
  33. package/src/components/DatePicker/DatePicker.stories.mdx +1 -1
  34. package/src/components/DatePicker/DatePicker.test.tsx +6 -6
  35. package/src/components/DatePicker/DatePicker.tsx +3 -4
  36. package/src/components/Fieldset/Fieldset.stories.mdx +1 -1
  37. package/src/components/Fieldset/Fieldset.tsx +2 -4
  38. package/src/components/Form/Form.stories.mdx +34 -16
  39. package/src/components/Form/Form.test.tsx +92 -3
  40. package/src/components/Form/Form.tsx +25 -21
  41. package/src/components/Form/FormTypes.tsx +2 -2
  42. package/src/components/Form/__snapshots__/Form.test.tsx.snap +0 -1
  43. package/src/components/Hero/Hero.stories.mdx +1 -1
  44. package/src/components/HorizontalRule/HorizontalRule.stories.mdx +3 -2
  45. package/src/components/HorizontalRule/HorizontalRule.tsx +2 -2
  46. package/src/components/HorizontalRule/__snapshots__/HorizontalRule.test.tsx.snap +4 -4
  47. package/src/components/List/List.stories.mdx +1 -1
  48. package/src/components/List/List.tsx +1 -1
  49. package/src/components/Pagination/Pagination.stories.mdx +1 -1
  50. package/src/components/Pagination/Pagination.tsx +2 -2
  51. package/src/components/Radio/__snapshots__/Radio.test.tsx.snap +5 -5
  52. package/src/components/RadioGroup/RadioGroup.stories.mdx +1 -1
  53. package/src/components/RadioGroup/RadioGroup.test.tsx +13 -11
  54. package/src/components/RadioGroup/RadioGroup.tsx +88 -89
  55. package/src/components/RadioGroup/__snapshots__/RadioGroup.test.tsx.snap +18 -18
  56. package/src/components/SearchBar/SearchBar.Test.tsx +106 -28
  57. package/src/components/SearchBar/SearchBar.stories.mdx +7 -4
  58. package/src/components/SearchBar/SearchBar.tsx +19 -20
  59. package/src/components/Select/Select.test.tsx +89 -0
  60. package/src/components/Select/Select.tsx +7 -1
  61. package/src/components/Select/__snapshots__/Select.test.tsx.snap +545 -0
  62. package/src/components/Slider/__snapshots__/Slider.test.tsx.snap +7 -0
  63. package/src/components/Table/Table.stories.mdx +118 -19
  64. package/src/components/Table/Table.test.tsx +80 -3
  65. package/src/components/Table/Table.tsx +26 -16
  66. package/src/components/Table/__snapshots__/Table.test.tsx.snap +1179 -0
  67. package/src/components/Tabs/Tabs.stories.mdx +1 -1
  68. package/src/components/Tabs/Tabs.test.tsx +21 -5
  69. package/src/components/Tabs/Tabs.tsx +33 -18
  70. package/src/components/Tabs/__snapshots__/Tabs.test.tsx.snap +195 -0
  71. package/src/components/Template/Template.stories.mdx +79 -4
  72. package/src/components/Template/Template.test.tsx +65 -3
  73. package/src/components/Template/Template.tsx +58 -8
  74. package/src/components/Template/__snapshots__/Template.test.tsx.snap +93 -0
  75. package/src/index.ts +8 -2
  76. package/src/theme/components/breadcrumb.ts +1 -1
  77. package/src/theme/components/customTable.ts +16 -3
  78. package/src/utils/componentCategories.ts +1 -0
@@ -6,8 +6,6 @@ import {
6
6
  useStyleConfig,
7
7
  } from "@chakra-ui/react";
8
8
 
9
- import generateUUID from "../../helpers/generateUUID";
10
- import { ColorVariants } from "./BreadcrumbsTypes";
11
9
  import Icon from "../Icons/Icon";
12
10
  import {
13
11
  IconNames,
@@ -15,6 +13,8 @@ import {
15
13
  IconSizes,
16
14
  IconTypes,
17
15
  } from "../Icons/IconTypes";
16
+ import generateUUID from "../../helpers/generateUUID";
17
+ import { ColorVariants } from "./BreadcrumbsTypes";
18
18
  import { getVariant } from "../../utils/utils";
19
19
 
20
20
  export interface BreadcrumbsDataProps {
@@ -83,9 +83,15 @@ function Breadcrumbs(props: React.PropsWithChildren<BreadcrumbProps>) {
83
83
  const styles = useStyleConfig("Breadcrumb", { variant });
84
84
  const finalStyles = { ...styles, ...additionalStyles };
85
85
  const breadcrumbItems = getElementsFromData(breadcrumbsData, id);
86
+ const ariaAttrs = { "aria-label": "Breadcrumb" };
86
87
 
87
88
  return (
88
- <ChakraBreadcrumb className={className} __css={finalStyles} id={id}>
89
+ <ChakraBreadcrumb
90
+ className={className}
91
+ id={id}
92
+ {...ariaAttrs}
93
+ __css={finalStyles}
94
+ >
89
95
  {breadcrumbItems}
90
96
  </ChakraBreadcrumb>
91
97
  );
@@ -2,7 +2,7 @@
2
2
 
3
3
  exports[`Breadcrumbs Snapshot Renders the UI snapshot correctly 1`] = `
4
4
  <nav
5
- aria-label="breadcrumb"
5
+ aria-label="Breadcrumb"
6
6
  className="chakra-breadcrumb css-0"
7
7
  id="breadcrumbs-test"
8
8
  >
@@ -101,7 +101,7 @@ exports[`Breadcrumbs Snapshot Renders the UI snapshot correctly 1`] = `
101
101
 
102
102
  exports[`Breadcrumbs Snapshot Renders the UI snapshot correctly 2`] = `
103
103
  <nav
104
- aria-label="breadcrumb"
104
+ aria-label="Breadcrumb"
105
105
  className="chakra-breadcrumb css-0"
106
106
  id="breadcrumbs-test"
107
107
  >
@@ -200,7 +200,7 @@ exports[`Breadcrumbs Snapshot Renders the UI snapshot correctly 2`] = `
200
200
 
201
201
  exports[`Breadcrumbs Snapshot Renders the UI snapshot correctly 3`] = `
202
202
  <nav
203
- aria-label="breadcrumb"
203
+ aria-label="Breadcrumb"
204
204
  className="chakra-breadcrumb css-0"
205
205
  id="breadcrumbs-test"
206
206
  >
@@ -299,7 +299,7 @@ exports[`Breadcrumbs Snapshot Renders the UI snapshot correctly 3`] = `
299
299
 
300
300
  exports[`Breadcrumbs Snapshot Renders the UI snapshot correctly 4`] = `
301
301
  <nav
302
- aria-label="breadcrumb"
302
+ aria-label="Breadcrumb"
303
303
  className="chakra-breadcrumb css-0"
304
304
  id="breadcrumbs-test"
305
305
  >
@@ -398,7 +398,7 @@ exports[`Breadcrumbs Snapshot Renders the UI snapshot correctly 4`] = `
398
398
 
399
399
  exports[`Breadcrumbs Snapshot Renders the UI snapshot correctly 5`] = `
400
400
  <nav
401
- aria-label="breadcrumb"
401
+ aria-label="Breadcrumb"
402
402
  className="chakra-breadcrumb css-1f2fw9u"
403
403
  id="breadcrumbs-test"
404
404
  >
@@ -93,7 +93,7 @@ export const cardLayoutsEnumValues = getStorybookEnumValues(
93
93
  | Component Version | DS Version |
94
94
  | ----------------- | ---------- |
95
95
  | Added | `0.24.0` |
96
- | Latest | `0.25.9` |
96
+ | Latest | `0.25.10` |
97
97
 
98
98
  <Description of={Card} />
99
99
 
@@ -222,7 +222,10 @@ export default function Card(props: React.PropsWithChildren<CardProps>) {
222
222
  child.props.children
223
223
  );
224
224
  const elem = React.cloneElement(child, {
225
- additionalStyles: styles.heading,
225
+ additionalStyles: {
226
+ ...styles.heading,
227
+ ...child.props.additionalStyles,
228
+ },
226
229
  key,
227
230
  center,
228
231
  // Override the child text with the potential `CardLinkOverlay`.
@@ -0,0 +1,113 @@
1
+ import { Box, Flex, Spacer, VStack } from "@chakra-ui/react";
2
+ import { Canvas, Meta, Story } from "@storybook/addon-docs";
3
+
4
+ import Heading from "../Heading/Heading";
5
+ import { HeadingLevels } from "../Heading/HeadingTypes";
6
+ import Link from "../Link/Link";
7
+ import { LinkTypes } from "../Link/LinkTypes";
8
+ import { getCategory } from "../../utils/componentCategories";
9
+ import DSProvider from "../../theme/provider";
10
+
11
+ <Meta title={getCategory("Flex")} component={Flex} />
12
+
13
+ # Flex
14
+
15
+ | Component Version | DS Version |
16
+ | ----------------- | ---------- |
17
+ | Added | `0.25.10` |
18
+ | Latest | `0.25.10` |
19
+
20
+ Note: This needs the use of the `DSProvider` component. See the
21
+ [README](https://nypl.github.io/nypl-design-system/storybook-static/?path=/story/chakra-ui--page#dsprovider)
22
+ for more information.
23
+
24
+ This component is directly exported from Chakra UI. The `Flex` component is
25
+ useful for simple layouts and can be used along with Chakra's `Spacer` component.
26
+ The combination can be used to create a container where the children span the
27
+ entire width of the container.
28
+
29
+ Details about available props and related child components can be found on the
30
+ [Flex component](https://chakra-ui.com/docs/layout/flex) page on the Chakra UI site.
31
+
32
+ <Canvas>
33
+ <Story
34
+ name="Flex"
35
+ args={{
36
+ alignItems: "baseline",
37
+ }}
38
+ >
39
+ {(args) => (
40
+ <Flex {...args}>
41
+ <Box w="20" h="20" bg="brand.primary" />
42
+ <Box w="20" h="20" bg="brand.secondary" />
43
+ <Box w="20" h="20" bg="brand.primary" />
44
+ <Box w="20" h="20" bg="brand.secondary" />
45
+ <Box w="20" h="20" bg="brand.primary" />
46
+ </Flex>
47
+ )}
48
+ </Story>
49
+ </Canvas>
50
+
51
+ ## Examples
52
+
53
+ Use the `justify` prop to move the children around.
54
+
55
+ <Canvas>
56
+ <DSProvider>
57
+ <VStack align="stretch">
58
+ <div>
59
+ <p>`justify` set to "center"</p>
60
+ <Flex alignItems="baseline" justify="center">
61
+ <Box w="20" h="20" bg="brand.primary" />
62
+ <Box w="20" h="20" bg="brand.secondary" />
63
+ <Box w="20" h="20" bg="brand.primary" />
64
+ <Box w="20" h="20" bg="brand.secondary" />
65
+ <Box w="20" h="20" bg="brand.primary" />
66
+ </Flex>
67
+ </div>
68
+ <div>
69
+ <p>`justify` set to "space-between"</p>
70
+ <Flex alignItems="baseline" justify="space-between">
71
+ <Box w="20" h="20" bg="brand.primary" />
72
+ <Box w="20" h="20" bg="brand.secondary" />
73
+ <Box w="20" h="20" bg="brand.primary" />
74
+ <Box w="20" h="20" bg="brand.secondary" />
75
+ <Box w="20" h="20" bg="brand.primary" />
76
+ </Flex>
77
+ </div>
78
+ </VStack>
79
+ </DSProvider>
80
+ </Canvas>
81
+
82
+ ## With Spacer
83
+
84
+ A common use-case is displaying a row with two children where the first aligns
85
+ left and the second aligns right. Add the `Spacer` component between the children.
86
+ This is similar to setting `justify="space-between"` on the `Flex` parent but
87
+ the `Spacer` component is more flexible for most situations.
88
+
89
+ <Canvas>
90
+ <DSProvider>
91
+ <Flex alignItems="baseline">
92
+ <Heading id="row-heading" level={HeadingLevels.Three}>
93
+ Heading
94
+ </Heading>
95
+ <Spacer />
96
+ <Link href="#viewmore" type={LinkTypes.Forwards}>
97
+ View more
98
+ </Link>
99
+ </Flex>
100
+ </DSProvider>
101
+ </Canvas>
102
+
103
+ ```jsx
104
+ <Flex alignItems="baseline">
105
+ <Heading id="row-heading" level={HeadingLevels.Three}>
106
+ Heading
107
+ </Heading>
108
+ <Spacer />
109
+ <Link href="#viewmore" type={LinkTypes.Forwards}>
110
+ View more
111
+ </Link>
112
+ </Flex>
113
+ ```
@@ -78,7 +78,7 @@ export const enumValues = getStorybookEnumValues(
78
78
  | Component Version | DS Version |
79
79
  | ----------------- | ---------- |
80
80
  | Added | `0.24.0` |
81
- | Latest | `0.25.9` |
81
+ | Latest | `0.25.10` |
82
82
 
83
83
  <Description of={DatePicker} />
84
84
 
@@ -9,7 +9,7 @@ import { DatePickerTypes } from "./DatePickerTypes";
9
9
  import { TextInputRefType } from "../TextInput/TextInput";
10
10
 
11
11
  /** This adds a "0" padding for date values under "10". */
12
- const str_pad = (n) => String("0" + n).slice(-2);
12
+ const strPad = (n) => String("0" + n).slice(-2);
13
13
  const monthArray = [
14
14
  "January",
15
15
  "February",
@@ -49,8 +49,8 @@ describe("DatePicker", () => {
49
49
  /** Returns today's year, month, and day values. */
50
50
  const getTodaysValues = () => {
51
51
  const year = todaysDate.getFullYear();
52
- const month = str_pad(todaysDate.getMonth() + 1);
53
- const day = str_pad(todaysDate.getDate());
52
+ const month = strPad(todaysDate.getMonth() + 1);
53
+ const day = strPad(todaysDate.getDate());
54
54
  return [year, month, day];
55
55
  };
56
56
  /** Returns today's date in string format based on the DatePicker type. */
@@ -328,9 +328,9 @@ describe("DatePicker", () => {
328
328
  expect(screen.getByDisplayValue(newDayValue)).toBeInTheDocument();
329
329
 
330
330
  const { startDate } = dateObject;
331
- const valueFromOnChange = `${startDate.getFullYear()}-${str_pad(
331
+ const valueFromOnChange = `${startDate.getFullYear()}-${strPad(
332
332
  startDate.getMonth() + 1
333
- )}-${str_pad(startDate.getDate())}`;
333
+ )}-${strPad(startDate.getDate())}`;
334
334
  expect(newDayValue).toEqual(valueFromOnChange);
335
335
  });
336
336
 
@@ -724,7 +724,7 @@ describe("DatePicker", () => {
724
724
  // We are two months ahead but still selecting the midmonth day.
725
725
  userEvent.click(screen.getByText(midMonthDay));
726
726
  // So only the month should change accordingly.
727
- const newMonthValue = `${newDayValue.substr(0, 5)}${str_pad(
727
+ const newMonthValue = `${newDayValue.substr(0, 5)}${strPad(
728
728
  "10"
729
729
  )}${newDayValue.substr(7)}`;
730
730
  expect(screen.getByDisplayValue(newMonthValue)).toBeInTheDocument();
@@ -4,7 +4,7 @@ import ReactDatePicker from "react-datepicker";
4
4
  import { DatePickerTypes } from "./DatePickerTypes";
5
5
  import Fieldset from "../Fieldset/Fieldset";
6
6
  import { FormRow, FormField } from "../Form/Form";
7
- import { FormSpacing } from "../Form/FormTypes";
7
+ import { FormGaps } from "../Form/FormTypes";
8
8
  import HelperErrorText, {
9
9
  HelperErrorTextType,
10
10
  } from "../HelperErrorText/HelperErrorText";
@@ -16,8 +16,7 @@ import generateUUID from "../../helpers/generateUUID";
16
16
  import { useMultiStyleConfig } from "@chakra-ui/system";
17
17
 
18
18
  // The object shape for the DatePicker's start and end date state values.
19
- // Internal use only.
20
- interface FullDateType {
19
+ export interface FullDateType {
21
20
  /** Date object that gets returned for the onChange
22
21
  * function only for date ranges. */
23
22
  endDate?: Date;
@@ -217,7 +216,7 @@ const DateRangeRow: React.FC<DateRangeRowProps> = ({
217
216
  children,
218
217
  }) =>
219
218
  isDateRange ? (
220
- <FormRow id={`${id}-form-row`} gap={FormSpacing.ExtraSmall}>
219
+ <FormRow id={`${id}-form-row`} gap={FormGaps.ExtraSmall}>
221
220
  {children}
222
221
  </FormRow>
223
222
  ) : (
@@ -39,7 +39,7 @@ import DSProvider from "../../theme/provider";
39
39
  | Component Version | DS Version |
40
40
  | ----------------- | ---------- |
41
41
  | Added | `0.25.3` |
42
- | Latest | `0.25.3` |
42
+ | Latest | `0.25.10` |
43
43
 
44
44
  <Description of={Fieldset} />
45
45
 
@@ -2,8 +2,6 @@ import React from "react";
2
2
  import { Box, useMultiStyleConfig } from "@chakra-ui/react";
3
3
 
4
4
  interface FieldsetProps {
5
- /** Children to render. Typically form-related components are used. */
6
- children: React.ReactNode;
7
5
  /** Additional class name to add. */
8
6
  className?: string;
9
7
  /** ID that other components can cross reference for accessibility purposes */
@@ -24,7 +22,7 @@ interface FieldsetProps {
24
22
  * A wrapper component that renders a `fieldset` element along with a `legend`
25
23
  * element as its first child. Commonly used to wrap form components.
26
24
  */
27
- const Fieldset: React.FC<FieldsetProps> = ({
25
+ const Fieldset = ({
28
26
  children,
29
27
  className,
30
28
  id,
@@ -32,7 +30,7 @@ const Fieldset: React.FC<FieldsetProps> = ({
32
30
  isRequired = false,
33
31
  legendText,
34
32
  optReqFlag = true,
35
- }) => {
33
+ }: React.PropsWithChildren<FieldsetProps>) => {
36
34
  const styles = useMultiStyleConfig("Fieldset", { isLegendHidden });
37
35
  return (
38
36
  <Box as="fieldset" id={id} __css={styles} className={className}>
@@ -14,7 +14,7 @@ import CheckboxGroup from "../CheckboxGroup/CheckboxGroup";
14
14
  import DatePicker from "../DatePicker/DatePicker";
15
15
  import { DatePickerTypes } from "../DatePicker/DatePickerTypes";
16
16
  import Form, { FormRow, FormField } from "./Form";
17
- import { FormSpacing } from "./FormTypes";
17
+ import { FormGaps } from "./FormTypes";
18
18
  import Heading from "../Heading/Heading";
19
19
  import { HeadingLevels } from "../Heading/HeadingTypes";
20
20
  import HorizontalRule from "../HorizontalRule/HorizontalRule";
@@ -27,7 +27,7 @@ import { getCategory } from "../../utils/componentCategories";
27
27
  import SimpleGrid from "../Grid/SimpleGrid";
28
28
  import { getStorybookEnumValues } from "../../utils/utils";
29
29
 
30
- export const enumValues = getStorybookEnumValues(FormSpacing, "FormSpacing");
30
+ export const enumValues = getStorybookEnumValues(FormGaps, "FormGaps");
31
31
 
32
32
  <Meta
33
33
  title={getCategory("Form")}
@@ -47,9 +47,9 @@ export const enumValues = getStorybookEnumValues(FormSpacing, "FormSpacing");
47
47
  control: { type: "radio" },
48
48
  options: ["get", "post"],
49
49
  },
50
- spacing: {
50
+ gap: {
51
51
  control: { type: "select" },
52
- table: { defaultValue: { summary: "FormSpacing.Large" } },
52
+ table: { defaultValue: { summary: "FormGaps.Large" } },
53
53
  options: enumValues.options,
54
54
  },
55
55
  }}
@@ -60,15 +60,31 @@ export const enumValues = getStorybookEnumValues(FormSpacing, "FormSpacing");
60
60
  | Component Version | DS Version |
61
61
  | ----------------- | ---------- |
62
62
  | Added | `0.23.2` |
63
- | Latest | `0.25.1` |
63
+ | Latest | `0.25.10` |
64
64
 
65
65
  <Description of={Form} />
66
66
 
67
- The `Form` component renders a standard `<form>` element and should be used to handle layout and spacing for child input fields. `FormRow` and `FormField` components should be used to build the `<form>` struture and to arrange input fields as needed.
67
+ The `Form` component renders a standard `<form>` element and should be used to
68
+ handle layout and spacing for child input fields. `FormRow` and `FormField`
69
+ components should be used to build the `<form>` structure and to arrange input
70
+ fields as needed.
68
71
 
69
- `FormField` should be used as a parent for all input components from the DS (`Button`, `Select`, `TextInput`, etc.).
72
+ ```jsx
73
+ <Form>
74
+ <FormRow>
75
+ <FormField>{/* ... */}</FormField>
76
+ </FormRow>
77
+ <FormRow>
78
+ <FormField>{/* ... */}</FormField>
79
+ </FormRow>
80
+ </Form>
81
+ ```
82
+
83
+ `FormField` should be used as a parent for all input components from the DS
84
+ (`Button`, `Select`, `TextInput`, etc.).
70
85
 
71
- `FormRow` should be used as a parent of multiple `FormField` components when you need to render multiple input components in a horizontal row.
86
+ `FormRow` should be used as a parent of multiple `FormField` components when you
87
+ need to render multiple input components in a horizontal row.
72
88
 
73
89
  <Canvas withToolbar>
74
90
  <Story
@@ -78,11 +94,11 @@ The `Form` component renders a standard `<form>` element and should be used to h
78
94
  className: undefined,
79
95
  id: "form-id",
80
96
  method: "get",
81
- spacing: "FormSpacing.Large",
97
+ gap: "FormGaps.Large",
82
98
  }}
83
99
  >
84
100
  {(args) => (
85
- <Form {...args} spacing={enumValues.getValue(args.spacing)}>
101
+ <Form {...args} gap={enumValues.getValue(args.gap)}>
86
102
  <FormRow>
87
103
  <FormField>
88
104
  <TextInput
@@ -205,7 +221,7 @@ export const formRow = (nameString, size) => {
205
221
  return (
206
222
  <li key={size}>
207
223
  <Heading level={HeadingLevels.Three}>{labelText}</Heading>
208
- <Form spacing={size}>
224
+ <Form gap={size}>
209
225
  <FormRow>
210
226
  <FormField>
211
227
  <Select
@@ -250,16 +266,18 @@ export const formRow = (nameString, size) => {
250
266
  );
251
267
  };
252
268
  export const sizes = [];
253
- for (const formSpacing in FormSpacing) {
254
- sizes.push(formRow(`FormSpacing.${formSpacing}`, FormSpacing[formSpacing]));
269
+ for (const FormGaps in FormGaps) {
270
+ sizes.push(formRow(`FormGaps.${FormGaps}`, FormGaps[FormGaps]));
255
271
  }
256
272
  export const getForms = (list) => <ul style={{ listStyle: "none" }}>{list}</ul>;
257
273
 
258
- By default, the `Form` component will handle the NYPL spacing around form input elements. The default spacing value is `large`, which corresponds to the CSS variable `--nypl-space-l` (2rem / 32px).
274
+ By default, the `Form` component will handle the NYPL spacing around form input
275
+ elements. The default spacing value is `large`, which corresponds to the CSS
276
+ variable `--nypl-space-l` (2rem / 32px).
259
277
 
260
278
  **IMPORTANT:** The default spacing should not be overwritten without a very good reason.
261
279
 
262
- Below are the spacing variants available with the `FormSpacing` enum.
280
+ Below are the spacing variants available with the `FormGaps` enum.
263
281
 
264
282
  <Canvas>
265
283
  <Story
@@ -277,7 +295,7 @@ Below are the spacing variants available with the `FormSpacing` enum.
277
295
  <Story name="Example Code" />
278
296
 
279
297
  ```jsx
280
- <Form action="/end/point" method="get" spacing={FormSpacing.Large}>
298
+ <Form action="/end/point" method="get" gap={FormGaps.Large}>
281
299
  <FormField>
282
300
  <TextInput
283
301
  labelText="Username"
@@ -1,10 +1,10 @@
1
1
  import * as React from "react";
2
- import { render, screen } from "@testing-library/react";
2
+ import { fireEvent, render, screen } from "@testing-library/react";
3
3
  import { axe } from "jest-axe";
4
4
  import renderer from "react-test-renderer";
5
5
 
6
6
  import Form, { FormRow, FormField } from "./Form";
7
- // import { FormSpacing } from "./FormTypes";
7
+ // import { FormGaps } from "./FormTypes";
8
8
  import TextInput from "../TextInput/TextInput";
9
9
 
10
10
  describe("Form Accessibility", () => {
@@ -12,6 +12,19 @@ describe("Form Accessibility", () => {
12
12
  const { container } = render(<Form />);
13
13
  expect(await axe(container)).toHaveNoViolations();
14
14
  });
15
+
16
+ it("passes axe accessibility test for the full hierachy", async () => {
17
+ const { container } = render(
18
+ <Form>
19
+ <FormRow>
20
+ <FormField>Form Field 1</FormField>
21
+ <FormField>Form Field 2</FormField>
22
+ <FormField>Form Field 3</FormField>
23
+ </FormRow>
24
+ </Form>
25
+ );
26
+ expect(await axe(container)).toHaveNoViolations();
27
+ });
15
28
  });
16
29
 
17
30
  describe("Form Snapshot", () => {
@@ -101,11 +114,87 @@ describe("Form", () => {
101
114
  expect(form).toHaveAttribute("method", "get");
102
115
  });
103
116
 
117
+ it("passes down the `Form`'s id down to its children", () => {
118
+ const { container } = render(
119
+ <Form id="formId">
120
+ <FormRow>
121
+ <FormField>
122
+ <TextInput labelText="Input Field" />
123
+ </FormField>
124
+ <FormField>
125
+ <TextInput labelText="Input Field" />
126
+ </FormField>
127
+ </FormRow>
128
+ <FormRow>
129
+ <FormField>
130
+ <TextInput labelText="Input Field" />
131
+ </FormField>
132
+ <FormField>
133
+ <TextInput labelText="Input Field" />
134
+ </FormField>
135
+ </FormRow>
136
+ </Form>
137
+ );
138
+
139
+ expect(container.querySelector("#formId")).toBeInTheDocument();
140
+ // The first `FormRow` adds "child0" to its id
141
+ expect(container.querySelector("#formId-child0")).toBeInTheDocument();
142
+ // The first `FormRow`'s first `FormField` adds "grandchild0" to its id
143
+ expect(
144
+ container.querySelector("#formId-child0-grandchild0")
145
+ ).toBeInTheDocument();
146
+ // The first `FormRow`'s second `FormField` adds "grandchild1" to its id
147
+ expect(
148
+ container.querySelector("#formId-child0-grandchild1")
149
+ ).toBeInTheDocument();
150
+ // The second `FormRow` adds "child1" to its id
151
+ expect(container.querySelector("#formId-child1")).toBeInTheDocument();
152
+ // The second `FormRow`'s first `FormField` adds "grandchild0" to its id
153
+ expect(
154
+ container.querySelector("#formId-child1-grandchild0")
155
+ ).toBeInTheDocument();
156
+ // The second `FormRow`'s second `FormField` adds "grandchild1" to its id
157
+ expect(
158
+ container.querySelector("#formId-child1-grandchild1")
159
+ ).toBeInTheDocument();
160
+ });
161
+
162
+ it("logs a warning if a child of `FormRow` is not a `FormField`", () => {
163
+ const warn = jest.spyOn(console, "warn");
164
+ render(
165
+ <Form>
166
+ <FormRow>
167
+ <div>Not a FormField</div>
168
+ </FormRow>
169
+ </Form>
170
+ );
171
+ expect(warn).toHaveBeenCalledWith(
172
+ "FormRow children must be `FormField` components."
173
+ );
174
+ });
175
+
176
+ it("calls the onSubmit function", () => {
177
+ const onSubmit = jest.fn();
178
+ render(
179
+ <Form onSubmit={onSubmit}>
180
+ <FormRow>
181
+ <FormField>
182
+ <TextInput labelText="Input Field" />
183
+ </FormField>
184
+ </FormRow>
185
+ </Form>
186
+ );
187
+ const form = screen.getByRole("form");
188
+ expect(onSubmit).toHaveBeenCalledTimes(0);
189
+ fireEvent.submit(form);
190
+ expect(onSubmit).toHaveBeenCalledTimes(1);
191
+ });
192
+
104
193
  // TO DO: There's somethign weird about checking for the "grid-gap" style.
105
194
  // Other styles can be validated, but "grid-gap" is being ellusive.
106
195
  // it("Renders a <form> element with spacing variant applied", () => {
107
196
  // render(
108
- // <Form spacing={FormSpacing.ExtraSmall}>
197
+ // <Form gap={FormGaps.ExtraSmall}>
109
198
  // <FormRow />
110
199
  // </Form>
111
200
  // );
@@ -1,30 +1,29 @@
1
- import { Box } from "@chakra-ui/react";
2
1
  import * as React from "react";
3
2
 
4
- import { FormSpacing } from "./FormTypes";
3
+ import { FormGaps } from "./FormTypes";
5
4
  import SimpleGrid from "../Grid/SimpleGrid";
6
5
  import generateUUID from "../../helpers/generateUUID";
7
6
 
8
- export interface FormChildProps {
9
- /** className to be applied to FormRow */
7
+ interface FormBaseProps {
8
+ /** className to be applied to FormRow, FormField, and Form */
10
9
  className?: string;
11
- /** Spacing size (internal use) */
12
- gap?: FormSpacing;
10
+ /** Optional spacing size; if omitted, the default `large` (2rem / 32px)
11
+ * spacing will be used; ```IMPORTANT: for general form layout, this prop
12
+ * should not be used``` */
13
+ gap?: FormGaps;
13
14
  /** ID that other components can cross reference (internal use) */
14
15
  id?: string;
15
16
  }
16
17
 
17
- export interface FormProps {
18
+ export interface FormChildProps extends FormBaseProps {}
19
+
20
+ export interface FormProps extends FormBaseProps {
18
21
  /** Optional form `action` attribute */
19
22
  action?: string;
20
- /** Optional className you can add in addition to `form` */
21
- className?: string;
22
- /** Optional ID that other components can cross reference */
23
- id?: string;
24
23
  /** Optional form `method` attribute */
25
24
  method?: "get" | "post";
26
- /** Optional spacing size; if omitted, the default `large` (2rem / 32px) spacing will be used; ```IMPORTANT: for general form layout, this prop should not be used``` */
27
- spacing?: FormSpacing;
25
+ /** Function to call for the `onSubmit` form event. */
26
+ onSubmit?: (e: React.FormEvent<HTMLFormElement>) => void;
28
27
  }
29
28
 
30
29
  /** FormRow child-component */
@@ -35,7 +34,11 @@ export function FormRow(props: React.PropsWithChildren<FormChildProps>) {
35
34
  children,
36
35
  (child: React.ReactElement, i) => {
37
36
  if (!child) return null;
38
- return React.cloneElement(child, { id: `${id}-grandchild${i}` });
37
+ if (child.type === FormField || child.props.mdxType === "FormField") {
38
+ return React.cloneElement(child, { id: `${id}-grandchild${i}` });
39
+ }
40
+ console.warn("FormRow children must be `FormField` components.");
41
+ return null;
39
42
  }
40
43
  );
41
44
  return (
@@ -61,9 +64,10 @@ export default function Form(props: React.PropsWithChildren<FormProps>) {
61
64
  action,
62
65
  children,
63
66
  className,
67
+ gap = FormGaps.Large,
64
68
  id = generateUUID(),
65
69
  method,
66
- spacing = FormSpacing.Large,
70
+ onSubmit,
67
71
  } = props;
68
72
 
69
73
  let attributes = {};
@@ -76,21 +80,21 @@ export default function Form(props: React.PropsWithChildren<FormProps>) {
76
80
  const alteredChildren = React.Children.map(
77
81
  children,
78
82
  (child: React.ReactElement, i) => {
79
- return React.cloneElement(child, { gap: spacing, id: `${id}-child${i}` });
83
+ return React.cloneElement(child, { gap, id: `${id}-child${i}` });
80
84
  }
81
85
  );
82
86
 
83
87
  return (
84
- <Box
85
- as="form"
88
+ <form
86
89
  aria-label="form"
90
+ className={className}
87
91
  id={id}
92
+ onSubmit={onSubmit}
88
93
  {...attributes}
89
- className={className}
90
94
  >
91
- <SimpleGrid columns={1} gap={spacing} id={id + "-parent"}>
95
+ <SimpleGrid columns={1} gap={gap} id={`${id}-parent`}>
92
96
  {alteredChildren}
93
97
  </SimpleGrid>
94
- </Box>
98
+ </form>
95
99
  );
96
100
  }
@@ -1,3 +1,3 @@
1
- import { GridGaps as FormSpacing } from "../Grid/GridTypes";
1
+ import { GridGaps as FormGaps } from "../Grid/GridTypes";
2
2
 
3
- export { FormSpacing };
3
+ export { FormGaps };