@choice-ui/radio 0.0.4 → 0.0.5

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.
package/dist/index.d.ts CHANGED
@@ -50,20 +50,4 @@ interface RadioGroupType {
50
50
  }
51
51
  declare const RadioGroup: RadioGroupType;
52
52
 
53
- interface RadioContextType {
54
- descriptionId: string;
55
- disabled?: boolean;
56
- id: string;
57
- }
58
- declare function useRadioContext(): RadioContextType;
59
- interface RadioGroupContextType {
60
- disabled?: boolean;
61
- name: string;
62
- onChange: (value: string) => void;
63
- readOnly?: boolean;
64
- value: string;
65
- variant?: "default" | "accent" | "outline";
66
- }
67
- declare function useRadioGroupContext(): RadioGroupContextType;
68
-
69
- export { Radio, type RadioContextType, RadioGroup, type RadioGroupContextType, type RadioGroupProps, RadioLabel, type RadioLabelProps, type RadioProps, useRadioContext, useRadioGroupContext };
53
+ export { Radio, RadioGroup, type RadioGroupProps, type RadioProps };
package/dist/index.js CHANGED
@@ -151,14 +151,14 @@ var RadioLabel = memo(
151
151
  forwardRef(function RadioLabel2(props, ref) {
152
152
  const { children, className, ...rest } = props;
153
153
  const { id, descriptionId, disabled } = useRadioContext();
154
- const styles = radioTv({ disabled });
154
+ const tv = radioTv({ disabled });
155
155
  return /* @__PURE__ */ jsx(
156
156
  "label",
157
157
  {
158
158
  ref,
159
159
  id: descriptionId,
160
160
  htmlFor: id,
161
- className: tcx(styles.label(), className),
161
+ className: tcx(tv.label(), className),
162
162
  ...rest,
163
163
  children
164
164
  }
@@ -184,7 +184,7 @@ var RadioBase = forwardRef(function Radio(props, ref) {
184
184
  } = props;
185
185
  const id = useId();
186
186
  const descriptionId = useId();
187
- const styles = radioTv({
187
+ const tv = radioTv({
188
188
  type: "radio",
189
189
  variant,
190
190
  disabled,
@@ -209,13 +209,13 @@ var RadioBase = forwardRef(function Radio(props, ref) {
209
209
  }
210
210
  return children;
211
211
  };
212
- return /* @__PURE__ */ jsx(RadioContext.Provider, { value: { id, descriptionId, disabled }, children: /* @__PURE__ */ jsxs("div", { className: tcx(styles.root(), className), children: [
212
+ return /* @__PURE__ */ jsx(RadioContext.Provider, { value: { id, descriptionId, disabled }, children: /* @__PURE__ */ jsxs("div", { className: tcx(tv.root(), className), children: [
213
213
  /* @__PURE__ */ jsxs("div", { className: "pointer-events-none relative", children: [
214
214
  /* @__PURE__ */ jsx(
215
215
  "input",
216
216
  {
217
217
  ref,
218
- className: styles.input(),
218
+ className: tv.input(),
219
219
  type: "radio",
220
220
  id,
221
221
  name,
@@ -231,7 +231,7 @@ var RadioBase = forwardRef(function Radio(props, ref) {
231
231
  ...rest
232
232
  }
233
233
  ),
234
- /* @__PURE__ */ jsx("div", { className: styles.box(), children: value && /* @__PURE__ */ jsx(Dot, {}) })
234
+ /* @__PURE__ */ jsx("div", { className: tv.box(), children: value && /* @__PURE__ */ jsx(Dot, {}) })
235
235
  ] }),
236
236
  renderChildren()
237
237
  ] }) });
@@ -332,4 +332,4 @@ var RadioGroup2 = MemoizedRadioGroup;
332
332
  RadioGroup2.Item = RadioGroupItem;
333
333
  RadioGroup2.displayName = "RadioGroup";
334
334
 
335
- export { Radio2 as Radio, RadioGroup2 as RadioGroup, RadioLabel, useRadioContext, useRadioGroupContext };
335
+ export { Radio2 as Radio, RadioGroup2 as RadioGroup };
package/package.json CHANGED
@@ -1,21 +1,18 @@
1
1
  {
2
2
  "name": "@choice-ui/radio",
3
- "version": "0.0.4",
4
- "description": "Radio component for Choiceform Design System",
3
+ "version": "0.0.5",
4
+ "description": "A radio button component for single selection from a group of mutually exclusive options",
5
5
  "sideEffects": false,
6
6
  "type": "module",
7
- "main": "./dist/index.cjs",
8
- "module": "./dist/index.js",
9
- "types": "./src/index.ts",
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
10
9
  "files": [
11
- "dist",
12
- "src"
10
+ "dist"
13
11
  ],
14
12
  "exports": {
15
13
  ".": {
16
- "types": "./src/index.ts",
17
- "import": "./dist/index.js",
18
- "require": "./dist/index.cjs"
14
+ "types": "./dist/index.d.ts",
15
+ "import": "./dist/index.js"
19
16
  }
20
17
  },
21
18
  "publishConfig": {
@@ -23,8 +20,8 @@
23
20
  },
24
21
  "dependencies": {
25
22
  "usehooks-ts": "^3.1.0",
26
- "@choiceform/icons-react": "^1.3.8",
27
- "@choice-ui/shared": "0.0.1"
23
+ "@choice-ui/shared": "^0.0.1",
24
+ "@choiceform/icons-react": "^1.3.8"
28
25
  },
29
26
  "devDependencies": {
30
27
  "@types/react": "^18.3.12",
@@ -39,6 +36,7 @@
39
36
  "react": ">=18.0.0",
40
37
  "react-dom": ">=18.0.0"
41
38
  },
39
+ "source": "./src/index.ts",
42
40
  "scripts": {
43
41
  "build": "tsup",
44
42
  "build:watch": "tsup --watch",
package/dist/index.cjs DELETED
@@ -1,341 +0,0 @@
1
- 'use strict';
2
-
3
- var shared = require('@choice-ui/shared');
4
- var iconsReact = require('@choiceform/icons-react');
5
- var react = require('react');
6
- var usehooksTs = require('usehooks-ts');
7
- var jsxRuntime = require('react/jsx-runtime');
8
-
9
- // src/radio.tsx
10
- var RadioContext = react.createContext(null);
11
- function useRadioContext() {
12
- const context = react.useContext(RadioContext);
13
- if (!context) {
14
- throw new Error("Radio.Label must be used within a Radio component");
15
- }
16
- return context;
17
- }
18
- var RadioGroupContext = react.createContext(null);
19
- function useRadioGroupContext() {
20
- const context = react.useContext(RadioGroupContext);
21
- if (!context) {
22
- throw new Error("RadioGroupItem must be used within a RadioGroup");
23
- }
24
- return context;
25
- }
26
- var radioTv = shared.tcv({
27
- slots: {
28
- root: "flex items-center select-none",
29
- box: ["relative flex size-4 items-center justify-center", "border border-solid"],
30
- input: "peer pointer-events-auto absolute inset-0 appearance-none opacity-0",
31
- label: "pl-2"
32
- },
33
- variants: {
34
- type: {
35
- checkbox: {
36
- box: "rounded-md"
37
- },
38
- radio: {
39
- box: "rounded-full"
40
- }
41
- },
42
- variant: {
43
- default: {},
44
- accent: {},
45
- outline: {}
46
- },
47
- checked: {
48
- true: {},
49
- false: {}
50
- },
51
- disabled: {
52
- true: {},
53
- false: {}
54
- },
55
- focused: {
56
- true: {},
57
- false: {}
58
- }
59
- },
60
- compoundVariants: [
61
- // 未选中状态
62
- {
63
- variant: ["default", "accent"],
64
- checked: false,
65
- disabled: false,
66
- focused: false,
67
- class: {
68
- box: "bg-secondary-background border-default-boundary"
69
- }
70
- },
71
- {
72
- variant: "outline",
73
- checked: false,
74
- disabled: false,
75
- focused: false,
76
- class: {
77
- box: ["border-default-foreground", "peer-focus-visible:border-selected-boundary"]
78
- }
79
- },
80
- // 选中状态 - default
81
- {
82
- variant: "default",
83
- checked: true,
84
- disabled: false,
85
- focused: false,
86
- class: {
87
- box: [
88
- "bg-secondary-background border-default-boundary",
89
- "peer-focus-visible:border-selected-strong-boundary"
90
- ]
91
- }
92
- },
93
- // 选中状态 - accent & outline
94
- {
95
- variant: ["accent", "outline"],
96
- checked: true,
97
- disabled: false,
98
- focused: false,
99
- class: {
100
- box: [
101
- "bg-accent-background border-selected-strong-boundary text-on-accent-foreground",
102
- "peer-focus-visible:border-selected-strong-boundary",
103
- "peer-focus-visible:text-on-accent-foreground",
104
- "peer-focus-visible:shadow-checked-focused"
105
- ]
106
- }
107
- },
108
- {
109
- variant: ["default", "accent", "outline"],
110
- checked: false,
111
- disabled: false,
112
- focused: true,
113
- class: {
114
- box: "border-selected-boundary"
115
- }
116
- },
117
- {
118
- variant: "default",
119
- checked: true,
120
- disabled: false,
121
- focused: true,
122
- class: {
123
- box: "border-selected-strong-boundary"
124
- }
125
- },
126
- {
127
- variant: ["accent", "outline"],
128
- checked: true,
129
- disabled: false,
130
- focused: true,
131
- class: {
132
- box: "text-on-accent-foreground border-selected-strong-boundary shadow-checked-focused"
133
- }
134
- },
135
- {
136
- variant: ["accent", "outline", "default"],
137
- disabled: true,
138
- class: {
139
- root: "text-default-background",
140
- box: "border-disabled-background bg-disabled-background",
141
- label: "text-disabled-foreground"
142
- }
143
- }
144
- ],
145
- defaultVariants: {
146
- variant: "default",
147
- checked: false,
148
- disabled: false,
149
- focused: false
150
- }
151
- });
152
- var RadioLabel = react.memo(
153
- react.forwardRef(function RadioLabel2(props, ref) {
154
- const { children, className, ...rest } = props;
155
- const { id, descriptionId, disabled } = useRadioContext();
156
- const styles = radioTv({ disabled });
157
- return /* @__PURE__ */ jsxRuntime.jsx(
158
- "label",
159
- {
160
- ref,
161
- id: descriptionId,
162
- htmlFor: id,
163
- className: shared.tcx(styles.label(), className),
164
- ...rest,
165
- children
166
- }
167
- );
168
- })
169
- );
170
- RadioLabel.displayName = "Radio.Label";
171
- var RadioBase = react.forwardRef(function Radio(props, ref) {
172
- const {
173
- value,
174
- onChange,
175
- disabled,
176
- readOnly = false,
177
- name,
178
- variant = "default",
179
- className,
180
- focused,
181
- children,
182
- "aria-label": ariaLabel,
183
- "aria-describedby": ariaDescribedby,
184
- onKeyDown,
185
- ...rest
186
- } = props;
187
- const id = react.useId();
188
- const descriptionId = react.useId();
189
- const styles = radioTv({
190
- type: "radio",
191
- variant,
192
- disabled,
193
- checked: value,
194
- focused
195
- });
196
- const handleChange = usehooksTs.useEventCallback((e) => {
197
- if (readOnly) return;
198
- onChange(e.target.checked);
199
- });
200
- const handleKeyDown = usehooksTs.useEventCallback((e) => {
201
- if (readOnly) return;
202
- if (e.key === " " || e.key === "Enter") {
203
- e.preventDefault();
204
- onChange(!value);
205
- }
206
- onKeyDown?.(e);
207
- });
208
- const renderChildren = () => {
209
- if (typeof children === "string" || typeof children === "number") {
210
- return /* @__PURE__ */ jsxRuntime.jsx(RadioLabel, { children });
211
- }
212
- return children;
213
- };
214
- return /* @__PURE__ */ jsxRuntime.jsx(RadioContext.Provider, { value: { id, descriptionId, disabled }, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: shared.tcx(styles.root(), className), children: [
215
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pointer-events-none relative", children: [
216
- /* @__PURE__ */ jsxRuntime.jsx(
217
- "input",
218
- {
219
- ref,
220
- className: styles.input(),
221
- type: "radio",
222
- id,
223
- name,
224
- checked: value,
225
- disabled: disabled || readOnly,
226
- onChange: handleChange,
227
- "aria-label": ariaLabel,
228
- "aria-describedby": ariaDescribedby || descriptionId,
229
- "aria-checked": value,
230
- "aria-disabled": disabled || readOnly,
231
- role: "radio",
232
- onKeyDown: handleKeyDown,
233
- ...rest
234
- }
235
- ),
236
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: styles.box(), children: value && /* @__PURE__ */ jsxRuntime.jsx(iconsReact.Dot, {}) })
237
- ] }),
238
- renderChildren()
239
- ] }) });
240
- });
241
- var MemoizedRadio = react.memo(RadioBase);
242
- var Radio2 = MemoizedRadio;
243
- Radio2.Label = RadioLabel;
244
- Radio2.displayName = "Radio";
245
- var RadioGroupItem = react.memo(
246
- react.forwardRef(function RadioGroupItem2(props, ref) {
247
- const { value, children, className, disabled, ...rest } = props;
248
- const {
249
- name,
250
- value: selectedValue,
251
- onChange,
252
- variant,
253
- readOnly: contextReadonly
254
- } = useRadioGroupContext();
255
- const isChecked = selectedValue === value;
256
- const handleChange = usehooksTs.useEventCallback(() => {
257
- if (contextReadonly) return;
258
- onChange(value);
259
- });
260
- return /* @__PURE__ */ jsxRuntime.jsx(
261
- Radio2,
262
- {
263
- name,
264
- value: isChecked,
265
- disabled,
266
- readOnly: contextReadonly,
267
- variant,
268
- onChange: handleChange,
269
- className,
270
- ...rest,
271
- ref,
272
- children: /* @__PURE__ */ jsxRuntime.jsx(Radio2.Label, { children })
273
- }
274
- );
275
- })
276
- );
277
- RadioGroupItem.displayName = "RadioGroup.Item";
278
- var RadioGroupBase = react.forwardRef(function RadioGroup(props, ref) {
279
- const {
280
- className,
281
- options,
282
- value,
283
- onChange,
284
- disabled,
285
- readOnly = false,
286
- variant = "default",
287
- children,
288
- ...rest
289
- } = props;
290
- const id = react.useId();
291
- const handleChange = usehooksTs.useEventCallback((newValue) => {
292
- if (readOnly) return;
293
- onChange(newValue);
294
- });
295
- const contextValue = react.useMemo(
296
- () => ({
297
- name: id,
298
- value,
299
- onChange: handleChange,
300
- disabled,
301
- readOnly,
302
- variant
303
- }),
304
- [id, value, handleChange, disabled, readOnly, variant]
305
- );
306
- const renderOptionsRadios = react.useMemo(() => {
307
- if (!options) return null;
308
- return options.map((option) => /* @__PURE__ */ jsxRuntime.jsx(
309
- RadioGroupItem,
310
- {
311
- value: option.value,
312
- disabled: option.disabled || disabled,
313
- variant,
314
- children: option.label
315
- },
316
- option.value
317
- ));
318
- }, [disabled, options, variant]);
319
- return /* @__PURE__ */ jsxRuntime.jsx(RadioGroupContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxRuntime.jsx(
320
- "div",
321
- {
322
- className: shared.tcx("flex flex-col gap-2", className),
323
- ref,
324
- role: "radiogroup",
325
- "aria-labelledby": props["aria-labelledby"],
326
- "aria-label": props["aria-label"],
327
- ...rest,
328
- children: options ? renderOptionsRadios : children
329
- }
330
- ) });
331
- });
332
- var MemoizedRadioGroup = react.memo(RadioGroupBase);
333
- var RadioGroup2 = MemoizedRadioGroup;
334
- RadioGroup2.Item = RadioGroupItem;
335
- RadioGroup2.displayName = "RadioGroup";
336
-
337
- exports.Radio = Radio2;
338
- exports.RadioGroup = RadioGroup2;
339
- exports.RadioLabel = RadioLabel;
340
- exports.useRadioContext = useRadioContext;
341
- exports.useRadioGroupContext = useRadioGroupContext;
package/dist/index.d.cts DELETED
@@ -1,69 +0,0 @@
1
- import * as react from 'react';
2
- import { HTMLProps, ReactNode } from 'react';
3
-
4
- interface RadioLabelProps extends Omit<HTMLProps<HTMLLabelElement>, "htmlFor" | "id" | "disabled"> {
5
- children: ReactNode;
6
- }
7
- declare const RadioLabel: react.MemoExoticComponent<react.ForwardRefExoticComponent<Omit<RadioLabelProps, "ref"> & react.RefAttributes<HTMLLabelElement>>>;
8
-
9
- interface RadioProps extends Omit<HTMLProps<HTMLInputElement>, "value" | "onChange"> {
10
- children?: ReactNode;
11
- className?: string;
12
- focused?: boolean;
13
- onChange: (value: boolean) => void;
14
- readOnly?: boolean;
15
- value: boolean;
16
- variant?: "default" | "accent" | "outline";
17
- }
18
- interface RadioType {
19
- (props: RadioProps & {
20
- ref?: React.Ref<HTMLInputElement>;
21
- }): JSX.Element;
22
- Label: typeof RadioLabel;
23
- displayName?: string;
24
- }
25
- declare const Radio: RadioType;
26
-
27
- interface RadioGroupProps extends Omit<HTMLProps<HTMLDivElement>, "value" | "onChange"> {
28
- children?: ReactNode;
29
- onChange: (value: string) => void;
30
- options?: {
31
- disabled?: boolean;
32
- label: string;
33
- value: string;
34
- }[];
35
- readOnly?: boolean;
36
- value: string;
37
- variant?: "default" | "accent" | "outline";
38
- }
39
- type RadioGroupItemProps = Omit<RadioProps, "value" | "onChange"> & {
40
- children: ReactNode;
41
- value: string;
42
- };
43
- declare const RadioGroupItem: react.MemoExoticComponent<react.ForwardRefExoticComponent<Omit<RadioGroupItemProps, "ref"> & react.RefAttributes<HTMLInputElement>>>;
44
- interface RadioGroupType {
45
- (props: RadioGroupProps & {
46
- ref?: React.Ref<HTMLDivElement>;
47
- }): JSX.Element;
48
- Item: typeof RadioGroupItem;
49
- displayName?: string;
50
- }
51
- declare const RadioGroup: RadioGroupType;
52
-
53
- interface RadioContextType {
54
- descriptionId: string;
55
- disabled?: boolean;
56
- id: string;
57
- }
58
- declare function useRadioContext(): RadioContextType;
59
- interface RadioGroupContextType {
60
- disabled?: boolean;
61
- name: string;
62
- onChange: (value: string) => void;
63
- readOnly?: boolean;
64
- value: string;
65
- variant?: "default" | "accent" | "outline";
66
- }
67
- declare function useRadioGroupContext(): RadioGroupContextType;
68
-
69
- export { Radio, type RadioContextType, RadioGroup, type RadioGroupContextType, type RadioGroupProps, RadioLabel, type RadioLabelProps, type RadioProps, useRadioContext, useRadioGroupContext };
package/src/context.ts DELETED
@@ -1,38 +0,0 @@
1
- import { createContext, useContext } from "react"
2
-
3
- // Radio 组件的 Context
4
- export interface RadioContextType {
5
- descriptionId: string
6
- disabled?: boolean
7
- id: string
8
- }
9
-
10
- export const RadioContext = createContext<RadioContextType | null>(null)
11
-
12
- export function useRadioContext() {
13
- const context = useContext(RadioContext)
14
- if (!context) {
15
- throw new Error("Radio.Label must be used within a Radio component")
16
- }
17
- return context
18
- }
19
-
20
- // RadioGroup 组件的 Context
21
- export interface RadioGroupContextType {
22
- disabled?: boolean
23
- name: string
24
- onChange: (value: string) => void
25
- readOnly?: boolean
26
- value: string
27
- variant?: "default" | "accent" | "outline"
28
- }
29
-
30
- export const RadioGroupContext = createContext<RadioGroupContextType | null>(null)
31
-
32
- export function useRadioGroupContext() {
33
- const context = useContext(RadioGroupContext)
34
- if (!context) {
35
- throw new Error("RadioGroupItem must be used within a RadioGroup")
36
- }
37
- return context
38
- }
package/src/index.ts DELETED
@@ -1,8 +0,0 @@
1
- export { Radio } from "./radio"
2
- export { RadioGroup } from "./radio-group"
3
- export { RadioLabel } from "./radio-label"
4
- export { useRadioContext, useRadioGroupContext } from "./context"
5
- export type { RadioProps } from "./radio"
6
- export type { RadioGroupProps } from "./radio-group"
7
- export type { RadioLabelProps } from "./radio-label"
8
- export type { RadioContextType, RadioGroupContextType } from "./context"
@@ -1,137 +0,0 @@
1
- import { tcx } from "@choice-ui/shared"
2
- import { forwardRef, HTMLProps, memo, ReactNode, useId, useMemo } from "react"
3
- import { useEventCallback } from "usehooks-ts"
4
- import { RadioGroupContext, useRadioGroupContext } from "./context"
5
- import { Radio, RadioProps } from "./radio"
6
-
7
- export interface RadioGroupProps extends Omit<HTMLProps<HTMLDivElement>, "value" | "onChange"> {
8
- children?: ReactNode
9
- onChange: (value: string) => void
10
- options?: {
11
- disabled?: boolean
12
- label: string
13
- value: string
14
- }[]
15
- readOnly?: boolean
16
- value: string
17
- variant?: "default" | "accent" | "outline"
18
- }
19
-
20
- type RadioGroupItemProps = Omit<RadioProps, "value" | "onChange"> & {
21
- children: ReactNode
22
- value: string
23
- }
24
-
25
- const RadioGroupItem = memo(
26
- forwardRef<HTMLInputElement, RadioGroupItemProps>(function RadioGroupItem(props, ref) {
27
- const { value, children, className, disabled, ...rest } = props
28
- const {
29
- name,
30
- value: selectedValue,
31
- onChange,
32
- variant,
33
- readOnly: contextReadonly,
34
- } = useRadioGroupContext()
35
- const isChecked = selectedValue === value
36
-
37
- const handleChange = useEventCallback(() => {
38
- if (contextReadonly) return
39
- onChange(value)
40
- })
41
-
42
- return (
43
- <Radio
44
- name={name}
45
- value={isChecked}
46
- disabled={disabled}
47
- readOnly={contextReadonly}
48
- variant={variant}
49
- onChange={handleChange}
50
- className={className}
51
- {...rest}
52
- ref={ref}
53
- >
54
- <Radio.Label>{children}</Radio.Label>
55
- </Radio>
56
- )
57
- }),
58
- )
59
-
60
- RadioGroupItem.displayName = "RadioGroup.Item"
61
-
62
- const RadioGroupBase = forwardRef<HTMLDivElement, RadioGroupProps>(function RadioGroup(props, ref) {
63
- const {
64
- className,
65
- options,
66
- value,
67
- onChange,
68
- disabled,
69
- readOnly = false,
70
- variant = "default",
71
- children,
72
- ...rest
73
- } = props
74
- const id = useId()
75
-
76
- const handleChange = useEventCallback((newValue: string) => {
77
- if (readOnly) return
78
- onChange(newValue)
79
- })
80
-
81
- const contextValue = useMemo(
82
- () => ({
83
- name: id,
84
- value,
85
- onChange: handleChange,
86
- disabled,
87
- readOnly,
88
- variant,
89
- }),
90
- [id, value, handleChange, disabled, readOnly, variant],
91
- )
92
-
93
- // 渲染基于选项的单选按钮
94
- const renderOptionsRadios = useMemo(() => {
95
- if (!options) return null
96
-
97
- return options.map((option) => (
98
- <RadioGroupItem
99
- key={option.value}
100
- value={option.value}
101
- disabled={option.disabled || disabled}
102
- variant={variant}
103
- >
104
- {option.label}
105
- </RadioGroupItem>
106
- ))
107
- }, [disabled, options, variant])
108
-
109
- return (
110
- <RadioGroupContext.Provider value={contextValue}>
111
- <div
112
- className={tcx("flex flex-col gap-2", className)}
113
- ref={ref}
114
- role="radiogroup"
115
- aria-labelledby={props["aria-labelledby"]}
116
- aria-label={props["aria-label"]}
117
- {...rest}
118
- >
119
- {options ? renderOptionsRadios : children}
120
- </div>
121
- </RadioGroupContext.Provider>
122
- )
123
- })
124
-
125
- // 使用 memo 包装组件以避免不必要的重渲染
126
- const MemoizedRadioGroup = memo(RadioGroupBase) as unknown as RadioGroupType
127
-
128
- interface RadioGroupType {
129
- (props: RadioGroupProps & { ref?: React.Ref<HTMLDivElement> }): JSX.Element
130
- Item: typeof RadioGroupItem
131
- displayName?: string
132
- }
133
-
134
- export const RadioGroup = MemoizedRadioGroup
135
-
136
- RadioGroup.Item = RadioGroupItem
137
- RadioGroup.displayName = "RadioGroup"
@@ -1,33 +0,0 @@
1
- import { tcx } from "@choice-ui/shared"
2
- import { forwardRef, HTMLProps, memo, ReactNode } from "react"
3
- import { useRadioContext } from "./context"
4
- import { radioTv } from "./tv"
5
-
6
- export interface RadioLabelProps extends Omit<
7
- HTMLProps<HTMLLabelElement>,
8
- "htmlFor" | "id" | "disabled"
9
- > {
10
- children: ReactNode
11
- }
12
-
13
- export const RadioLabel = memo(
14
- forwardRef<HTMLLabelElement, RadioLabelProps>(function RadioLabel(props, ref) {
15
- const { children, className, ...rest } = props
16
- const { id, descriptionId, disabled } = useRadioContext()
17
- const styles = radioTv({ disabled })
18
-
19
- return (
20
- <label
21
- ref={ref}
22
- id={descriptionId}
23
- htmlFor={id}
24
- className={tcx(styles.label(), className)}
25
- {...rest}
26
- >
27
- {children}
28
- </label>
29
- )
30
- }),
31
- )
32
-
33
- RadioLabel.displayName = "Radio.Label"
package/src/radio.tsx DELETED
@@ -1,109 +0,0 @@
1
- import { tcx } from "@choice-ui/shared"
2
- import { Dot } from "@choiceform/icons-react"
3
- import { forwardRef, HTMLProps, memo, ReactNode, useId } from "react"
4
- import { useEventCallback } from "usehooks-ts"
5
- import { RadioContext } from "./context"
6
- import { RadioLabel } from "./radio-label"
7
- import { radioTv } from "./tv"
8
-
9
- export interface RadioProps extends Omit<HTMLProps<HTMLInputElement>, "value" | "onChange"> {
10
- children?: ReactNode
11
- className?: string
12
- focused?: boolean
13
- onChange: (value: boolean) => void
14
- readOnly?: boolean
15
- value: boolean
16
- variant?: "default" | "accent" | "outline"
17
- }
18
-
19
- const RadioBase = forwardRef<HTMLInputElement, RadioProps>(function Radio(props, ref) {
20
- const {
21
- value,
22
- onChange,
23
- disabled,
24
- readOnly = false,
25
- name,
26
- variant = "default",
27
- className,
28
- focused,
29
- children,
30
- "aria-label": ariaLabel,
31
- "aria-describedby": ariaDescribedby,
32
- onKeyDown,
33
- ...rest
34
- } = props
35
- const id = useId()
36
- const descriptionId = useId()
37
-
38
- const styles = radioTv({
39
- type: "radio",
40
- variant,
41
- disabled,
42
- checked: value,
43
- focused,
44
- })
45
-
46
- const handleChange = useEventCallback((e: React.ChangeEvent<HTMLInputElement>) => {
47
- if (readOnly) return
48
- onChange(e.target.checked)
49
- })
50
-
51
- const handleKeyDown = useEventCallback((e: React.KeyboardEvent<HTMLInputElement>) => {
52
- if (readOnly) return
53
- if (e.key === " " || e.key === "Enter") {
54
- e.preventDefault()
55
- onChange(!value)
56
- }
57
- onKeyDown?.(e)
58
- })
59
-
60
- // 自动将字符串类型的 children 包装成 RadioLabel
61
- const renderChildren = () => {
62
- if (typeof children === "string" || typeof children === "number") {
63
- return <RadioLabel>{children}</RadioLabel>
64
- }
65
- return children
66
- }
67
-
68
- return (
69
- <RadioContext.Provider value={{ id, descriptionId, disabled }}>
70
- <div className={tcx(styles.root(), className)}>
71
- <div className="pointer-events-none relative">
72
- <input
73
- ref={ref}
74
- className={styles.input()}
75
- type="radio"
76
- id={id}
77
- name={name}
78
- checked={value}
79
- disabled={disabled || readOnly}
80
- onChange={handleChange}
81
- aria-label={ariaLabel}
82
- aria-describedby={ariaDescribedby || descriptionId}
83
- aria-checked={value}
84
- aria-disabled={disabled || readOnly}
85
- role="radio"
86
- onKeyDown={handleKeyDown}
87
- {...rest}
88
- />
89
- <div className={styles.box()}>{value && <Dot />}</div>
90
- </div>
91
-
92
- {renderChildren()}
93
- </div>
94
- </RadioContext.Provider>
95
- )
96
- })
97
-
98
- const MemoizedRadio = memo(RadioBase) as unknown as RadioType
99
-
100
- interface RadioType {
101
- (props: RadioProps & { ref?: React.Ref<HTMLInputElement> }): JSX.Element
102
- Label: typeof RadioLabel
103
- displayName?: string
104
- }
105
-
106
- export const Radio = MemoizedRadio as RadioType
107
-
108
- Radio.Label = RadioLabel
109
- Radio.displayName = "Radio"
package/src/tv.ts DELETED
@@ -1,128 +0,0 @@
1
- import { tcv } from "@choice-ui/shared"
2
-
3
- export const radioTv = tcv({
4
- slots: {
5
- root: "flex items-center select-none",
6
- box: ["relative flex size-4 items-center justify-center", "border border-solid"],
7
- input: "peer pointer-events-auto absolute inset-0 appearance-none opacity-0",
8
- label: "pl-2",
9
- },
10
- variants: {
11
- type: {
12
- checkbox: {
13
- box: "rounded-md",
14
- },
15
- radio: {
16
- box: "rounded-full",
17
- },
18
- },
19
- variant: {
20
- default: {},
21
- accent: {},
22
- outline: {},
23
- },
24
- checked: {
25
- true: {},
26
- false: {},
27
- },
28
- disabled: {
29
- true: {},
30
- false: {},
31
- },
32
- focused: {
33
- true: {},
34
- false: {},
35
- },
36
- },
37
- compoundVariants: [
38
- // 未选中状态
39
- {
40
- variant: ["default", "accent"],
41
- checked: false,
42
- disabled: false,
43
- focused: false,
44
- class: {
45
- box: "bg-secondary-background border-default-boundary",
46
- },
47
- },
48
- {
49
- variant: "outline",
50
- checked: false,
51
- disabled: false,
52
- focused: false,
53
- class: {
54
- box: ["border-default-foreground", "peer-focus-visible:border-selected-boundary"],
55
- },
56
- },
57
- // 选中状态 - default
58
- {
59
- variant: "default",
60
- checked: true,
61
- disabled: false,
62
- focused: false,
63
- class: {
64
- box: [
65
- "bg-secondary-background border-default-boundary",
66
- "peer-focus-visible:border-selected-strong-boundary",
67
- ],
68
- },
69
- },
70
- // 选中状态 - accent & outline
71
- {
72
- variant: ["accent", "outline"],
73
- checked: true,
74
- disabled: false,
75
- focused: false,
76
- class: {
77
- box: [
78
- "bg-accent-background border-selected-strong-boundary text-on-accent-foreground",
79
- "peer-focus-visible:border-selected-strong-boundary",
80
- "peer-focus-visible:text-on-accent-foreground",
81
- "peer-focus-visible:shadow-checked-focused",
82
- ],
83
- },
84
- },
85
- {
86
- variant: ["default", "accent", "outline"],
87
- checked: false,
88
- disabled: false,
89
- focused: true,
90
- class: {
91
- box: "border-selected-boundary",
92
- },
93
- },
94
- {
95
- variant: "default",
96
- checked: true,
97
- disabled: false,
98
- focused: true,
99
- class: {
100
- box: "border-selected-strong-boundary",
101
- },
102
- },
103
- {
104
- variant: ["accent", "outline"],
105
- checked: true,
106
- disabled: false,
107
- focused: true,
108
- class: {
109
- box: "text-on-accent-foreground border-selected-strong-boundary shadow-checked-focused",
110
- },
111
- },
112
- {
113
- variant: ["accent", "outline", "default"],
114
- disabled: true,
115
- class: {
116
- root: "text-default-background",
117
- box: "border-disabled-background bg-disabled-background",
118
- label: "text-disabled-foreground",
119
- },
120
- },
121
- ],
122
- defaultVariants: {
123
- variant: "default",
124
- checked: false,
125
- disabled: false,
126
- focused: false,
127
- },
128
- })