@seed-design/react 1.0.7 → 1.1.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.
Files changed (155) hide show
  1. package/lib/components/ActionButton/ActionButton.cjs +7 -1
  2. package/lib/components/ActionButton/ActionButton.d.ts +7 -1
  3. package/lib/components/ActionButton/ActionButton.d.ts.map +1 -1
  4. package/lib/components/ActionButton/ActionButton.js +7 -1
  5. package/lib/components/BottomSheet/BottomSheet.cjs +14 -18
  6. package/lib/components/BottomSheet/BottomSheet.d.ts +12 -19
  7. package/lib/components/BottomSheet/BottomSheet.d.ts.map +1 -1
  8. package/lib/components/BottomSheet/BottomSheet.js +14 -18
  9. package/lib/components/BottomSheet/BottomSheet.namespace.cjs +2 -0
  10. package/lib/components/BottomSheet/BottomSheet.namespace.d.ts +1 -0
  11. package/lib/components/BottomSheet/BottomSheet.namespace.d.ts.map +1 -1
  12. package/lib/components/BottomSheet/BottomSheet.namespace.js +1 -0
  13. package/lib/components/BottomSheetHandle/BottomSheetHandle.cjs +20 -0
  14. package/lib/components/BottomSheetHandle/BottomSheetHandle.d.ts +6 -0
  15. package/lib/components/BottomSheetHandle/BottomSheetHandle.d.ts.map +1 -0
  16. package/lib/components/BottomSheetHandle/BottomSheetHandle.js +16 -0
  17. package/lib/components/BottomSheetHandle/index.cjs +9 -0
  18. package/lib/components/BottomSheetHandle/index.d.ts +2 -0
  19. package/lib/components/BottomSheetHandle/index.d.ts.map +1 -0
  20. package/lib/components/BottomSheetHandle/index.js +1 -0
  21. package/lib/components/Chip/Chip.cjs +4 -3
  22. package/lib/components/Chip/Chip.d.ts.map +1 -1
  23. package/lib/components/Chip/Chip.js +4 -3
  24. package/lib/components/Field/Field.cjs +108 -0
  25. package/lib/components/Field/Field.d.ts +41 -0
  26. package/lib/components/Field/Field.d.ts.map +1 -0
  27. package/lib/components/Field/Field.js +96 -0
  28. package/lib/components/Field/Field.namespace.cjs +17 -0
  29. package/lib/components/Field/Field.namespace.d.ts +2 -0
  30. package/lib/components/Field/Field.namespace.d.ts.map +1 -0
  31. package/lib/components/Field/Field.namespace.js +1 -0
  32. package/lib/components/Field/index.cjs +19 -0
  33. package/lib/components/Field/index.d.ts +3 -0
  34. package/lib/components/Field/index.d.ts.map +1 -0
  35. package/lib/components/Field/index.js +3 -0
  36. package/lib/components/FieldButton/FieldButton.cjs +201 -0
  37. package/lib/components/FieldButton/FieldButton.d.ts +61 -0
  38. package/lib/components/FieldButton/FieldButton.d.ts.map +1 -0
  39. package/lib/components/FieldButton/FieldButton.js +161 -0
  40. package/lib/components/FieldButton/FieldButton.namespace.cjs +26 -0
  41. package/lib/components/FieldButton/FieldButton.namespace.d.ts +2 -0
  42. package/lib/components/FieldButton/FieldButton.namespace.d.ts.map +1 -0
  43. package/lib/components/FieldButton/FieldButton.namespace.js +1 -0
  44. package/lib/components/FieldButton/index.cjs +28 -0
  45. package/lib/components/FieldButton/index.d.ts +3 -0
  46. package/lib/components/FieldButton/index.d.ts.map +1 -0
  47. package/lib/components/FieldButton/index.js +3 -0
  48. package/lib/components/HelpBubble/HelpBubble.d.ts +1 -1
  49. package/lib/components/List/List.cjs +5 -4
  50. package/lib/components/List/List.d.ts.map +1 -1
  51. package/lib/components/List/List.js +5 -4
  52. package/lib/components/PageBanner/PageBanner.cjs +8 -3
  53. package/lib/components/PageBanner/PageBanner.d.ts +5 -2
  54. package/lib/components/PageBanner/PageBanner.d.ts.map +1 -1
  55. package/lib/components/PageBanner/PageBanner.js +7 -3
  56. package/lib/components/PageBanner/PageBanner.namespace.cjs +2 -1
  57. package/lib/components/PageBanner/PageBanner.namespace.d.ts +1 -1
  58. package/lib/components/PageBanner/PageBanner.namespace.d.ts.map +1 -1
  59. package/lib/components/PageBanner/PageBanner.namespace.js +1 -1
  60. package/lib/components/PageBanner/index.cjs +2 -1
  61. package/lib/components/PageBanner/index.d.ts +1 -1
  62. package/lib/components/PageBanner/index.d.ts.map +1 -1
  63. package/lib/components/PageBanner/index.js +1 -1
  64. package/lib/components/Slider/Slider.cjs +110 -0
  65. package/lib/components/Slider/Slider.d.ts +51 -0
  66. package/lib/components/Slider/Slider.d.ts.map +1 -0
  67. package/lib/components/Slider/Slider.js +94 -0
  68. package/lib/components/Slider/Slider.namespace.cjs +21 -0
  69. package/lib/components/Slider/Slider.namespace.d.ts +2 -0
  70. package/lib/components/Slider/Slider.namespace.d.ts.map +1 -0
  71. package/lib/components/Slider/Slider.namespace.js +1 -0
  72. package/lib/components/Slider/index.cjs +23 -0
  73. package/lib/components/Slider/index.d.ts +3 -0
  74. package/lib/components/Slider/index.d.ts.map +1 -0
  75. package/lib/components/Slider/index.js +3 -0
  76. package/lib/components/TextField/TextField.cjs +54 -74
  77. package/lib/components/TextField/TextField.d.ts +2 -35
  78. package/lib/components/TextField/TextField.d.ts.map +1 -1
  79. package/lib/components/TextField/TextField.js +56 -65
  80. package/lib/components/TextField/TextField.namespace.cjs +0 -11
  81. package/lib/components/TextField/TextField.namespace.d.ts +1 -1
  82. package/lib/components/TextField/TextField.namespace.d.ts.map +1 -1
  83. package/lib/components/TextField/TextField.namespace.js +1 -1
  84. package/lib/components/TextField/TextField.test.d.ts +1 -0
  85. package/lib/components/TextField/TextField.test.d.ts.map +1 -0
  86. package/lib/components/TextField/index.cjs +2 -11
  87. package/lib/components/TextField/index.d.ts +2 -1
  88. package/lib/components/TextField/index.d.ts.map +1 -1
  89. package/lib/components/TextField/index.js +2 -1
  90. package/lib/components/TextField/memoize.cjs +18 -0
  91. package/lib/components/TextField/memoize.d.ts +2 -0
  92. package/lib/components/TextField/memoize.d.ts.map +1 -0
  93. package/lib/components/TextField/memoize.js +14 -0
  94. package/lib/components/TextField/useTextFieldWithGraphemes.cjs +52 -0
  95. package/lib/components/TextField/useTextFieldWithGraphemes.d.ts +23 -0
  96. package/lib/components/TextField/useTextFieldWithGraphemes.d.ts.map +1 -0
  97. package/lib/components/TextField/useTextFieldWithGraphemes.js +48 -0
  98. package/lib/components/TextField/useTextFieldWithGraphemes.test.d.ts +1 -0
  99. package/lib/components/TextField/useTextFieldWithGraphemes.test.d.ts.map +1 -0
  100. package/lib/components/index.cjs +53 -12
  101. package/lib/components/index.d.ts +3 -0
  102. package/lib/components/index.d.ts.map +1 -1
  103. package/lib/components/index.js +12 -2
  104. package/lib/index.cjs +53 -12
  105. package/lib/index.js +12 -2
  106. package/lib/node_modules/unicode-segmenter/_grapheme_data.cjs +23 -0
  107. package/lib/node_modules/unicode-segmenter/_grapheme_data.js +19 -0
  108. package/lib/node_modules/unicode-segmenter/_incb_data.cjs +29 -0
  109. package/lib/node_modules/unicode-segmenter/_incb_data.js +25 -0
  110. package/lib/node_modules/unicode-segmenter/core.cjs +83 -0
  111. package/lib/node_modules/unicode-segmenter/core.js +78 -0
  112. package/lib/node_modules/unicode-segmenter/grapheme.cjs +312 -0
  113. package/lib/node_modules/unicode-segmenter/grapheme.js +307 -0
  114. package/lib/primitive.cjs +7 -0
  115. package/lib/primitive.d.ts +1 -0
  116. package/lib/primitive.d.ts.map +1 -1
  117. package/lib/primitive.js +1 -0
  118. package/lib/utils/createWithStateProps.cjs +8 -4
  119. package/lib/utils/createWithStateProps.d.ts +6 -3
  120. package/lib/utils/createWithStateProps.d.ts.map +1 -1
  121. package/lib/utils/createWithStateProps.js +8 -4
  122. package/package.json +9 -6
  123. package/src/components/ActionButton/ActionButton.tsx +21 -2
  124. package/src/components/BottomSheet/BottomSheet.namespace.ts +5 -0
  125. package/src/components/BottomSheet/BottomSheet.tsx +24 -38
  126. package/src/components/BottomSheetHandle/BottomSheetHandle.tsx +22 -0
  127. package/src/components/BottomSheetHandle/index.ts +1 -0
  128. package/src/components/Chip/Chip.tsx +4 -3
  129. package/src/components/Field/Field.namespace.ts +19 -0
  130. package/src/components/Field/Field.tsx +136 -0
  131. package/src/components/Field/index.ts +21 -0
  132. package/src/components/FieldButton/FieldButton.namespace.ts +38 -0
  133. package/src/components/FieldButton/FieldButton.tsx +249 -0
  134. package/src/components/FieldButton/index.ts +40 -0
  135. package/src/components/HelpBubble/HelpBubble.tsx +1 -1
  136. package/src/components/List/List.tsx +5 -4
  137. package/src/components/PageBanner/PageBanner.namespace.ts +4 -2
  138. package/src/components/PageBanner/PageBanner.tsx +10 -3
  139. package/src/components/PageBanner/index.ts +4 -2
  140. package/src/components/Slider/Slider.namespace.ts +28 -0
  141. package/src/components/Slider/Slider.tsx +154 -0
  142. package/src/components/Slider/index.ts +30 -0
  143. package/src/components/TextField/TextField.namespace.ts +2 -24
  144. package/src/components/TextField/TextField.test.tsx +260 -0
  145. package/src/components/TextField/TextField.tsx +67 -143
  146. package/src/components/TextField/index.ts +7 -24
  147. package/src/components/TextField/memoize.ts +14 -0
  148. package/src/components/TextField/useTextFieldWithGraphemes.test.tsx +301 -0
  149. package/src/components/TextField/useTextFieldWithGraphemes.ts +65 -0
  150. package/src/components/index.ts +3 -0
  151. package/src/primitive.ts +1 -0
  152. package/src/utils/createWithStateProps.tsx +14 -9
  153. package/lib/components/List/ListHeader.namespace.d.ts +0 -2
  154. package/lib/components/List/ListHeader.namespace.d.ts.map +0 -1
  155. package/src/components/List/ListHeader.namespace.ts +0 -0
@@ -0,0 +1,260 @@
1
+ import "@testing-library/jest-dom/vitest";
2
+ import { cleanup, render } from "@testing-library/react";
3
+ import userEvent from "@testing-library/user-event";
4
+ import { afterEach, describe, expect, it, vi } from "vitest";
5
+ import type { ReactElement } from "react";
6
+ import React from "react";
7
+
8
+ import { Field } from "@seed-design/react-field";
9
+ import {
10
+ TextFieldRoot,
11
+ TextFieldInput,
12
+ TextFieldTextarea,
13
+ TextFieldPrefixIcon,
14
+ TextFieldPrefixText,
15
+ TextFieldSuffixIcon,
16
+ TextFieldSuffixText,
17
+ } from "./TextField";
18
+
19
+ afterEach(cleanup);
20
+
21
+ function setUp(jsx: ReactElement) {
22
+ return {
23
+ user: userEvent.setup(),
24
+ ...render(jsx),
25
+ };
26
+ }
27
+
28
+ describe("TextField", () => {
29
+ describe("props merging", () => {
30
+ describe("TextFieldInput", () => {
31
+ it("should merge props from TextFieldRoot", () => {
32
+ const { getByRole } = setUp(
33
+ <TextFieldRoot defaultValue="initial">
34
+ <TextFieldInput placeholder="Placeholder" />
35
+ </TextFieldRoot>,
36
+ );
37
+
38
+ const input = getByRole("textbox");
39
+ expect(input).toHaveValue("initial");
40
+ expect(input).toHaveAttribute("placeholder", "Placeholder");
41
+ });
42
+
43
+ it("should merge props from Field context", () => {
44
+ const { getByRole } = setUp(
45
+ <Field.Root required invalid disabled name="test-field">
46
+ <TextFieldRoot>
47
+ <TextFieldInput />
48
+ </TextFieldRoot>
49
+ </Field.Root>,
50
+ );
51
+
52
+ const input = getByRole("textbox");
53
+ expect(input).toHaveAttribute("name", "test-field");
54
+ expect(input).toHaveAttribute("aria-required", "true");
55
+ expect(input).toHaveAttribute("aria-invalid", "true");
56
+ expect(input).toBeDisabled();
57
+ });
58
+
59
+ it("should prioritize direct props over context props", () => {
60
+ const { getByRole } = setUp(
61
+ <Field.Root name="field-name">
62
+ <TextFieldRoot>
63
+ <TextFieldInput name="direct-name" />
64
+ </TextFieldRoot>
65
+ </Field.Root>,
66
+ );
67
+
68
+ const input = getByRole("textbox");
69
+ expect(input).toHaveAttribute("name", "direct-name");
70
+ });
71
+
72
+ it("should merge data attributes", () => {
73
+ const { getByRole } = setUp(
74
+ <Field.Root disabled readOnly invalid>
75
+ <TextFieldRoot>
76
+ <TextFieldInput data-custom="value" />
77
+ </TextFieldRoot>
78
+ </Field.Root>,
79
+ );
80
+
81
+ const input = getByRole("textbox");
82
+ expect(input).toHaveAttribute("data-disabled", "");
83
+ expect(input).toHaveAttribute("data-readonly", "");
84
+ expect(input).toHaveAttribute("data-invalid", "");
85
+ expect(input).toHaveAttribute("data-custom", "value");
86
+ });
87
+
88
+ it("should handle uncontrolled modes", async () => {
89
+ const handleChange = vi.fn();
90
+ const { getByRole, user } = setUp(
91
+ <TextFieldRoot onValueChange={handleChange}>
92
+ <TextFieldInput />
93
+ </TextFieldRoot>,
94
+ );
95
+
96
+ const input = getByRole("textbox");
97
+
98
+ await user.type(input, "test");
99
+ expect(handleChange).toHaveBeenCalledWith("test");
100
+ });
101
+
102
+ it("should handle controlled modes", async () => {
103
+ function Controlled() {
104
+ const [value, setValue] = React.useState("");
105
+
106
+ return (
107
+ <TextFieldRoot value={value} onValueChange={setValue}>
108
+ <TextFieldInput />
109
+ </TextFieldRoot>
110
+ );
111
+ }
112
+
113
+ const { getByRole, user } = setUp(<Controlled />);
114
+
115
+ const input = getByRole("textbox");
116
+
117
+ await user.type(input, "test");
118
+ expect(input).toHaveValue("test");
119
+ });
120
+ });
121
+
122
+ describe("TextFieldTextarea", () => {
123
+ it("should merge props from TextFieldRoot", () => {
124
+ const { getByRole } = setUp(
125
+ <TextFieldRoot defaultValue="initial">
126
+ <TextFieldTextarea placeholder="Placeholder" />
127
+ </TextFieldRoot>,
128
+ );
129
+
130
+ const input = getByRole("textbox");
131
+ expect(input).toHaveValue("initial");
132
+ expect(input).toHaveAttribute("placeholder", "Placeholder");
133
+ });
134
+
135
+ it("should merge props from Field context", () => {
136
+ const { getByRole } = setUp(
137
+ <Field.Root required invalid disabled name="test-field">
138
+ <TextFieldRoot>
139
+ <TextFieldTextarea />
140
+ </TextFieldRoot>
141
+ </Field.Root>,
142
+ );
143
+
144
+ const input = getByRole("textbox");
145
+ expect(input).toHaveAttribute("name", "test-field");
146
+ expect(input).toHaveAttribute("aria-required", "true");
147
+ expect(input).toHaveAttribute("aria-invalid", "true");
148
+ expect(input).toBeDisabled();
149
+ });
150
+
151
+ it("should prioritize direct props over context props", () => {
152
+ const { getByRole } = setUp(
153
+ <Field.Root disabled name="field-name">
154
+ <TextFieldRoot>
155
+ <TextFieldTextarea disabled={false} name="direct-name" />
156
+ </TextFieldRoot>
157
+ </Field.Root>,
158
+ );
159
+
160
+ const input = getByRole("textbox");
161
+ expect(input).toHaveAttribute("name", "direct-name");
162
+ expect(input).not.toBeDisabled();
163
+ });
164
+
165
+ it("should merge data attributes", () => {
166
+ const { getByRole } = setUp(
167
+ <Field.Root disabled readOnly invalid>
168
+ <TextFieldRoot>
169
+ <TextFieldTextarea data-custom="value" />
170
+ </TextFieldRoot>
171
+ </Field.Root>,
172
+ );
173
+
174
+ const input = getByRole("textbox");
175
+ expect(input).toHaveAttribute("data-disabled", "");
176
+ expect(input).toHaveAttribute("data-readonly", "");
177
+ expect(input).toHaveAttribute("data-invalid", "");
178
+ expect(input).toHaveAttribute("data-custom", "value");
179
+ });
180
+
181
+ it("should handle uncontrolled modes", async () => {
182
+ const handleChange = vi.fn();
183
+ const { getByRole, user } = setUp(
184
+ <TextFieldRoot onValueChange={handleChange}>
185
+ <TextFieldTextarea />
186
+ </TextFieldRoot>,
187
+ );
188
+
189
+ const input = getByRole("textbox");
190
+
191
+ await user.type(input, "test");
192
+ expect(handleChange).toHaveBeenCalledWith("test");
193
+ });
194
+
195
+ it("should handle controlled modes", async () => {
196
+ function Controlled() {
197
+ const [value, setValue] = React.useState("");
198
+
199
+ return (
200
+ <TextFieldRoot value={value} onValueChange={setValue}>
201
+ <TextFieldTextarea />
202
+ </TextFieldRoot>
203
+ );
204
+ }
205
+
206
+ const { getByRole, user } = setUp(<Controlled />);
207
+
208
+ const input = getByRole("textbox");
209
+
210
+ await user.type(input, "test");
211
+ expect(input).toHaveValue("test");
212
+ });
213
+ });
214
+
215
+ describe("Prefix and Suffix components", () => {
216
+ it("should apply state props to affix elements", () => {
217
+ const { getByTestId } = setUp(
218
+ <TextFieldRoot disabled invalid readOnly>
219
+ <TextFieldPrefixText>Prefix</TextFieldPrefixText>
220
+ <TextFieldPrefixIcon svg={<svg data-testid="prefix-icon" />} />
221
+ <TextFieldInput />
222
+ <TextFieldSuffixIcon svg={<svg data-testid="suffix-icon" />} />
223
+ <TextFieldSuffixText>Suffix</TextFieldSuffixText>
224
+ </TextFieldRoot>,
225
+ );
226
+
227
+ const prefixIcon = getByTestId("prefix-icon");
228
+ expect(prefixIcon).toHaveAttribute("data-disabled", "");
229
+ expect(prefixIcon).toHaveAttribute("data-invalid", "");
230
+ expect(prefixIcon).toHaveAttribute("data-readonly", "");
231
+
232
+ const suffixIcon = getByTestId("suffix-icon");
233
+ expect(suffixIcon).toHaveAttribute("data-disabled", "");
234
+ expect(suffixIcon).toHaveAttribute("data-invalid", "");
235
+ expect(suffixIcon).toHaveAttribute("data-readonly", "");
236
+ });
237
+ });
238
+
239
+ describe("Complex prop merging scenarios", () => {
240
+ // this is possible, but you shouldn't be doing this
241
+ it("should handle nested Field and TextField contexts", () => {
242
+ const { getByRole } = setUp(
243
+ <Field.Root required name="field">
244
+ <Field.Label>Label</Field.Label>
245
+ <TextFieldRoot invalid>
246
+ <TextFieldInput />
247
+ </TextFieldRoot>
248
+ <Field.Description>Description</Field.Description>
249
+ </Field.Root>,
250
+ );
251
+
252
+ const input = getByRole("textbox");
253
+ expect(input).toHaveAttribute("name", "field");
254
+ expect(input).toHaveAttribute("aria-required", "true");
255
+ expect(input).toHaveAttribute("aria-invalid", "true");
256
+ expect(input).toHaveAttribute("aria-describedby");
257
+ });
258
+ });
259
+ });
260
+ });
@@ -1,7 +1,8 @@
1
1
  import { useLayoutEffect } from "@radix-ui/react-use-layout-effect";
2
2
  import { Primitive, type PrimitiveProps } from "@seed-design/react-primitive";
3
3
  import { TextField, useTextFieldContext } from "@seed-design/react-text-field";
4
- import { textField, type TextFieldVariantProps } from "@seed-design/css/recipes/text-field";
4
+ import { useFieldContext } from "@seed-design/react-field";
5
+ import { textInput, type TextInputVariantProps } from "@seed-design/css/recipes/text-input";
5
6
  import clsx from "clsx";
6
7
  import type * as React from "react";
7
8
  import { forwardRef, useCallback, useRef } from "react";
@@ -9,74 +10,27 @@ import { createSlotRecipeContext } from "../../utils/createSlotRecipeContext";
9
10
  import { createWithStateProps } from "../../utils/createWithStateProps";
10
11
  import { InternalIcon, type InternalIconProps } from "../private/Icon";
11
12
  import { composeRefs } from "@radix-ui/react-compose-refs";
13
+ import { mergeProps } from "@seed-design/dom-utils";
12
14
 
13
- const { withProvider, withContext, useClassNames } = createSlotRecipeContext(textField);
14
- const withStateProps = createWithStateProps([useTextFieldContext]);
15
+ const { withProvider, withContext, useClassNames } = createSlotRecipeContext(textInput);
16
+
17
+ const withFieldStateProps = createWithStateProps([{ useContext: useFieldContext, strict: false }]);
18
+ const withStateProps = createWithStateProps([
19
+ useTextFieldContext,
20
+ { useContext: useFieldContext, strict: false },
21
+ ]);
15
22
 
16
23
  ////////////////////////////////////////////////////////////////////////////////////
17
24
 
18
- export interface TextFieldRootProps extends TextFieldVariantProps, TextField.RootProps {}
25
+ export interface TextFieldRootProps extends TextInputVariantProps, TextField.RootProps {}
19
26
 
20
27
  export const TextFieldRoot = withProvider<HTMLDivElement, TextFieldRootProps>(
21
- TextField.Root,
28
+ withFieldStateProps(TextField.Root),
22
29
  "root",
23
30
  );
24
31
 
25
32
  ////////////////////////////////////////////////////////////////////////////////////
26
33
 
27
- export interface TextFieldHeaderProps
28
- extends PrimitiveProps,
29
- React.HTMLAttributes<HTMLDivElement> {}
30
-
31
- export const TextFieldHeader = withContext<HTMLDivElement, TextFieldHeaderProps>(
32
- withStateProps(Primitive.div),
33
- "header",
34
- );
35
-
36
- ////////////////////////////////////////////////////////////////////////////////////
37
-
38
- export interface TextFieldLabelProps extends TextField.LabelProps {}
39
-
40
- export const TextFieldLabel = withContext<HTMLLabelElement, TextFieldLabelProps>(
41
- TextField.Label,
42
- "label",
43
- );
44
-
45
- ////////////////////////////////////////////////////////////////////////////////////
46
-
47
- export interface TextFieldIndicatorProps
48
- extends PrimitiveProps,
49
- React.HTMLAttributes<HTMLSpanElement> {}
50
-
51
- export const TextFieldIndicator = forwardRef<HTMLSpanElement, TextFieldIndicatorProps>(
52
- (props, ref) => {
53
- const { className, ...otherProps } = props;
54
- const classNames = useClassNames();
55
-
56
- return (
57
- <>
58
- <Primitive.span> </Primitive.span>
59
- <Primitive.span
60
- ref={ref}
61
- className={clsx(classNames.indicator, className)}
62
- {...otherProps}
63
- />
64
- </>
65
- );
66
- },
67
- );
68
-
69
- ////////////////////////////////////////////////////////////////////////////////////
70
-
71
- export interface TextFieldFieldProps extends PrimitiveProps, React.HTMLAttributes<HTMLDivElement> {}
72
-
73
- export const TextFieldField = withContext<HTMLDivElement, TextFieldFieldProps>(
74
- withStateProps(Primitive.div),
75
- "field",
76
- );
77
-
78
- ////////////////////////////////////////////////////////////////////////////////////
79
-
80
34
  export interface TextFieldPrefixIconProps extends InternalIconProps {}
81
35
 
82
36
  export const TextFieldPrefixIcon = withContext<SVGSVGElement, TextFieldPrefixIconProps>(
@@ -84,8 +38,6 @@ export const TextFieldPrefixIcon = withContext<SVGSVGElement, TextFieldPrefixIco
84
38
  "prefixIcon",
85
39
  );
86
40
 
87
- ////////////////////////////////////////////////////////////////////////////////////
88
-
89
41
  export interface TextFieldPrefixTextProps
90
42
  extends PrimitiveProps,
91
43
  React.HTMLAttributes<HTMLSpanElement> {}
@@ -95,8 +47,6 @@ export const TextFieldPrefixText = withContext<HTMLSpanElement, TextFieldPrefixT
95
47
  "prefixText",
96
48
  );
97
49
 
98
- ////////////////////////////////////////////////////////////////////////////////////
99
-
100
50
  export interface TextFieldSuffixIconProps extends InternalIconProps {}
101
51
 
102
52
  export const TextFieldSuffixIcon = withContext<SVGSVGElement, TextFieldSuffixIconProps>(
@@ -104,8 +54,6 @@ export const TextFieldSuffixIcon = withContext<SVGSVGElement, TextFieldSuffixIco
104
54
  "suffixIcon",
105
55
  );
106
56
 
107
- ////////////////////////////////////////////////////////////////////////////////////
108
-
109
57
  export interface TextFieldSuffixTextProps
110
58
  extends PrimitiveProps,
111
59
  React.HTMLAttributes<HTMLSpanElement> {}
@@ -119,12 +67,37 @@ export const TextFieldSuffixText = withContext<HTMLSpanElement, TextFieldSuffixT
119
67
 
120
68
  export interface TextFieldInputProps extends TextField.InputProps {}
121
69
 
122
- export const TextFieldInput = withContext<HTMLInputElement, TextFieldInputProps>(
123
- TextField.Input,
124
- "value",
125
- );
70
+ export const TextFieldInput = forwardRef<HTMLInputElement, TextFieldInputProps>(
71
+ ({ className, ...otherProps }, ref) => {
72
+ const classNames = useClassNames();
73
+ const textFieldContext = useTextFieldContext();
74
+ const fieldContext = useFieldContext({ strict: false });
75
+
76
+ const mergedProps = mergeProps(
77
+ fieldContext ? fieldContext.stateProps : {},
78
+ fieldContext ? fieldContext.inputAriaAttributes : {},
79
+ textFieldContext.inputProps,
80
+ fieldContext ? fieldContext.inputProps : {},
81
+ otherProps,
82
+ );
126
83
 
127
- ////////////////////////////////////////////////////////////////////////////////////
84
+ if (
85
+ // if not in field, then must have aria-label or aria-labelledby
86
+ !fieldContext &&
87
+ !otherProps["aria-label"] &&
88
+ !otherProps["aria-labelledby"]
89
+ ) {
90
+ console.warn(
91
+ "TextFieldInput: Please provide `aria-label` or `aria-labelledby` for accessibility, or put `TextFieldInput` inside a `Field` where a `FieldLabel` is provided.",
92
+ );
93
+ }
94
+
95
+ return (
96
+ <TextField.Input ref={ref} {...mergedProps} className={clsx(classNames.value, className)} />
97
+ );
98
+ },
99
+ );
100
+ TextFieldInput.displayName = "TextFieldInput";
128
101
 
129
102
  export interface TextFieldTextareaProps extends TextField.TextareaProps {
130
103
  /**
@@ -135,10 +108,29 @@ export interface TextFieldTextareaProps extends TextField.TextareaProps {
135
108
  }
136
109
 
137
110
  export const TextFieldTextarea = forwardRef<HTMLTextAreaElement, TextFieldTextareaProps>(
138
- (props, ref) => {
139
- const { className, autoresize = true, ...otherProps } = props;
111
+ ({ className, autoresize = true, ...otherProps }, ref) => {
140
112
  const classNames = useClassNames();
141
- const { value } = useTextFieldContext();
113
+ const textFieldContext = useTextFieldContext();
114
+ const fieldContext = useFieldContext({ strict: false });
115
+
116
+ const mergedProps = mergeProps(
117
+ fieldContext ? fieldContext.stateProps : {},
118
+ fieldContext ? fieldContext.inputAriaAttributes : {},
119
+ textFieldContext.inputProps,
120
+ fieldContext ? fieldContext.inputProps : {},
121
+ otherProps,
122
+ );
123
+
124
+ if (
125
+ // if not in field, then must have aria-label or aria-labelledby
126
+ !fieldContext &&
127
+ !otherProps["aria-label"] &&
128
+ !otherProps["aria-labelledby"]
129
+ ) {
130
+ console.warn(
131
+ "TextFieldTextarea: Please provide `aria-label` or `aria-labelledby` for accessibility, or put `TextFieldTextarea` inside a `Field` where a `FieldLabel` is provided.",
132
+ );
133
+ }
142
134
 
143
135
  // referenced from React Spectrum
144
136
  const inputRef = useRef<HTMLTextAreaElement>(null);
@@ -176,83 +168,15 @@ export const TextFieldTextarea = forwardRef<HTMLTextAreaElement, TextFieldTextar
176
168
  if (inputRef.current) {
177
169
  onHeightChange();
178
170
  }
179
- }, [onHeightChange, value, inputRef]);
171
+ }, [onHeightChange, textFieldContext.value, inputRef]);
180
172
 
181
173
  return (
182
174
  <TextField.Textarea
183
175
  ref={composeRefs(inputRef, ref)}
184
- {...otherProps}
176
+ {...mergedProps}
185
177
  className={clsx(classNames.value, className)}
186
178
  />
187
179
  );
188
180
  },
189
181
  );
190
-
191
- ////////////////////////////////////////////////////////////////////////////////////
192
-
193
- export interface TextFieldFooterProps
194
- extends PrimitiveProps,
195
- React.HTMLAttributes<HTMLDivElement> {}
196
-
197
- export const TextFieldFooter = withContext<HTMLDivElement, TextFieldFooterProps>(
198
- withStateProps(Primitive.div),
199
- "footer",
200
- );
201
-
202
- ////////////////////////////////////////////////////////////////////////////////////
203
-
204
- export interface TextFieldDescriptionProps extends TextField.DescriptionProps {}
205
-
206
- export const TextFieldDescription = withContext<HTMLSpanElement, TextFieldDescriptionProps>(
207
- TextField.Description,
208
- "description",
209
- );
210
-
211
- ////////////////////////////////////////////////////////////////////////////////////
212
-
213
- export interface TextFieldErrorMessageProps extends TextField.ErrorMessageProps {}
214
-
215
- export const TextFieldErrorMessage = withContext<HTMLSpanElement, TextFieldErrorMessageProps>(
216
- TextField.ErrorMessage,
217
- "errorMessage",
218
- );
219
-
220
- ////////////////////////////////////////////////////////////////////////////////////
221
-
222
- export interface TextFieldErrorIconProps extends InternalIconProps {}
223
-
224
- export const TextFieldErrorIcon = withContext<SVGSVGElement, TextFieldErrorIconProps>(
225
- withStateProps(InternalIcon),
226
- "errorIcon",
227
- );
228
-
229
- ////////////////////////////////////////////////////////////////////////////////////
230
-
231
- export interface TextFieldCharacterCountAreaProps
232
- extends PrimitiveProps,
233
- React.HTMLAttributes<HTMLDivElement> {}
234
-
235
- export const TextFieldCharacterCountArea = withContext<
236
- HTMLDivElement,
237
- TextFieldCharacterCountAreaProps
238
- >(withStateProps(Primitive.div), "characterCountArea");
239
-
240
- ////////////////////////////////////////////////////////////////////////////////////
241
-
242
- export interface TextFieldCharacterCountProps extends TextField.GraphemeCountProps {}
243
-
244
- export const TextFieldCharacterCount = withContext<HTMLDivElement, TextFieldCharacterCountProps>(
245
- TextField.GraphemeCount,
246
- "characterCount",
247
- );
248
-
249
- ////////////////////////////////////////////////////////////////////////////////////
250
-
251
- export interface TextFieldMaxCharacterCountProps
252
- extends PrimitiveProps,
253
- React.HTMLAttributes<HTMLSpanElement> {}
254
-
255
- export const TextFieldMaxCharacterCount = withContext<
256
- HTMLSpanElement,
257
- TextFieldMaxCharacterCountProps
258
- >(withStateProps(Primitive.span), "maxCharacterCount");
182
+ TextFieldTextarea.displayName = "TextFieldTextarea";
@@ -1,40 +1,23 @@
1
1
  export {
2
- TextFieldCharacterCount,
3
- TextFieldCharacterCountArea,
4
- TextFieldDescription,
5
- TextFieldErrorIcon,
6
- TextFieldErrorMessage,
7
- TextFieldField,
8
- TextFieldFooter,
9
- TextFieldHeader,
10
- TextFieldIndicator,
2
+ TextFieldRoot,
11
3
  TextFieldInput,
12
4
  TextFieldTextarea,
13
- TextFieldLabel,
14
- TextFieldMaxCharacterCount,
15
5
  TextFieldPrefixIcon,
16
6
  TextFieldPrefixText,
17
- TextFieldRoot,
18
7
  TextFieldSuffixIcon,
19
8
  TextFieldSuffixText,
20
- type TextFieldCharacterCountAreaProps,
21
- type TextFieldCharacterCountProps,
22
- type TextFieldDescriptionProps,
23
- type TextFieldErrorIconProps,
24
- type TextFieldErrorMessageProps,
25
- type TextFieldFieldProps,
26
- type TextFieldFooterProps,
27
- type TextFieldHeaderProps,
28
- type TextFieldIndicatorProps,
9
+ type TextFieldRootProps,
29
10
  type TextFieldInputProps,
30
11
  type TextFieldTextareaProps,
31
- type TextFieldLabelProps,
32
- type TextFieldMaxCharacterCountProps,
33
12
  type TextFieldPrefixIconProps,
34
13
  type TextFieldPrefixTextProps,
35
- type TextFieldRootProps,
36
14
  type TextFieldSuffixIconProps,
37
15
  type TextFieldSuffixTextProps,
38
16
  } from "./TextField";
39
17
 
18
+ export {
19
+ useTextFieldWithGraphemes,
20
+ type UseTextFieldWithGraphemesParams,
21
+ } from "./useTextFieldWithGraphemes";
22
+
40
23
  export * as TextField from "./TextField.namespace";
@@ -0,0 +1,14 @@
1
+ export function memoize<Arg, Result>(fn: (arg: Arg) => Result): (arg: Arg) => Result {
2
+ const cache = new Map<Arg, Result>();
3
+
4
+ return (arg: Arg) => {
5
+ if (cache.has(arg)) {
6
+ return cache.get(arg) as Result;
7
+ }
8
+
9
+ const result = fn(arg);
10
+ cache.set(arg, result);
11
+
12
+ return result;
13
+ };
14
+ }