@seed-design/react-field-button 1.0.0 → 1.0.1

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.
@@ -4,6 +4,7 @@ import { composeRefs } from '@radix-ui/react-compose-refs';
4
4
  import { elementProps, dataAttr, buttonProps, inputProps, ariaAttr, mergeProps } from '@seed-design/dom-utils';
5
5
  import { Primitive } from '@seed-design/react-primitive';
6
6
  import { useId, useCallback, useState, createContext, useContext, forwardRef } from 'react';
7
+ import { useSupports } from '@seed-design/react-supports';
7
8
 
8
9
  const getDescriptionId = (id)=>`field-button:${id}:description`;
9
10
  const getErrorMessageId = (id)=>`field-button:${id}:error-message`;
@@ -42,9 +43,9 @@ function useFieldButtonState({ values = [], onValuesChange = ()=>{} }) {
42
43
  setIsFocusVisible
43
44
  };
44
45
  }
45
- function useFieldButton(props) {
46
+ function useFieldButton({ values: propValues, onValuesChange, disabled = false, invalid = false, readOnly = false, name }) {
46
47
  const id = useId();
47
- const { values: propValues, onValuesChange, disabled = false, invalid = false, name } = props;
48
+ const isFocusVisibleSupported = useSupports("selector(:focus-visible)");
48
49
  const { values: stateValues, isHovered, isActive, isFocused, isFocusVisible, refs, renderedElements, setValues, setIsHovered, setIsActive, setIsFocused, setIsFocusVisible } = useFieldButtonState({
49
50
  values: propValues,
50
51
  onValuesChange
@@ -59,13 +60,16 @@ function useFieldButton(props) {
59
60
  "data-focus": dataAttr(isFocused),
60
61
  "data-focus-visible": dataAttr(isFocusVisible),
61
62
  "data-disabled": dataAttr(disabled),
62
- "data-invalid": dataAttr(invalid)
63
+ "data-invalid": dataAttr(invalid),
64
+ "data-readonly": dataAttr(readOnly)
63
65
  });
64
66
  return {
65
67
  values: stateValues,
66
68
  active: isActive,
67
69
  focused: isFocused,
68
70
  invalid,
71
+ disabled,
72
+ readOnly,
69
73
  setIsFocused,
70
74
  setIsFocusVisible,
71
75
  refs,
@@ -83,8 +87,8 @@ function useFieldButton(props) {
83
87
  buttonProps: buttonProps({
84
88
  ...stateProps,
85
89
  type: "button",
86
- disabled,
87
- "aria-disabled": ariaAttr(disabled),
90
+ disabled: disabled || readOnly,
91
+ "aria-disabled": ariaAttr(disabled || readOnly),
88
92
  "aria-describedby": ariaDescribedBy,
89
93
  // note that pointerdown and pointerup are attached to the button, not the root
90
94
  // this is for preventing setting isActive to true when the clear button is pressed
@@ -96,21 +100,25 @@ function useFieldButton(props) {
96
100
  },
97
101
  onBlur () {
98
102
  setIsFocused(false);
99
- setIsFocusVisible(false);
103
+ if (isFocusVisibleSupported) {
104
+ setIsFocusVisible(false);
105
+ }
100
106
  },
101
107
  onFocus (event) {
102
108
  setIsFocused(true);
103
- setIsFocusVisible(event.target.matches(":focus-visible"));
109
+ if (isFocusVisibleSupported) {
110
+ setIsFocusVisible(event.target.matches(":focus-visible"));
111
+ }
104
112
  }
105
113
  }),
106
114
  clearButtonProps: buttonProps({
107
115
  ...stateProps,
108
116
  type: "button",
109
- disabled,
117
+ disabled: disabled || readOnly,
110
118
  onClick: useCallback(()=>setValues([]), [
111
119
  setValues
112
120
  ]),
113
- hidden: disabled
121
+ hidden: disabled || readOnly
114
122
  }),
115
123
  getHiddenInputProps: useCallback((index)=>{
116
124
  const value = stateValues[index];
@@ -118,12 +126,14 @@ function useFieldButton(props) {
118
126
  return inputProps({
119
127
  type: "hidden",
120
128
  value,
129
+ disabled,
121
130
  name: name || id
122
131
  });
123
132
  }, [
124
133
  stateValues,
125
134
  name,
126
- id
135
+ id,
136
+ disabled
127
137
  ]),
128
138
  descriptionProps: elementProps({
129
139
  ...stateProps,
@@ -147,9 +157,10 @@ function useFieldButtonContext({ strict = true } = {}) {
147
157
  return context;
148
158
  }
149
159
 
150
- const FieldButtonRoot = /*#__PURE__*/ forwardRef(({ disabled, invalid, name, values, onValuesChange, ...otherProps }, ref)=>{
160
+ const FieldButtonRoot = /*#__PURE__*/ forwardRef(({ disabled, readOnly, invalid, name, values, onValuesChange, ...otherProps }, ref)=>{
151
161
  const api = useFieldButton({
152
162
  disabled,
163
+ readOnly,
153
164
  invalid,
154
165
  name,
155
166
  values,
@@ -4,6 +4,7 @@ var reactComposeRefs = require('@radix-ui/react-compose-refs');
4
4
  var domUtils = require('@seed-design/dom-utils');
5
5
  var reactPrimitive = require('@seed-design/react-primitive');
6
6
  var react = require('react');
7
+ var reactSupports = require('@seed-design/react-supports');
7
8
 
8
9
  const getDescriptionId = (id)=>`field-button:${id}:description`;
9
10
  const getErrorMessageId = (id)=>`field-button:${id}:error-message`;
@@ -42,9 +43,9 @@ function useFieldButtonState({ values = [], onValuesChange = ()=>{} }) {
42
43
  setIsFocusVisible
43
44
  };
44
45
  }
45
- function useFieldButton(props) {
46
+ function useFieldButton({ values: propValues, onValuesChange, disabled = false, invalid = false, readOnly = false, name }) {
46
47
  const id = react.useId();
47
- const { values: propValues, onValuesChange, disabled = false, invalid = false, name } = props;
48
+ const isFocusVisibleSupported = reactSupports.useSupports("selector(:focus-visible)");
48
49
  const { values: stateValues, isHovered, isActive, isFocused, isFocusVisible, refs, renderedElements, setValues, setIsHovered, setIsActive, setIsFocused, setIsFocusVisible } = useFieldButtonState({
49
50
  values: propValues,
50
51
  onValuesChange
@@ -59,13 +60,16 @@ function useFieldButton(props) {
59
60
  "data-focus": domUtils.dataAttr(isFocused),
60
61
  "data-focus-visible": domUtils.dataAttr(isFocusVisible),
61
62
  "data-disabled": domUtils.dataAttr(disabled),
62
- "data-invalid": domUtils.dataAttr(invalid)
63
+ "data-invalid": domUtils.dataAttr(invalid),
64
+ "data-readonly": domUtils.dataAttr(readOnly)
63
65
  });
64
66
  return {
65
67
  values: stateValues,
66
68
  active: isActive,
67
69
  focused: isFocused,
68
70
  invalid,
71
+ disabled,
72
+ readOnly,
69
73
  setIsFocused,
70
74
  setIsFocusVisible,
71
75
  refs,
@@ -83,8 +87,8 @@ function useFieldButton(props) {
83
87
  buttonProps: domUtils.buttonProps({
84
88
  ...stateProps,
85
89
  type: "button",
86
- disabled,
87
- "aria-disabled": domUtils.ariaAttr(disabled),
90
+ disabled: disabled || readOnly,
91
+ "aria-disabled": domUtils.ariaAttr(disabled || readOnly),
88
92
  "aria-describedby": ariaDescribedBy,
89
93
  // note that pointerdown and pointerup are attached to the button, not the root
90
94
  // this is for preventing setting isActive to true when the clear button is pressed
@@ -96,21 +100,25 @@ function useFieldButton(props) {
96
100
  },
97
101
  onBlur () {
98
102
  setIsFocused(false);
99
- setIsFocusVisible(false);
103
+ if (isFocusVisibleSupported) {
104
+ setIsFocusVisible(false);
105
+ }
100
106
  },
101
107
  onFocus (event) {
102
108
  setIsFocused(true);
103
- setIsFocusVisible(event.target.matches(":focus-visible"));
109
+ if (isFocusVisibleSupported) {
110
+ setIsFocusVisible(event.target.matches(":focus-visible"));
111
+ }
104
112
  }
105
113
  }),
106
114
  clearButtonProps: domUtils.buttonProps({
107
115
  ...stateProps,
108
116
  type: "button",
109
- disabled,
117
+ disabled: disabled || readOnly,
110
118
  onClick: react.useCallback(()=>setValues([]), [
111
119
  setValues
112
120
  ]),
113
- hidden: disabled
121
+ hidden: disabled || readOnly
114
122
  }),
115
123
  getHiddenInputProps: react.useCallback((index)=>{
116
124
  const value = stateValues[index];
@@ -118,12 +126,14 @@ function useFieldButton(props) {
118
126
  return domUtils.inputProps({
119
127
  type: "hidden",
120
128
  value,
129
+ disabled,
121
130
  name: name || id
122
131
  });
123
132
  }, [
124
133
  stateValues,
125
134
  name,
126
- id
135
+ id,
136
+ disabled
127
137
  ]),
128
138
  descriptionProps: domUtils.elementProps({
129
139
  ...stateProps,
@@ -147,9 +157,10 @@ function useFieldButtonContext({ strict = true } = {}) {
147
157
  return context;
148
158
  }
149
159
 
150
- const FieldButtonRoot = /*#__PURE__*/ react.forwardRef(({ disabled, invalid, name, values, onValuesChange, ...otherProps }, ref)=>{
160
+ const FieldButtonRoot = /*#__PURE__*/ react.forwardRef(({ disabled, readOnly, invalid, name, values, onValuesChange, ...otherProps }, ref)=>{
151
161
  const api = useFieldButton({
152
162
  disabled,
163
+ readOnly,
153
164
  invalid,
154
165
  name,
155
166
  values,
package/lib/index.cjs CHANGED
@@ -1,4 +1,4 @@
1
- var FieldButton12s = require('./FieldButton-12s-CF9DP1xP.cjs');
1
+ var FieldButton12s = require('./FieldButton-12s-Ctrm9BuF.cjs');
2
2
 
3
3
  var FieldButton_namespace = {
4
4
  __proto__: null,
package/lib/index.d.ts CHANGED
@@ -15,14 +15,20 @@ interface UseFieldButtonProps extends UseFieldButtonStateProps {
15
15
  * @default false
16
16
  */
17
17
  invalid?: boolean;
18
+ /**
19
+ * @default false
20
+ */
21
+ readOnly?: boolean;
18
22
  name?: string;
19
23
  }
20
24
  type UseFieldButtonReturn = ReturnType<typeof useFieldButton>;
21
- declare function useFieldButton(props: UseFieldButtonProps): {
25
+ declare function useFieldButton({ values: propValues, onValuesChange, disabled, invalid, readOnly, name, }: UseFieldButtonProps): {
22
26
  values: string[];
23
27
  active: boolean;
24
28
  focused: boolean;
25
29
  invalid: boolean;
30
+ disabled: boolean;
31
+ readOnly: boolean;
26
32
  setIsFocused: react.Dispatch<react.SetStateAction<boolean>>;
27
33
  setIsFocusVisible: react.Dispatch<react.SetStateAction<boolean>>;
28
34
  refs: {
@@ -1179,6 +1185,7 @@ declare function useFieldButton(props: UseFieldButtonProps): {
1179
1185
  };
1180
1186
  getHiddenInputProps: (index: number) => {
1181
1187
  disabled?: boolean | undefined | undefined;
1188
+ readOnly?: boolean | undefined | undefined;
1182
1189
  name?: string | undefined | undefined;
1183
1190
  defaultChecked?: boolean | undefined | undefined;
1184
1191
  defaultValue?: string | number | readonly string[] | undefined;
@@ -1482,7 +1489,6 @@ declare function useFieldButton(props: UseFieldButtonProps): {
1482
1489
  multiple?: boolean | undefined | undefined;
1483
1490
  pattern?: string | undefined | undefined;
1484
1491
  placeholder?: string | undefined | undefined;
1485
- readOnly?: boolean | undefined | undefined;
1486
1492
  required?: boolean | undefined | undefined;
1487
1493
  size?: number | undefined | undefined;
1488
1494
  src?: string | undefined | undefined;
package/lib/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { F as FieldButtonButton, a as FieldButtonClearButton, b as FieldButtonDescription, c as FieldButtonErrorMessage, d as FieldButtonHiddenInput, e as FieldButtonRoot } from './FieldButton-12s-nUvzy79W.js';
2
- export { u as useFieldButtonContext } from './FieldButton-12s-nUvzy79W.js';
1
+ import { F as FieldButtonButton, a as FieldButtonClearButton, b as FieldButtonDescription, c as FieldButtonErrorMessage, d as FieldButtonHiddenInput, e as FieldButtonRoot } from './FieldButton-12s-CcR1XhHB.js';
2
+ export { u as useFieldButtonContext } from './FieldButton-12s-CcR1XhHB.js';
3
3
 
4
4
  var FieldButton_namespace = {
5
5
  __proto__: null,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seed-design/react-field-button",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/daangn/seed-design.git",
@@ -30,7 +30,8 @@
30
30
  "@radix-ui/react-compose-refs": "^1.1.2",
31
31
  "@radix-ui/react-use-controllable-state": "^1.2.2",
32
32
  "@seed-design/dom-utils": "1.0.0",
33
- "@seed-design/react-primitive": "1.0.0"
33
+ "@seed-design/react-primitive": "1.0.0",
34
+ "@seed-design/react-supports": "0.0.1"
34
35
  },
35
36
  "devDependencies": {
36
37
  "@types/react": "^19.1.6",
@@ -13,8 +13,8 @@ export interface FieldButtonRootProps
13
13
  HTMLAttributes<HTMLDivElement> {}
14
14
 
15
15
  export const FieldButtonRoot = forwardRef<HTMLDivElement, FieldButtonRootProps>(
16
- ({ disabled, invalid, name, values, onValuesChange, ...otherProps }, ref) => {
17
- const api = useFieldButton({ disabled, invalid, name, values, onValuesChange });
16
+ ({ disabled, readOnly, invalid, name, values, onValuesChange, ...otherProps }, ref) => {
17
+ const api = useFieldButton({ disabled, readOnly, invalid, name, values, onValuesChange });
18
18
  const mergedProps = mergeProps(api.rootProps, otherProps);
19
19
 
20
20
  return (
@@ -39,6 +39,11 @@ function FieldButton(props: FieldButtonRootProps) {
39
39
  }
40
40
 
41
41
  describe("useFieldButton", () => {
42
+ global.CSS = {
43
+ // @ts-expect-error
44
+ supports: (_k, _v) => true,
45
+ };
46
+
42
47
  it("should render the field button correctly", () => {
43
48
  const { getByRole } = setUp(<FieldButton />);
44
49
  const button = getByRole("button", { name: "Click me" });
@@ -103,6 +108,15 @@ describe("useFieldButton", () => {
103
108
  expect(button).toHaveAttribute("aria-disabled", "true");
104
109
  });
105
110
 
111
+ it("should have readOnly state when readOnly prop is true", () => {
112
+ const { getByRole } = setUp(<FieldButton readOnly />);
113
+ const button = getByRole("button", { name: "Click me" });
114
+
115
+ expect(button).toBeDisabled();
116
+ expect(button).toHaveAttribute("data-readonly");
117
+ expect(button).toHaveAttribute("aria-disabled", "true");
118
+ });
119
+
106
120
  it("should have invalid state when invalid prop is true", () => {
107
121
  const { getByRole } = setUp(<FieldButton invalid />);
108
122
  const button = getByRole("button", { name: "Click me" });
@@ -158,6 +172,26 @@ describe("useFieldButton", () => {
158
172
  expect(button).not.toHaveAttribute("data-active");
159
173
  });
160
174
 
175
+ it("should not disable hidden inputs when readOnly (unlike disabled)", () => {
176
+ const { container } = setUp(<FieldButton readOnly values={["value1", "value2"]} />);
177
+ const hiddenInputs = container.querySelectorAll('input[type="hidden"]');
178
+
179
+ expect(hiddenInputs).toHaveLength(2);
180
+ expect(hiddenInputs[0]).not.toBeDisabled();
181
+ expect(hiddenInputs[1]).not.toBeDisabled();
182
+ expect(hiddenInputs[0]).toHaveValue("value1");
183
+ expect(hiddenInputs[1]).toHaveValue("value2");
184
+ });
185
+
186
+ it("should disable hidden inputs when disabled", () => {
187
+ const { container } = setUp(<FieldButton disabled values={["value1", "value2"]} />);
188
+ const hiddenInputs = container.querySelectorAll('input[type="hidden"]');
189
+
190
+ expect(hiddenInputs).toHaveLength(2);
191
+ expect(hiddenInputs[0]).toBeDisabled();
192
+ expect(hiddenInputs[1]).toBeDisabled();
193
+ });
194
+
161
195
  it("should render without description and errorMessage", () => {
162
196
  function MinimalFieldButton(props: FieldButtonRootProps) {
163
197
  return (
@@ -222,6 +256,19 @@ describe("useFieldButton", () => {
222
256
  expect(handleValuesChange).not.toHaveBeenCalled();
223
257
  });
224
258
 
259
+ it("should hide clear button when readOnly", async () => {
260
+ const handleValuesChange = vi.fn();
261
+ const { getByText, user } = setUp(
262
+ <FieldButton readOnly values={["value1"]} onValuesChange={handleValuesChange} />,
263
+ );
264
+ const clearButton = getByText("Clear");
265
+
266
+ expect(clearButton).toHaveAttribute("hidden");
267
+
268
+ await user.click(clearButton);
269
+ expect(handleValuesChange).not.toHaveBeenCalled();
270
+ });
271
+
225
272
  it("should not have active state when clear button is pressed", () => {
226
273
  const { getByRole } = setUp(<FieldButton />);
227
274
  const button = getByRole("button", { name: "Click me" });
@@ -1,6 +1,7 @@
1
1
  import { ariaAttr, buttonProps, dataAttr, elementProps, inputProps } from "@seed-design/dom-utils";
2
2
  import { useCallback, useId, useState } from "react";
3
3
  import { getDescriptionId, getErrorMessageId } from "./dom";
4
+ import { useSupports } from "@seed-design/react-supports";
4
5
 
5
6
  interface UseFieldButtonStateProps {
6
7
  values?: string[];
@@ -57,14 +58,26 @@ export interface UseFieldButtonProps extends UseFieldButtonStateProps {
57
58
  */
58
59
  invalid?: boolean;
59
60
 
61
+ /**
62
+ * @default false
63
+ */
64
+ readOnly?: boolean;
65
+
60
66
  name?: string;
61
67
  }
62
68
 
63
69
  export type UseFieldButtonReturn = ReturnType<typeof useFieldButton>;
64
70
 
65
- export function useFieldButton(props: UseFieldButtonProps) {
71
+ export function useFieldButton({
72
+ values: propValues,
73
+ onValuesChange,
74
+ disabled = false,
75
+ invalid = false,
76
+ readOnly = false,
77
+ name,
78
+ }: UseFieldButtonProps) {
66
79
  const id = useId();
67
- const { values: propValues, onValuesChange, disabled = false, invalid = false, name } = props;
80
+ const isFocusVisibleSupported = useSupports("selector(:focus-visible)");
68
81
 
69
82
  const {
70
83
  values: stateValues,
@@ -96,6 +109,7 @@ export function useFieldButton(props: UseFieldButtonProps) {
96
109
  "data-focus-visible": dataAttr(isFocusVisible),
97
110
  "data-disabled": dataAttr(disabled),
98
111
  "data-invalid": dataAttr(invalid),
112
+ "data-readonly": dataAttr(readOnly),
99
113
  });
100
114
 
101
115
  return {
@@ -103,6 +117,8 @@ export function useFieldButton(props: UseFieldButtonProps) {
103
117
  active: isActive,
104
118
  focused: isFocused,
105
119
  invalid,
120
+ disabled,
121
+ readOnly,
106
122
 
107
123
  setIsFocused,
108
124
  setIsFocusVisible,
@@ -127,9 +143,9 @@ export function useFieldButton(props: UseFieldButtonProps) {
127
143
  ...stateProps,
128
144
 
129
145
  type: "button",
130
- disabled,
146
+ disabled: disabled || readOnly,
131
147
 
132
- "aria-disabled": ariaAttr(disabled),
148
+ "aria-disabled": ariaAttr(disabled || readOnly),
133
149
  "aria-describedby": ariaDescribedBy,
134
150
 
135
151
  // note that pointerdown and pointerup are attached to the button, not the root
@@ -143,11 +159,15 @@ export function useFieldButton(props: UseFieldButtonProps) {
143
159
 
144
160
  onBlur() {
145
161
  setIsFocused(false);
146
- setIsFocusVisible(false);
162
+ if (isFocusVisibleSupported) {
163
+ setIsFocusVisible(false);
164
+ }
147
165
  },
148
166
  onFocus(event) {
149
167
  setIsFocused(true);
150
- setIsFocusVisible(event.target.matches(":focus-visible"));
168
+ if (isFocusVisibleSupported) {
169
+ setIsFocusVisible(event.target.matches(":focus-visible"));
170
+ }
151
171
  },
152
172
  }),
153
173
 
@@ -155,11 +175,11 @@ export function useFieldButton(props: UseFieldButtonProps) {
155
175
  ...stateProps,
156
176
 
157
177
  type: "button",
158
- disabled,
178
+ disabled: disabled || readOnly,
159
179
 
160
180
  onClick: useCallback(() => setValues([]), [setValues]),
161
181
 
162
- hidden: disabled,
182
+ hidden: disabled || readOnly,
163
183
  }),
164
184
 
165
185
  getHiddenInputProps: useCallback(
@@ -171,10 +191,11 @@ export function useFieldButton(props: UseFieldButtonProps) {
171
191
  return inputProps({
172
192
  type: "hidden",
173
193
  value,
194
+ disabled, // disabled field button should not submit values, while readonly field button should submit values
174
195
  name: name || id,
175
196
  });
176
197
  },
177
- [stateValues, name, id],
198
+ [stateValues, name, id, disabled],
178
199
  ),
179
200
 
180
201
  descriptionProps: elementProps({