@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 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
+ }
@@ -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
+ }