@coopdigital/react 1.17.1 → 1.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,44 +1,31 @@
1
1
  "use client";
2
2
  import { getSlotProp } from "../../utils/index.js";
3
3
  import { useId as useId$1 } from "../../hooks/useId.js";
4
- import { useSlots } from "../../hooks/useSlots.js";
5
4
  import { FieldContext } from "./Field.context.js";
6
5
  import { Error } from "../FieldMarkers/Error.js";
7
6
  import { Hint } from "../FieldMarkers/Hint.js";
8
7
  import { Label } from "../FieldMarkers/Label.js";
9
8
  import clsx from "clsx";
10
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
9
+ import { jsx } from "react/jsx-runtime";
11
10
  import "react";
12
11
  //#region src/components/Field/Field.tsx
13
- const componentSlots = {
14
- Children: null,
15
- Control: null,
16
- FieldError: null,
17
- FieldHint: null,
18
- FieldLabel: null
19
- };
20
12
  const Field = ({ boxed = false, children, className, error = false, hideErrorBar = false, ref, ...props }) => {
21
- const slots = { ...useSlots(componentSlots, children) };
22
- const hasMarkers = !!(slots.FieldLabel ?? slots.FieldError ?? slots.FieldHint);
23
13
  const controlProps = {
24
14
  disabled: false,
25
15
  id: useId$1()
26
16
  };
27
- if (Array.isArray(slots.Children)) slots.Children = slots.Children.filter((child) => {
17
+ Array.from([children].flat()).forEach((child) => {
28
18
  if (child && typeof child === "object" && "type" in child) {
29
19
  if (typeof child?.type === "function" && "slot" in child.type) {
30
20
  const { slot } = child.type;
31
- slots.Control = child;
32
- if (slots.Control.props) {
21
+ if (slot.field && child.props) {
33
22
  controlProps.inline = slot.inline ?? false;
34
- controlProps.disabled = getSlotProp(slots.Control, "disabled") ?? false;
35
- controlProps.id = getSlotProp(slots.Control, "id") ?? controlProps.id;
23
+ controlProps.disabled = getSlotProp(child, "disabled") ?? false;
24
+ controlProps.id = getSlotProp(child, "id") ?? controlProps.id;
36
25
  controlProps.suffix = slot.suffix ?? void 0;
37
26
  }
38
- return false;
39
27
  }
40
28
  }
41
- return true;
42
29
  });
43
30
  const componentProps = {
44
31
  ...props,
@@ -50,22 +37,10 @@ const Field = ({ boxed = false, children, className, error = false, hideErrorBar
50
37
  };
51
38
  return /* @__PURE__ */ jsx(FieldContext.Provider, {
52
39
  value: { ...controlProps },
53
- children: /* @__PURE__ */ jsxs("div", {
40
+ children: /* @__PURE__ */ jsx("div", {
54
41
  ...componentProps,
55
42
  ref,
56
- children: [
57
- !controlProps?.inline && hasMarkers && /* @__PURE__ */ jsxs(Fragment, { children: [
58
- slots.FieldLabel,
59
- slots.FieldHint,
60
- slots.FieldError
61
- ] }),
62
- slots.Control && /* @__PURE__ */ jsxs(Fragment, { children: [slots.Control, controlProps?.inline && hasMarkers && /* @__PURE__ */ jsxs(Fragment, { children: [
63
- slots.FieldLabel,
64
- slots.FieldHint,
65
- slots.FieldError
66
- ] })] }),
67
- slots.Children
68
- ]
43
+ children
69
44
  })
70
45
  });
71
46
  };
@@ -0,0 +1,21 @@
1
+ import { TextInputProps } from "../TextInput/TextInput.js";
2
+ import "../TextInput/index.js";
3
+ import { JSX } from "react";
4
+ //#region src/components/PasswordInput/PasswordInput.d.ts
5
+ interface PasswordInputProps extends Omit<TextInputProps, "prefix" | "prefixInline" | "suffix" | "suffixInline" | "type"> {
6
+ /**
7
+ * **(Optional)** Specify if the show/hide toggle button should be shown.
8
+ *
9
+ * Default: false
10
+ */
11
+ showToggle?: boolean;
12
+ }
13
+ declare const PasswordInput: {
14
+ ({ disabled, id, showToggle, size, ...props }: PasswordInputProps): JSX.Element;
15
+ slot: {
16
+ field: boolean;
17
+ name: string;
18
+ };
19
+ };
20
+ //#endregion
21
+ export { PasswordInput, PasswordInput as default, PasswordInputProps };
@@ -0,0 +1,55 @@
1
+ "use client";
2
+ import { Button } from "../Button/Button.js";
3
+ import { useField } from "../Field/Field.context.js";
4
+ import { TextInput } from "../TextInput/TextInput.js";
5
+ import { jsx, jsxs } from "react/jsx-runtime";
6
+ import { useCallback, useState } from "react";
7
+ //#region src/components/PasswordInput/PasswordInput.tsx
8
+ const PasswordInput = ({ disabled, id, showToggle = false, size, ...props }) => {
9
+ const [isVisible, setIsVisible] = useState(false);
10
+ const field = useField();
11
+ const inputId = id ?? field?.id;
12
+ const buttonId = `${inputId}-toggle`;
13
+ const toggleVisibility = useCallback(() => {
14
+ setIsVisible((previous) => !previous);
15
+ }, []);
16
+ const input = /* @__PURE__ */ jsx(TextInput, {
17
+ ...props,
18
+ autoCapitalize: "none",
19
+ disabled,
20
+ id: inputId,
21
+ ref: props.ref,
22
+ size,
23
+ spellCheck: "false",
24
+ type: isVisible ? "text" : "password"
25
+ });
26
+ if (!showToggle) return input;
27
+ return /* @__PURE__ */ jsxs("div", {
28
+ className: "coop-password-input--wrapper",
29
+ children: [
30
+ input,
31
+ /* @__PURE__ */ jsx("div", {
32
+ "aria-live": "polite",
33
+ className: "sr-only",
34
+ children: isVisible ? "Your password is visible" : "Your password is hidden"
35
+ }),
36
+ /* @__PURE__ */ jsx(Button, {
37
+ "aria-controls": inputId,
38
+ "aria-label": isVisible ? "Hide password" : "Show password",
39
+ className: "bg-tint-grey",
40
+ disabled,
41
+ id: buttonId,
42
+ onClick: toggleVisibility,
43
+ size,
44
+ type: "button",
45
+ children: isVisible ? "Hide" : "Show"
46
+ })
47
+ ]
48
+ });
49
+ };
50
+ PasswordInput.slot = {
51
+ field: true,
52
+ name: "PasswordInput"
53
+ };
54
+ //#endregion
55
+ export { PasswordInput, PasswordInput as default };
@@ -0,0 +1,2 @@
1
+ import { PasswordInput, PasswordInputProps } from "./PasswordInput.js";
2
+ export { PasswordInput, PasswordInput as default, PasswordInputProps };
@@ -0,0 +1,5 @@
1
+ import { PasswordInput } from "./PasswordInput.js";
2
+ //#region src/components/PasswordInput/index.ts
3
+ var PasswordInput_default = PasswordInput;
4
+ //#endregion
5
+ export { PasswordInput, PasswordInput_default as default };
@@ -41,6 +41,7 @@ interface SelectOptionProps extends OptionHTMLAttributes<HTMLOptionElement> {
41
41
  declare const Select: {
42
42
  ({ children, className, disabled, error, fit, id, name, ref, size, ...props }: SelectProps): JSX.Element;
43
43
  slot: {
44
+ field: boolean;
44
45
  name: string;
45
46
  };
46
47
  OptionGroup: {
@@ -31,7 +31,10 @@ const SelectOption = ({ children, className, ...props }) => {
31
31
  children
32
32
  });
33
33
  };
34
- Select.slot = { name: "Select" };
34
+ Select.slot = {
35
+ field: true,
36
+ name: "Select"
37
+ };
35
38
  SelectOption.slot = { name: "SelectOption" };
36
39
  SelectOptionGroup.slot = { name: "SelectOptionGroup" };
37
40
  Select.OptionGroup = SelectOptionGroup;
@@ -37,6 +37,7 @@ interface TextInputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "pr
37
37
  declare const TextInput: {
38
38
  ({ "aria-placeholder": ariaPlaceholder, className, disabled, error, fit, id, name, placeholder, prefix, prefixInline, ref, size, suffix, suffixInline, type, ...props }: TextInputProps): JSX.Element;
39
39
  slot: {
40
+ field: boolean;
40
41
  name: string;
41
42
  };
42
43
  };
@@ -47,6 +47,9 @@ const TextInput = ({ "aria-placeholder": ariaPlaceholder, className, disabled, e
47
47
  ref
48
48
  });
49
49
  };
50
- TextInput.slot = { name: "TextInput" };
50
+ TextInput.slot = {
51
+ field: true,
52
+ name: "TextInput"
53
+ };
51
54
  //#endregion
52
55
  export { TextInput, TextInput as default };
@@ -40,6 +40,7 @@ interface TextareaProps extends TextareaHTMLAttributes<HTMLTextAreaElement> {
40
40
  declare const Textarea: {
41
41
  ({ "aria-placeholder": ariaPlaceholder, className, cols, counter, cutoff, disabled, error, fit, id, maxLength, name, onChange: userOnChange, placeholder, ref, rows, size, ...props }: TextareaProps): JSX.Element;
42
42
  slot: {
43
+ field: boolean;
43
44
  name: string;
44
45
  };
45
46
  };
@@ -51,6 +51,9 @@ const Textarea = ({ "aria-placeholder": ariaPlaceholder, className, cols = 30, c
51
51
  })] })]
52
52
  });
53
53
  };
54
- Textarea.slot = { name: "Textarea" };
54
+ Textarea.slot = {
55
+ field: true,
56
+ name: "Textarea"
57
+ };
55
58
  //#endregion
56
59
  export { Textarea, Textarea as default };
package/dist/index.d.ts CHANGED
@@ -12,11 +12,13 @@ import { Fieldset, FieldsetError, FieldsetFields, FieldsetHint, FieldsetLegend,
12
12
  import { Flourish, FlourishProps } from "./components/Flourish/Flourish.js";
13
13
  import { Form, FormProps } from "./components/Form/Form.js";
14
14
  import { LoadingSpinner, LoadingSpinnerProps } from "./components/LoadingSpinner/LoadingSpinner.js";
15
+ import { Logo, LogoProps } from "./components/Logo/Logo.js";
16
+ import { TextInput, TextInputProps } from "./components/TextInput/TextInput.js";
17
+ import { PasswordInput, PasswordInputProps } from "./components/PasswordInput/PasswordInput.js";
15
18
  import { Pill, PillBadge, PillProps } from "./components/Pill/Pill.js";
16
19
  import { Popover, PopoverAnchor, PopoverClose, PopoverContent, PopoverProps, PopoverTrigger } from "./components/Popover/Popover.js";
17
20
  import { Radio, RadioProps } from "./components/Radio/Radio.js";
18
21
  import { RootSVG } from "./components/RootSVG/RootSVG.js";
19
- import { TextInput, TextInputProps } from "./components/TextInput/TextInput.js";
20
22
  import { Searchbox, SearchboxButton, SearchboxInput, SearchboxProps } from "./components/Searchbox/Searchbox.js";
21
23
  import { Select, SelectOption, SelectOptionGroup, SelectProps } from "./components/Select/Select.js";
22
24
  import { Signpost, SignpostProps } from "./components/Signpost/Signpost.js";
@@ -25,4 +27,4 @@ import { Squircle, SquircleProps } from "./components/Squircle/Squircle.js";
25
27
  import { Tag, TagProps } from "./components/Tag/Tag.js";
26
28
  import { Textarea, TextareaProps } from "./components/Textarea/Textarea.js";
27
29
  import { ValidationSummary, ValidationSummaryErrors, ValidationSummaryHeading, ValidationSummaryPrompt, ValidationSummaryProps } from "./components/ValidationSummary/ValidationSummary.js";
28
- export { AlertBanner, AlertBannerHeading, AlertBannerProps, Author, AuthorProps, Button, ButtonProps, Card, CardBadge, CardBody, CardHeading, CardImage, CardLabel, CardProps, Checkbox, CheckboxProps, DatePicker, DatePickerProps, Dialog, DialogBody, DialogHeader, DialogProps, Expandable, ExpandableContent, ExpandableProps, ExpandableSummary, Field, FieldError, FieldHint, FieldLabel, Fieldset, FieldsetError, FieldsetFields, FieldsetHint, FieldsetLegend, FieldsetProps, Flourish, FlourishProps, Form, FormProps, Image, ImageProps, LoadingSpinner, LoadingSpinnerProps, Pill, PillBadge, PillProps, Popover, PopoverAnchor, PopoverClose, PopoverContent, PopoverProps, PopoverTrigger, Radio, RadioProps, RootSVG, Searchbox, SearchboxButton, SearchboxInput, SearchboxProps, Select, SelectOption, SelectOptionGroup, SelectProps, Signpost, SignpostProps, SkipNav, SkipNavProps, Squircle, SquircleProps, Tag, TagProps, TextInput, TextInputProps, Textarea, TextareaProps, ValidationSummary, ValidationSummaryErrors, ValidationSummaryHeading, ValidationSummaryPrompt, ValidationSummaryProps };
30
+ export { AlertBanner, AlertBannerHeading, AlertBannerProps, Author, AuthorProps, Button, ButtonProps, Card, CardBadge, CardBody, CardHeading, CardImage, CardLabel, CardProps, Checkbox, CheckboxProps, DatePicker, DatePickerProps, Dialog, DialogBody, DialogHeader, DialogProps, Expandable, ExpandableContent, ExpandableProps, ExpandableSummary, Field, FieldError, FieldHint, FieldLabel, Fieldset, FieldsetError, FieldsetFields, FieldsetHint, FieldsetLegend, FieldsetProps, Flourish, FlourishProps, Form, FormProps, Image, ImageProps, LoadingSpinner, LoadingSpinnerProps, Logo, LogoProps, PasswordInput, PasswordInputProps, Pill, PillBadge, PillProps, Popover, PopoverAnchor, PopoverClose, PopoverContent, PopoverProps, PopoverTrigger, Radio, RadioProps, RootSVG, Searchbox, SearchboxButton, SearchboxInput, SearchboxProps, Select, SelectOption, SelectOptionGroup, SelectProps, Signpost, SignpostProps, SkipNav, SkipNavProps, Squircle, SquircleProps, Tag, TagProps, TextInput, TextInputProps, Textarea, TextareaProps, ValidationSummary, ValidationSummaryErrors, ValidationSummaryHeading, ValidationSummaryPrompt, ValidationSummaryProps };
package/dist/index.js CHANGED
@@ -14,6 +14,8 @@ import { Field, FieldError, FieldHint, FieldLabel } from "./components/Field/Fie
14
14
  import { Fieldset, FieldsetError, FieldsetFields, FieldsetHint, FieldsetLegend } from "./components/Fieldset/Fieldset.js";
15
15
  import { Flourish } from "./components/Flourish/Flourish.js";
16
16
  import { Form } from "./components/Form/Form.js";
17
+ import { Logo } from "./components/Logo/Logo.js";
18
+ import { PasswordInput } from "./components/PasswordInput/PasswordInput.js";
17
19
  import { Pill, PillBadge } from "./components/Pill/Pill.js";
18
20
  import { Radio } from "./components/Radio/Radio.js";
19
21
  import { RootSVG } from "./components/RootSVG/RootSVG.js";
@@ -25,4 +27,4 @@ import { Squircle } from "./components/Squircle/Squircle.js";
25
27
  import { Tag } from "./components/Tag/Tag.js";
26
28
  import { Textarea } from "./components/Textarea/Textarea.js";
27
29
  import { ValidationSummary, ValidationSummaryErrors, ValidationSummaryHeading, ValidationSummaryPrompt } from "./components/ValidationSummary/ValidationSummary.js";
28
- export { AlertBanner, AlertBannerHeading, Author, Button, Card, CardBadge, CardBody, CardHeading, CardImage, CardLabel, Checkbox, DatePicker, Dialog, DialogBody, DialogHeader, Expandable, ExpandableContent, ExpandableSummary, Field, FieldError, FieldHint, FieldLabel, Fieldset, FieldsetError, FieldsetFields, FieldsetHint, FieldsetLegend, Flourish, Form, Image, LoadingSpinner, Pill, PillBadge, Popover, PopoverAnchor, PopoverClose, PopoverContent, PopoverTrigger, Radio, RootSVG, Searchbox, SearchboxButton, SearchboxInput, Select, SelectOption, SelectOptionGroup, Signpost, SkipNav, Squircle, Tag, TextInput, Textarea, ValidationSummary, ValidationSummaryErrors, ValidationSummaryHeading, ValidationSummaryPrompt };
30
+ export { AlertBanner, AlertBannerHeading, Author, Button, Card, CardBadge, CardBody, CardHeading, CardImage, CardLabel, Checkbox, DatePicker, Dialog, DialogBody, DialogHeader, Expandable, ExpandableContent, ExpandableSummary, Field, FieldError, FieldHint, FieldLabel, Fieldset, FieldsetError, FieldsetFields, FieldsetHint, FieldsetLegend, Flourish, Form, Image, LoadingSpinner, Logo, PasswordInput, Pill, PillBadge, Popover, PopoverAnchor, PopoverClose, PopoverContent, PopoverTrigger, Radio, RootSVG, Searchbox, SearchboxButton, SearchboxInput, Select, SelectOption, SelectOptionGroup, Signpost, SkipNav, Squircle, Tag, TextInput, Textarea, ValidationSummary, ValidationSummaryErrors, ValidationSummaryHeading, ValidationSummaryPrompt };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@coopdigital/react",
3
3
  "type": "module",
4
- "version": "1.17.1",
4
+ "version": "1.18.0",
5
5
  "private": false,
6
6
  "publishConfig": {
7
7
  "access": "public"
@@ -63,12 +63,12 @@
63
63
  "license": "MIT",
64
64
  "description": "React components for the Experience Library design system",
65
65
  "devDependencies": {
66
- "@axe-core/playwright": "^4.12.1",
66
+ "@axe-core/playwright": "^4.13.0",
67
67
  "@playwright/test": "^1.62.1",
68
- "@storybook/addon-a11y": "^10.5.7",
69
- "@storybook/addon-docs": "^10.5.7",
70
- "@storybook/addon-onboarding": "^10.5.7",
71
- "@storybook/react-vite": "^10.5.7",
68
+ "@storybook/addon-a11y": "^10.5.8",
69
+ "@storybook/addon-docs": "^10.5.8",
70
+ "@storybook/addon-onboarding": "^10.5.8",
71
+ "@storybook/react-vite": "^10.5.8",
72
72
  "@testing-library/jest-dom": "^7.0.1",
73
73
  "@testing-library/react": "^16.3.2",
74
74
  "@types/react": "^19.2.18",
@@ -78,7 +78,7 @@
78
78
  "react-dom": "^19.2.8",
79
79
  "resize-observer-polyfill": "^1.5.1",
80
80
  "serve": "^14.2.6",
81
- "storybook": "^10.5.7",
81
+ "storybook": "^10.5.8",
82
82
  "storybook-addon-tag-badges": "^3.1.0"
83
83
  },
84
84
  "peerDependencies": {
@@ -102,12 +102,12 @@
102
102
  "storybook": "$storybook"
103
103
  },
104
104
  "dependencies": {
105
- "@coopdigital/styles": "^1.12.3",
105
+ "@coopdigital/styles": "^1.13.0",
106
106
  "@radix-ui/react-popover": "^1.1.23",
107
107
  "clsx": "^2.1.1",
108
108
  "date-fns": "^4.4.0",
109
109
  "merge-refs": "^2.0.0",
110
110
  "react-day-picker": "^10.0.1"
111
111
  },
112
- "gitHead": "797e4ee819a4ec42723fa1537cce1369763b6eb7"
112
+ "gitHead": "f8498869d526396c1119c13f1d5afbb083344547"
113
113
  }
@@ -9,7 +9,6 @@ export interface FieldConfig {
9
9
  }
10
10
 
11
11
  export interface FieldContextValue {
12
- // register: ({ control, inline, suffix, uid }: FieldConfig) => void
13
12
  disabled?: boolean
14
13
  id?: string
15
14
  inline?: boolean
@@ -5,7 +5,6 @@ import clsx from "clsx"
5
5
  import React from "react"
6
6
 
7
7
  import { useId } from "../../hooks/useId"
8
- import { useSlots } from "../../hooks/useSlots"
9
8
  import { SlotConfig } from "../../types"
10
9
  import { getSlotProp } from "../../utils"
11
10
  import { Error as BaseError, type ErrorProps } from "../FieldMarkers/Error"
@@ -13,14 +12,6 @@ import { Hint as BaseHint, type HintProps } from "../FieldMarkers/Hint"
13
12
  import { Label as BaseLabel, type LabelProps } from "../FieldMarkers/Label"
14
13
  import { FieldConfig, FieldContext } from "./Field.context"
15
14
 
16
- const componentSlots = {
17
- Children: null,
18
- Control: null,
19
- FieldError: null,
20
- FieldHint: null,
21
- FieldLabel: null,
22
- }
23
-
24
15
  interface FieldProps extends HTMLAttributes<HTMLDivElement> {
25
16
  /** **(Optional)** Specify whether the Field should render inside a box. */
26
17
  boxed?: boolean
@@ -47,9 +38,6 @@ const Field = ({
47
38
  ref,
48
39
  ...props
49
40
  }: FieldProps): JSX.Element => {
50
- const slots = { ...useSlots(componentSlots, children) }
51
- const hasMarkers = !!(slots.FieldLabel ?? slots.FieldError ?? slots.FieldHint)
52
-
53
41
  const internalId = useId()
54
42
 
55
43
  const controlProps: FieldConfig = {
@@ -57,30 +45,19 @@ const Field = ({
57
45
  id: internalId,
58
46
  }
59
47
 
60
- if (Array.isArray(slots.Children)) {
61
- slots.Children = slots.Children.filter((child: React.ReactNode) => {
62
- //resolveComponentName(child)
63
-
64
- if (child && typeof child === "object" && "type" in child) {
65
- if (typeof child?.type === "function" && "slot" in child.type) {
66
- const { slot } = child.type as { slot: SlotConfig }
67
-
68
- slots.Control = child
69
-
70
- if (slots.Control.props) {
71
- controlProps.inline = slot.inline ?? false
72
- controlProps.disabled = getSlotProp<boolean>(slots.Control, "disabled") ?? false
73
- controlProps.id = getSlotProp<string>(slots.Control, "id") ?? controlProps.id
74
- controlProps.suffix = slot.suffix ?? undefined
75
- }
76
-
77
- return false
48
+ Array.from([children].flat()).forEach((child: React.ReactNode) => {
49
+ if (child && typeof child === "object" && "type" in child) {
50
+ if (typeof child?.type === "function" && "slot" in child.type) {
51
+ const { slot } = child.type as { slot: SlotConfig }
52
+ if (slot.field && child.props) {
53
+ controlProps.inline = slot.inline ?? false
54
+ controlProps.disabled = getSlotProp<boolean>(child, "disabled") ?? false
55
+ controlProps.id = getSlotProp<string>(child, "id") ?? controlProps.id
56
+ controlProps.suffix = slot.suffix ?? undefined
78
57
  }
79
58
  }
80
-
81
- return true
82
- })
83
- }
59
+ }
60
+ })
84
61
 
85
62
  const componentProps = {
86
63
  ...props,
@@ -94,26 +71,7 @@ const Field = ({
94
71
  return (
95
72
  <FieldContext.Provider value={{ ...controlProps }}>
96
73
  <div {...componentProps} ref={ref}>
97
- {!controlProps?.inline && hasMarkers && (
98
- <>
99
- {slots.FieldLabel}
100
- {slots.FieldHint}
101
- {slots.FieldError}
102
- </>
103
- )}
104
- {slots.Control && (
105
- <>
106
- {slots.Control}
107
- {controlProps?.inline && hasMarkers && (
108
- <>
109
- {slots.FieldLabel}
110
- {slots.FieldHint}
111
- {slots.FieldError}
112
- </>
113
- )}
114
- </>
115
- )}
116
- {slots.Children}
74
+ {children}
117
75
  </div>
118
76
  </FieldContext.Provider>
119
77
  )
@@ -0,0 +1,80 @@
1
+ "use client"
2
+ import type { JSX } from "react"
3
+
4
+ import { useCallback, useState } from "react"
5
+
6
+ import { Button } from "../Button"
7
+ import { useField } from "../Field/Field.context"
8
+ import { TextInput, type TextInputProps } from "../TextInput"
9
+
10
+ export interface PasswordInputProps extends Omit<
11
+ TextInputProps,
12
+ "prefix" | "prefixInline" | "suffix" | "suffixInline" | "type"
13
+ > {
14
+ /**
15
+ * **(Optional)** Specify if the show/hide toggle button should be shown.
16
+ *
17
+ * Default: false
18
+ */
19
+ showToggle?: boolean
20
+ }
21
+
22
+ const PasswordInput = ({
23
+ disabled,
24
+ id,
25
+ showToggle = false,
26
+ size,
27
+ ...props
28
+ }: PasswordInputProps): JSX.Element => {
29
+ const [isVisible, setIsVisible] = useState(false)
30
+ const field = useField()
31
+ const inputId = id ?? field?.id
32
+ const buttonId = `${inputId}-toggle`
33
+
34
+ const toggleVisibility = useCallback(() => {
35
+ setIsVisible((previous) => !previous)
36
+ }, [])
37
+
38
+ const input = (
39
+ <TextInput
40
+ {...props}
41
+ autoCapitalize="none"
42
+ disabled={disabled}
43
+ id={inputId}
44
+ ref={props.ref}
45
+ size={size}
46
+ spellCheck="false"
47
+ type={isVisible ? "text" : "password"}
48
+ />
49
+ )
50
+
51
+ if (!showToggle) {
52
+ return input
53
+ }
54
+
55
+ return (
56
+ <div className="coop-password-input--wrapper">
57
+ {input}
58
+ <div aria-live="polite" className="sr-only">
59
+ {isVisible ? "Your password is visible" : "Your password is hidden"}
60
+ </div>
61
+ <Button
62
+ aria-controls={inputId}
63
+ aria-label={isVisible ? "Hide password" : "Show password"}
64
+ className="bg-tint-grey"
65
+ disabled={disabled}
66
+ id={buttonId}
67
+ onClick={toggleVisibility}
68
+ size={size}
69
+ type="button" //type button prevents it from submitting the form accidentally when used with a keyboard.
70
+ >
71
+ {isVisible ? "Hide" : "Show"}
72
+ </Button>
73
+ </div>
74
+ )
75
+ }
76
+
77
+ PasswordInput.slot = { field: true, name: "PasswordInput" }
78
+
79
+ export default PasswordInput
80
+ export { PasswordInput }
@@ -0,0 +1,5 @@
1
+ import PasswordInput from "./PasswordInput"
2
+
3
+ export default PasswordInput
4
+ export { PasswordInput }
5
+ export * from "./PasswordInput"
@@ -97,7 +97,7 @@ const SelectOption = ({ children, className, ...props }: SelectOptionProps): JSX
97
97
  )
98
98
  }
99
99
 
100
- Select.slot = { name: "Select" }
100
+ Select.slot = { field: true, name: "Select" }
101
101
  SelectOption.slot = { name: "SelectOption" }
102
102
  SelectOptionGroup.slot = { name: "SelectOptionGroup" }
103
103
 
@@ -97,8 +97,6 @@ const TextInput = ({
97
97
  ? { className: "coop-text-input--suffix", "data-inline": suffixInline || undefined }
98
98
  : {}
99
99
 
100
- //const formItemProps = { "aria-disabled": disabled ? true : undefined }
101
-
102
100
  return prefix || suffix ? (
103
101
  <div className="coop-text-input-wrapper">
104
102
  {prefix && <span {...prefixProps}>{prefix}</span>}
@@ -110,7 +108,7 @@ const TextInput = ({
110
108
  )
111
109
  }
112
110
 
113
- TextInput.slot = { name: "TextInput" }
111
+ TextInput.slot = { field: true, name: "TextInput" }
114
112
 
115
113
  export default TextInput
116
114
  export { TextInput }
@@ -120,7 +120,7 @@ const Textarea = ({
120
120
  )
121
121
  }
122
122
 
123
- Textarea.slot = { name: "Textarea" }
123
+ Textarea.slot = { field: true, name: "Textarea" }
124
124
 
125
125
  export default Textarea
126
126
  export { Textarea }
package/src/index.ts CHANGED
@@ -12,6 +12,8 @@ export * from "./components/Flourish/Flourish"
12
12
  export * from "./components/Form/Form"
13
13
  export * from "./components/Image/Image"
14
14
  export * from "./components/LoadingSpinner/LoadingSpinner"
15
+ export * from "./components/Logo/Logo"
16
+ export * from "./components/PasswordInput/PasswordInput"
15
17
  export * from "./components/Pill/Pill"
16
18
  export * from "./components/Popover/Popover"
17
19
  export * from "./components/Radio/Radio"