@startupjs-ui/radio 0.1.3
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/CHANGELOG.md +20 -0
- package/README.mdx +117 -0
- package/helpers.ts +35 -0
- package/index.cssx.styl +54 -0
- package/index.d.ts +29 -0
- package/index.tsx +71 -0
- package/input.tsx +101 -0
- package/package.json +21 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Change Log
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
|
+
|
|
6
|
+
## [0.1.3](https://github.com/startupjs/startupjs-ui/compare/v0.1.2...v0.1.3) (2025-12-29)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @startupjs-ui/radio
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
## [0.1.2](https://github.com/startupjs/startupjs-ui/compare/v0.1.1...v0.1.2) (2025-12-29)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
### Features
|
|
18
|
+
|
|
19
|
+
* add mdx and docs packages. Refactor docs to get rid of any @startupjs/ui usage and use startupjs-ui instead ([703c926](https://github.com/startupjs/startupjs-ui/commit/703c92636efb0421ffd11783f692fc892b74018f))
|
|
20
|
+
* **radio:** refactor Radio component ([d114561](https://github.com/startupjs/startupjs-ui/commit/d1145614879b5e8cb2764e295126d659b618d256))
|
package/README.mdx
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { useState } from 'react'
|
|
2
|
+
import Radio, { _PropsJsonSchema as RadioPropsJsonSchema } from './index'
|
|
3
|
+
import { Sandbox } from '@startupjs-ui/docs'
|
|
4
|
+
|
|
5
|
+
# Radio
|
|
6
|
+
|
|
7
|
+
Radio button allows the user to select one option from a list.
|
|
8
|
+
|
|
9
|
+
```jsx
|
|
10
|
+
import { Radio } from 'startupjs-ui'
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Basic usage
|
|
14
|
+
|
|
15
|
+
```jsx example
|
|
16
|
+
const [checked, setChecked] = useState()
|
|
17
|
+
return (
|
|
18
|
+
<Radio
|
|
19
|
+
value={checked}
|
|
20
|
+
onChange={value => setChecked(value)}
|
|
21
|
+
options={['js', 'php']}
|
|
22
|
+
/>
|
|
23
|
+
)
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Custom label and description
|
|
27
|
+
|
|
28
|
+
```jsx example
|
|
29
|
+
const [checked, setChecked] = useState()
|
|
30
|
+
return (
|
|
31
|
+
<Radio
|
|
32
|
+
value={checked}
|
|
33
|
+
onChange={value => setChecked(value)}
|
|
34
|
+
options={[
|
|
35
|
+
{
|
|
36
|
+
value: 'js',
|
|
37
|
+
label: 'JavaScript',
|
|
38
|
+
description: 'Scripting language which helps you create interactive web pages'
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
value: 'php',
|
|
42
|
+
label: 'PHP',
|
|
43
|
+
description: 'Server side scripting language'
|
|
44
|
+
}
|
|
45
|
+
]}
|
|
46
|
+
/>
|
|
47
|
+
)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Disabled
|
|
51
|
+
|
|
52
|
+
```jsx example
|
|
53
|
+
const [checked, setChecked] = useState()
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<Radio
|
|
57
|
+
value={checked}
|
|
58
|
+
onChange={value => setChecked(value)}
|
|
59
|
+
options={[
|
|
60
|
+
{
|
|
61
|
+
value: 'js',
|
|
62
|
+
label: 'JavaScript'
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
value: 'php',
|
|
66
|
+
label: 'PHP'
|
|
67
|
+
}
|
|
68
|
+
]}
|
|
69
|
+
disabled
|
|
70
|
+
/>
|
|
71
|
+
)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Readonly
|
|
75
|
+
|
|
76
|
+
```jsx example
|
|
77
|
+
const [checked, setChecked] = useState('js')
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<Radio
|
|
81
|
+
value={checked}
|
|
82
|
+
onChange={value => setChecked(value)}
|
|
83
|
+
options={[
|
|
84
|
+
{
|
|
85
|
+
value: 'js',
|
|
86
|
+
label: 'JavaScript'
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
value: 'php',
|
|
90
|
+
label: 'PHP'
|
|
91
|
+
}
|
|
92
|
+
]}
|
|
93
|
+
readonly
|
|
94
|
+
/>
|
|
95
|
+
)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Sandbox
|
|
99
|
+
|
|
100
|
+
<Sandbox
|
|
101
|
+
Component={Radio}
|
|
102
|
+
propsJsonSchema={RadioPropsJsonSchema}
|
|
103
|
+
props={{
|
|
104
|
+
options: [
|
|
105
|
+
{
|
|
106
|
+
value: 'red',
|
|
107
|
+
label: 'Red'
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
value: 'blue',
|
|
111
|
+
label: 'Blue'
|
|
112
|
+
}
|
|
113
|
+
],
|
|
114
|
+
value: 'red',
|
|
115
|
+
onChange: value => alert('Enter \"' + value + '\" in the value input')
|
|
116
|
+
}}
|
|
117
|
+
/>
|
package/helpers.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export interface RadioOptionObject {
|
|
2
|
+
/** Stored value (used for equality / onChange) */
|
|
3
|
+
value?: any
|
|
4
|
+
/** Visible label */
|
|
5
|
+
label?: string
|
|
6
|
+
/** Optional description under the label */
|
|
7
|
+
description?: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export type RadioOption = string | number | RadioOptionObject
|
|
11
|
+
export type RadioValue = string | number | RadioOptionObject | null | undefined
|
|
12
|
+
|
|
13
|
+
export function getOptionLabel (option: RadioOption): any {
|
|
14
|
+
return (option as any)?.label || option
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function getOptionDescription (option: RadioOption): any {
|
|
18
|
+
return (option as any)?.description
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function stringifyValue (option: any): string {
|
|
22
|
+
return JSON.stringify(option?.value || option)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function parseValue (value: any): any {
|
|
26
|
+
return JSON.parse(value)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function getLabelFromValue (value: RadioValue, options: RadioOption[]): any {
|
|
30
|
+
for (const option of options) {
|
|
31
|
+
if (stringifyValue(value) === stringifyValue(option)) {
|
|
32
|
+
return getOptionLabel(option)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
package/index.cssx.styl
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
$radioSize = 2u
|
|
2
|
+
|
|
3
|
+
$this = merge({
|
|
4
|
+
size: 2u,
|
|
5
|
+
borderWidth: 2px,
|
|
6
|
+
borderColor: var(--color-border-main)
|
|
7
|
+
}, $UI.Radio, true)
|
|
8
|
+
|
|
9
|
+
.root
|
|
10
|
+
&.row
|
|
11
|
+
flex-direction row
|
|
12
|
+
|
|
13
|
+
.input
|
|
14
|
+
&.row
|
|
15
|
+
margin-left 2u
|
|
16
|
+
|
|
17
|
+
&.first
|
|
18
|
+
margin-left 0
|
|
19
|
+
|
|
20
|
+
.input-input
|
|
21
|
+
align-self flex-start
|
|
22
|
+
padding-top 1u
|
|
23
|
+
padding-bottom @padding-top
|
|
24
|
+
|
|
25
|
+
.radio
|
|
26
|
+
width: $this.size
|
|
27
|
+
height @width
|
|
28
|
+
border-color: $this.borderColor
|
|
29
|
+
radius(circle)
|
|
30
|
+
border-width: $this.borderWidth
|
|
31
|
+
justify-content center
|
|
32
|
+
align-items center
|
|
33
|
+
|
|
34
|
+
&.checked
|
|
35
|
+
border-color var(--color-border-primary)
|
|
36
|
+
|
|
37
|
+
&.error
|
|
38
|
+
border-color var(--color-border-error)
|
|
39
|
+
|
|
40
|
+
.circle
|
|
41
|
+
radius(circle)
|
|
42
|
+
width $this.circleSize ? $this.circleSize : ($this.size / 2)
|
|
43
|
+
height @width
|
|
44
|
+
background-color var(--color-bg-primary)
|
|
45
|
+
|
|
46
|
+
&.error
|
|
47
|
+
background-color var(--color-bg-error)
|
|
48
|
+
|
|
49
|
+
.container
|
|
50
|
+
margin-left 1u
|
|
51
|
+
flex-shrink 1
|
|
52
|
+
|
|
53
|
+
.description
|
|
54
|
+
font(caption)
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
// DO NOT MODIFY THIS FILE - IT IS AUTOMATICALLY GENERATED ON COMMITS.
|
|
3
|
+
|
|
4
|
+
import { type StyleProp, type ViewStyle } from 'react-native';
|
|
5
|
+
import { type RadioOption, type RadioValue } from './helpers';
|
|
6
|
+
import './index.cssx.styl';
|
|
7
|
+
declare const _default: import("react").ComponentType<RadioProps>;
|
|
8
|
+
export default _default;
|
|
9
|
+
export declare const _PropsJsonSchema: {};
|
|
10
|
+
export interface RadioProps {
|
|
11
|
+
/** Custom styles for the root wrapper */
|
|
12
|
+
style?: StyleProp<ViewStyle>;
|
|
13
|
+
/** Custom styles for each option item */
|
|
14
|
+
inputStyle?: StyleProp<ViewStyle>;
|
|
15
|
+
/** Current selected value */
|
|
16
|
+
value?: RadioValue;
|
|
17
|
+
/** List of options @default [] */
|
|
18
|
+
options?: RadioOption[];
|
|
19
|
+
/** Render options in a row @default false */
|
|
20
|
+
row?: boolean;
|
|
21
|
+
/** Disable interactions @default false */
|
|
22
|
+
disabled?: boolean;
|
|
23
|
+
/** Render as non-interactive @default false */
|
|
24
|
+
readonly?: boolean;
|
|
25
|
+
/** Change handler */
|
|
26
|
+
onChange?: (value: any) => void;
|
|
27
|
+
/** Error flag @private */
|
|
28
|
+
_hasError?: boolean;
|
|
29
|
+
}
|
package/index.tsx
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { type ReactNode } from 'react'
|
|
2
|
+
import { type StyleProp, type ViewStyle } from 'react-native'
|
|
3
|
+
import { pug, observer } from 'startupjs'
|
|
4
|
+
import Div from '@startupjs-ui/div'
|
|
5
|
+
import Input from './input'
|
|
6
|
+
import {
|
|
7
|
+
type RadioOption,
|
|
8
|
+
type RadioValue,
|
|
9
|
+
getOptionLabel,
|
|
10
|
+
getOptionDescription,
|
|
11
|
+
stringifyValue
|
|
12
|
+
} from './helpers'
|
|
13
|
+
import './index.cssx.styl'
|
|
14
|
+
|
|
15
|
+
export default observer(Radio)
|
|
16
|
+
|
|
17
|
+
export const _PropsJsonSchema = {/* RadioProps */}
|
|
18
|
+
|
|
19
|
+
export interface RadioProps {
|
|
20
|
+
/** Custom styles for the root wrapper */
|
|
21
|
+
style?: StyleProp<ViewStyle>
|
|
22
|
+
/** Custom styles for each option item */
|
|
23
|
+
inputStyle?: StyleProp<ViewStyle>
|
|
24
|
+
/** Current selected value */
|
|
25
|
+
value?: RadioValue
|
|
26
|
+
/** List of options @default [] */
|
|
27
|
+
options?: RadioOption[]
|
|
28
|
+
/** Render options in a row @default false */
|
|
29
|
+
row?: boolean
|
|
30
|
+
/** Disable interactions @default false */
|
|
31
|
+
disabled?: boolean
|
|
32
|
+
/** Render as non-interactive @default false */
|
|
33
|
+
readonly?: boolean
|
|
34
|
+
/** Change handler */
|
|
35
|
+
onChange?: (value: any) => void
|
|
36
|
+
/** Error flag @private */
|
|
37
|
+
_hasError?: boolean
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function Radio ({
|
|
41
|
+
style,
|
|
42
|
+
inputStyle,
|
|
43
|
+
value,
|
|
44
|
+
options = [],
|
|
45
|
+
row = false,
|
|
46
|
+
disabled = false,
|
|
47
|
+
readonly = false,
|
|
48
|
+
_hasError,
|
|
49
|
+
...props
|
|
50
|
+
}: RadioProps): ReactNode {
|
|
51
|
+
return pug`
|
|
52
|
+
Div.root(style=style styleName={ row })
|
|
53
|
+
each option, index in options
|
|
54
|
+
- const optionValue = stringifyValue(option)
|
|
55
|
+
- const checked = optionValue === stringifyValue(value)
|
|
56
|
+
- const error = _hasError && (value ? checked : true)
|
|
57
|
+
|
|
58
|
+
Input.input(
|
|
59
|
+
key=optionValue
|
|
60
|
+
style=inputStyle
|
|
61
|
+
styleName={ row, first: !index }
|
|
62
|
+
checked=checked
|
|
63
|
+
value=optionValue
|
|
64
|
+
description=getOptionDescription(option)
|
|
65
|
+
error=error
|
|
66
|
+
disabled=disabled
|
|
67
|
+
readonly=readonly
|
|
68
|
+
...props
|
|
69
|
+
)= getOptionLabel(option)
|
|
70
|
+
`
|
|
71
|
+
}
|
package/input.tsx
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { type ReactNode, useRef } from 'react'
|
|
2
|
+
import { Animated, Easing, Platform, type StyleProp, type ViewStyle } from 'react-native'
|
|
3
|
+
import { pug, observer, useDidUpdate } from 'startupjs'
|
|
4
|
+
import Div from '@startupjs-ui/div'
|
|
5
|
+
import Span from '@startupjs-ui/span'
|
|
6
|
+
import { themed } from '@startupjs-ui/core'
|
|
7
|
+
import { parseValue } from './helpers'
|
|
8
|
+
import './index.cssx.styl'
|
|
9
|
+
|
|
10
|
+
const IS_ANDROID = Platform.OS === 'android'
|
|
11
|
+
const ANIMATION_TIMING = 100
|
|
12
|
+
// workaround for android
|
|
13
|
+
// https://github.com/facebook/react-native/issues/6278
|
|
14
|
+
const MIN_SCALE_RATIO = IS_ANDROID ? 0.1 : 0
|
|
15
|
+
const MAX_SCALE_RATIO = 1
|
|
16
|
+
|
|
17
|
+
export interface RadioInputProps {
|
|
18
|
+
/** Custom styles for the input wrapper */
|
|
19
|
+
style?: StyleProp<ViewStyle>
|
|
20
|
+
/** JSON-stringified option value */
|
|
21
|
+
value: string
|
|
22
|
+
/** Optional description displayed under the label */
|
|
23
|
+
description?: string
|
|
24
|
+
/** Label content */
|
|
25
|
+
children?: ReactNode
|
|
26
|
+
/** Checked state */
|
|
27
|
+
checked?: boolean
|
|
28
|
+
/** Disable interactions */
|
|
29
|
+
disabled?: boolean
|
|
30
|
+
/** Render as non-interactive */
|
|
31
|
+
readonly?: boolean
|
|
32
|
+
/** Change handler */
|
|
33
|
+
onChange?: (value: any) => void
|
|
34
|
+
/** Error state */
|
|
35
|
+
error?: boolean
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const RadioInput = function ({
|
|
39
|
+
style,
|
|
40
|
+
value,
|
|
41
|
+
description,
|
|
42
|
+
children,
|
|
43
|
+
checked,
|
|
44
|
+
disabled,
|
|
45
|
+
readonly,
|
|
46
|
+
onChange,
|
|
47
|
+
error
|
|
48
|
+
}: RadioInputProps): ReactNode {
|
|
49
|
+
const animation = useRef(
|
|
50
|
+
new Animated.Value(checked ? MAX_SCALE_RATIO : MIN_SCALE_RATIO)
|
|
51
|
+
).current
|
|
52
|
+
|
|
53
|
+
useDidUpdate(() => {
|
|
54
|
+
if (checked) {
|
|
55
|
+
Animated.timing(
|
|
56
|
+
animation,
|
|
57
|
+
{
|
|
58
|
+
toValue: MAX_SCALE_RATIO,
|
|
59
|
+
duration: ANIMATION_TIMING,
|
|
60
|
+
easing: Easing.linear,
|
|
61
|
+
useNativeDriver: true
|
|
62
|
+
}
|
|
63
|
+
).start()
|
|
64
|
+
} else {
|
|
65
|
+
Animated.timing(
|
|
66
|
+
animation,
|
|
67
|
+
{
|
|
68
|
+
toValue: MIN_SCALE_RATIO,
|
|
69
|
+
duration: ANIMATION_TIMING,
|
|
70
|
+
easing: Easing.linear,
|
|
71
|
+
useNativeDriver: true
|
|
72
|
+
}
|
|
73
|
+
).start()
|
|
74
|
+
}
|
|
75
|
+
}, [checked])
|
|
76
|
+
|
|
77
|
+
return pug`
|
|
78
|
+
Div.input-input(
|
|
79
|
+
style=style
|
|
80
|
+
vAlign='center'
|
|
81
|
+
disabled=disabled || readonly
|
|
82
|
+
onPress=() => onChange && onChange(parseValue(value))
|
|
83
|
+
accessibilityRole='radio'
|
|
84
|
+
row
|
|
85
|
+
)
|
|
86
|
+
Div.radio(
|
|
87
|
+
styleName=[{ checked, error }]
|
|
88
|
+
)
|
|
89
|
+
Animated.View.circle(
|
|
90
|
+
style={ transform: [{ scale: animation }] }
|
|
91
|
+
styleName={ error }
|
|
92
|
+
)
|
|
93
|
+
if children
|
|
94
|
+
Div.container
|
|
95
|
+
Span.label= children
|
|
96
|
+
if description
|
|
97
|
+
Span.description(description)= description
|
|
98
|
+
`
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export default observer(themed('Radio', RadioInput))
|
package/package.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@startupjs-ui/radio",
|
|
3
|
+
"version": "0.1.3",
|
|
4
|
+
"publishConfig": {
|
|
5
|
+
"access": "public"
|
|
6
|
+
},
|
|
7
|
+
"main": "index.tsx",
|
|
8
|
+
"types": "index.d.ts",
|
|
9
|
+
"type": "module",
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"@startupjs-ui/core": "^0.1.3",
|
|
12
|
+
"@startupjs-ui/div": "^0.1.3",
|
|
13
|
+
"@startupjs-ui/span": "^0.1.3"
|
|
14
|
+
},
|
|
15
|
+
"peerDependencies": {
|
|
16
|
+
"react": "*",
|
|
17
|
+
"react-native": "*",
|
|
18
|
+
"startupjs": "*"
|
|
19
|
+
},
|
|
20
|
+
"gitHead": "fd964ebc3892d3dd0a6c85438c0af619cc50c3f0"
|
|
21
|
+
}
|