@startupjs-ui/button 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/button
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
+ * **button:** refactor Button component ([96de289](https://github.com/startupjs/startupjs-ui/commit/96de2890f0c080c4147d93275942a8ce5d670571))
package/README.mdx ADDED
@@ -0,0 +1,195 @@
1
+ import { useState } from 'react'
2
+ import { pug } from 'startupjs'
3
+ import Button, { _PropsJsonSchema as ButtonPropsJsonSchema } from './index'
4
+ import Div from '@startupjs-ui/div'
5
+ import Br from '@startupjs-ui/br'
6
+ import { Sandbox } from '@startupjs-ui/docs'
7
+ import {
8
+ faHouseUser,
9
+ faCheck,
10
+ faCoffee
11
+ } from '@fortawesome/free-solid-svg-icons'
12
+ import './index.mdx.cssx.styl'
13
+
14
+ # Button
15
+
16
+ Buttons allow users to perform an action or to navigate to another page. They have multiple styles for various needs, and are ideal for calling attention to where a user needs to do something in order to move forward in a flow.
17
+
18
+ ```jsx
19
+ import { Button } from 'startupjs-ui'
20
+ ```
21
+
22
+ ## Simple example
23
+ ```jsx example
24
+ const [counter, setCounter] = useState(0)
25
+
26
+ return (
27
+ <Button onPress={() => setCounter(counter + 1)}>
28
+ Clicked {counter} times
29
+ </Button>
30
+ )
31
+ ```
32
+
33
+ ## Async button
34
+
35
+ ```jsx example
36
+ async function onPress() {
37
+ await new Promise((resolve, reject) => {
38
+ setTimeout(() => {
39
+ resolve()
40
+ }, 5000)
41
+ })
42
+ }
43
+
44
+ return (
45
+ <Button onPress={onPress}>
46
+ Async button
47
+ </Button>
48
+ )
49
+ ```
50
+
51
+ ## Variants
52
+
53
+ The `variant` property is responsible for the external display of the button (affects the color of the icon and button text). Default value is `outlined`.
54
+
55
+ ```jsx example
56
+ return pug`
57
+ Div
58
+ Button(
59
+ onPress=() => console.log('pressed')
60
+ icon=faCoffee
61
+ ) Outlined
62
+ Br
63
+ Button(
64
+ onPress=() => console.log('pressed')
65
+ variant='flat'
66
+ icon=faCoffee
67
+ ) Flat
68
+ Br
69
+ Button(
70
+ onPress=() => console.log('pressed')
71
+ variant='text'
72
+ icon=faCoffee
73
+ ) Text
74
+ `
75
+ ```
76
+
77
+ ## Colors
78
+
79
+ Color is `secondary` by default. It can be changed by passing color name from the config colors to `color` property. Possible values of property can be found in the `Sandbox` section at the bottom of the page.
80
+
81
+ ```jsx example
82
+ return (
83
+ <Div>
84
+ <Button onPress={() => console.log('pressed')} variant='flat'>
85
+ Default dark
86
+ </Button>
87
+ <Br />
88
+ <Button onPress={() => console.log('pressed')} color='primary' variant='flat'>
89
+ Primary
90
+ </Button>
91
+ <Br />
92
+ <Button onPress={() => console.log('pressed')} color='success' variant='flat'>
93
+ Success
94
+ </Button>
95
+ <Br />
96
+ <Button onPress={() => console.log('pressed')} color='error' variant='flat'>
97
+ Error
98
+ </Button>
99
+ </Div>
100
+ )
101
+ ```
102
+
103
+ ## Sizes
104
+
105
+ The `size` property applies to the button size, icon and button text. Default value is `m`. Possible values of property can be found in the `Sandbox` section at the bottom of the page.
106
+
107
+ ```jsx example
108
+ return (
109
+ <Div>
110
+ <Button variant='flat' size='s' icon={faCheck}>
111
+ Small
112
+ </Button>
113
+ <Br />
114
+ <Button variant='flat' icon={faCheck}>
115
+ Default
116
+ </Button>
117
+ <Br />
118
+ <Button variant='flat' size='l' icon={faCheck}>
119
+ Large
120
+ </Button>
121
+ </Div>
122
+ )
123
+ ```
124
+
125
+ ## Icons
126
+
127
+ Icon can be added using `icon` property. Position of icon can be changed by passing `iconPosition` to component (`left` by default). To change icon color use the `iconStyleName` property.
128
+
129
+ In `.styl` file
130
+ ```stylus
131
+ .icon
132
+ color var(--color-text-success)
133
+ ```
134
+
135
+ ```jsx example
136
+ return (
137
+ <Div>
138
+ <Button icon={faCoffee} iconStyleName='icon'>
139
+ Icon on the left (default) with the changed color
140
+ </Button>
141
+ <Br />
142
+ <Button icon={faCoffee} iconPosition='right'>
143
+ Icon on the right
144
+ </Button>
145
+ </Div>
146
+ )
147
+ ```
148
+
149
+ ## Call to Action
150
+
151
+ Try to keep the variety of button combinations you use in your app to the minimum.
152
+
153
+ Use regular Button without any options by default. Whenever you want to emphasize a particular `Call-to-Action`, use a primary flat button.
154
+
155
+ ```jsx example
156
+ <Button color='primary' variant='flat'>
157
+ Destroy the Death Star!
158
+ </Button>
159
+ ```
160
+
161
+ ## Buttons order
162
+
163
+ Whenever specifiying several buttons on one line, stick to the order of buttons which people are used to from various OS'es. `Cancel` should be first, and then `OK`.
164
+
165
+ Never put multiple `Call-to-Action` buttons in one component. And try to keep the amount of them visible at the same time on one page to the minimum.
166
+
167
+ It's a much better UX decision to use regular buttons by default and decide which of them to make a `Call-to-Action` later.
168
+
169
+ ```jsx example
170
+ <Div row align='right'>
171
+ <Button>
172
+ Cancel
173
+ </Button>
174
+ <Button pushed color='primary' variant='flat'>
175
+ OK
176
+ </Button>
177
+ </Div>
178
+ ```
179
+
180
+ ## Sandbox
181
+
182
+ <Sandbox
183
+ Component={Button}
184
+ propsJsonSchema={ButtonPropsJsonSchema}
185
+ extraParams={{
186
+ icon: {
187
+ showIconSelect: true
188
+ }
189
+ }}
190
+ props={{
191
+ children: 'Press me',
192
+ onPress: () => alert('"onPress" event on "Button" component'),
193
+ onLongPress: () => alert('"onLongPress" event on "Button" component')
194
+ }}
195
+ />
@@ -0,0 +1,69 @@
1
+ // ----- CONFIG: $UI.Button
2
+
3
+ $this = merge({
4
+ heights: {
5
+ xs: 2u,
6
+ s: 3u,
7
+ m: 4u,
8
+ l: 5u,
9
+ xl: 6u,
10
+ xxl: 7u
11
+ },
12
+ fontSizes: {
13
+ xs: 1.25u,
14
+ s: $UI.fontSizes.caption,
15
+ m: $UI.fontSizes.body2,
16
+ l: $UI.fontSizes.body1,
17
+ xl: $UI.fontSizes.h5,
18
+ xxl: $UI.fontSizes.h4
19
+ },
20
+ lineHeights: {
21
+ xs: 1.75u,
22
+ s: $UI.lineHeights.caption,
23
+ m: $UI.lineHeights.body2,
24
+ l: $UI.lineHeights.body1,
25
+ xl: $UI.lineHeights.h5,
26
+ xxl: $UI.lineHeights.h4
27
+ },
28
+ outlinedBorderWidth: 1px,
29
+ iconMargins: {
30
+ xs: .5u,
31
+ s: .5u,
32
+ m: 1u,
33
+ l: 1u,
34
+ xl: 1.5u,
35
+ xxl: 1.5u
36
+ },
37
+ disabledOpacity: 0.25
38
+ }, $UI.Button, true)
39
+
40
+ // ----- COMPONENT
41
+
42
+ _sizes = ('xs' 's' 'm' 'l' 'xl' 'xxl')
43
+
44
+ .root
45
+ &.disabled
46
+ opacity $this.disabledOpacity
47
+
48
+ .label
49
+ fontFamily('normal', 500)
50
+
51
+ &.invisible
52
+ opacity 0
53
+
54
+ for size in _sizes
55
+ &.{size}
56
+ font-size $this.fontSizes[size]
57
+ line-height $this.lineHeights[size]
58
+
59
+ .icon
60
+ &.invisible
61
+ opacity 0
62
+
63
+ .loader
64
+ position absolute
65
+
66
+ // ----- JS EXPORTS
67
+
68
+ :export
69
+ config: $this
package/index.d.ts ADDED
@@ -0,0 +1,38 @@
1
+ /* eslint-disable */
2
+ // DO NOT MODIFY THIS FILE - IT IS AUTOMATICALLY GENERATED ON COMMITS.
3
+
4
+ import { type ComponentType, type JSXElementConstructor, type ReactNode } from 'react';
5
+ import { type GestureResponderEvent, type StyleProp, type TextStyle, type ViewStyle } from 'react-native';
6
+ declare const _default: ComponentType<ButtonProps>;
7
+ export default _default;
8
+ export declare const _PropsJsonSchema: {};
9
+ export interface ButtonProps {
10
+ /** color name @default 'secondary' */
11
+ color?: string;
12
+ /** variant @default 'outlined' */
13
+ variant?: 'flat' | 'outlined' | 'text';
14
+ /** size @default 'm' */
15
+ size?: 'xs' | 's' | 'm' | 'l' | 'xl' | 'xxl';
16
+ /** icon component */
17
+ icon?: ComponentType | JSXElementConstructor<any>;
18
+ /** shape @default 'rounded' */
19
+ shape?: 'squared' | 'rounded' | 'circle';
20
+ /** icon position relative to label @default 'left' */
21
+ iconPosition?: 'left' | 'right';
22
+ /** disable button */
23
+ disabled?: boolean;
24
+ /** button label text or a custom react node */
25
+ children?: ReactNode;
26
+ /** custom styles for root element */
27
+ style?: StyleProp<ViewStyle>;
28
+ /** custom styles for icon */
29
+ iconStyle?: StyleProp<TextStyle>;
30
+ /** custom styles for label text */
31
+ textStyle?: StyleProp<TextStyle>;
32
+ /** custom styles for hover state */
33
+ hoverStyle?: StyleProp<ViewStyle>;
34
+ /** custom styles for active state */
35
+ activeStyle?: StyleProp<ViewStyle>;
36
+ /** onPress handler */
37
+ onPress?: (event: GestureResponderEvent) => void | Promise<void>;
38
+ }
@@ -0,0 +1,2 @@
1
+ .icon
2
+ color var(--color-text-success)
package/index.tsx ADDED
@@ -0,0 +1,197 @@
1
+ import { Children, useState, type ComponentType, type JSXElementConstructor, type ReactNode } from 'react'
2
+ import { StyleSheet, type GestureResponderEvent, type StyleProp, type TextStyle, type ViewStyle } from 'react-native'
3
+ import { pug, observer, useIsMountedRef } from 'startupjs'
4
+ import { Colors, colorToRGBA, themed, useColors } from '@startupjs-ui/core'
5
+ import Div from '@startupjs-ui/div'
6
+ import Icon from '@startupjs-ui/icon'
7
+ import Loader from '@startupjs-ui/loader'
8
+ import Span from '@startupjs-ui/span'
9
+ import STYLES from './index.cssx.styl'
10
+
11
+ const {
12
+ config: {
13
+ heights, outlinedBorderWidth, iconMargins
14
+ }
15
+ } = STYLES
16
+
17
+ export default observer(themed('Button', Button))
18
+
19
+ export const _PropsJsonSchema = {/* ButtonProps */} // used in docs generation
20
+ export interface ButtonProps {
21
+ /** color name @default 'secondary' */
22
+ color?: string
23
+ /** variant @default 'outlined' */
24
+ variant?: 'flat' | 'outlined' | 'text'
25
+ /** size @default 'm' */
26
+ size?: 'xs' | 's' | 'm' | 'l' | 'xl' | 'xxl'
27
+ /** icon component */
28
+ icon?: ComponentType | JSXElementConstructor<any>
29
+ /** shape @default 'rounded' */
30
+ shape?: 'squared' | 'rounded' | 'circle'
31
+ /** icon position relative to label @default 'left' */
32
+ iconPosition?: 'left' | 'right'
33
+ /** disable button */
34
+ disabled?: boolean
35
+ /** button label text or a custom react node */
36
+ children?: ReactNode
37
+ /** custom styles for root element */
38
+ style?: StyleProp<ViewStyle>
39
+ /** custom styles for icon */
40
+ iconStyle?: StyleProp<TextStyle>
41
+ /** custom styles for label text */
42
+ textStyle?: StyleProp<TextStyle>
43
+ /** custom styles for hover state */
44
+ hoverStyle?: StyleProp<ViewStyle>
45
+ /** custom styles for active state */
46
+ activeStyle?: StyleProp<ViewStyle>
47
+ /** onPress handler */
48
+ onPress?: (event: GestureResponderEvent) => void | Promise<void>
49
+ }
50
+ function Button ({
51
+ style,
52
+ iconStyle,
53
+ textStyle,
54
+ children,
55
+ color = Colors.secondary,
56
+ variant = 'outlined',
57
+ size = 'm',
58
+ shape = 'rounded',
59
+ icon,
60
+ iconPosition = 'left',
61
+ disabled,
62
+ hoverStyle,
63
+ activeStyle,
64
+ onPress,
65
+ ...props
66
+ }: ButtonProps): ReactNode {
67
+ const isMountedRef = useIsMountedRef()
68
+ const [asyncActive, setAsyncActive] = useState(false)
69
+ const getColor = useColors()
70
+
71
+ function getFlatTextColorName () {
72
+ return getColor(`text-on-${color}`) ? `text-on-${color}` : 'text-on-color'
73
+ }
74
+
75
+ async function _onPress (event: GestureResponderEvent) {
76
+ if (!onPress) return
77
+ let resolved = false
78
+ const promise = onPress(event)
79
+ if (!(promise && promise.then)) return
80
+ promise.then(() => { resolved = true })
81
+ await new Promise((resolve) => setTimeout(resolve, 0))
82
+ if (resolved) return
83
+ setAsyncActive(true)
84
+ await promise
85
+ if (!isMountedRef.current) return
86
+ setAsyncActive(false)
87
+ }
88
+
89
+ if (!getColor(color)) console.error('Button component: Color for color property is incorrect. Use colors from Colors')
90
+
91
+ const isFlat = variant === 'flat'
92
+ const _color = getColor(color) as string | undefined
93
+ const textColor = isFlat ? getFlatTextColorName() : color
94
+ const _textColor = getColor(textColor) as string | undefined
95
+ const _colorString = _color ?? ''
96
+ const hasChildren = Children.count(children)
97
+ const height = heights[size] as number
98
+ const rootStyle: Record<string, any> = { height }
99
+ const rootExtraProps: Record<string, any> = {}
100
+ const iconWrapperStyle: Record<string, any> = {}
101
+ let extraHoverStyle: StyleProp<ViewStyle>
102
+ let extraActiveStyle: StyleProp<ViewStyle>
103
+
104
+ textStyle = StyleSheet.flatten<TextStyle>([
105
+ { color: _textColor as any },
106
+ textStyle
107
+ ])
108
+ iconStyle = StyleSheet.flatten<TextStyle>([
109
+ { color: _textColor as any },
110
+ iconStyle
111
+ ])
112
+
113
+ switch (variant) {
114
+ case 'flat':
115
+ rootStyle.backgroundColor = _color
116
+ break
117
+ case 'outlined':
118
+ rootStyle.borderWidth = outlinedBorderWidth
119
+ rootStyle.borderColor = colorToRGBA(_colorString, 0.5)
120
+ extraHoverStyle = { backgroundColor: colorToRGBA(_colorString, 0.05) }
121
+ extraActiveStyle = { backgroundColor: colorToRGBA(_colorString, 0.25) }
122
+ break
123
+ case 'text':
124
+ extraHoverStyle = { backgroundColor: colorToRGBA(_colorString, 0.05) }
125
+ extraActiveStyle = { backgroundColor: colorToRGBA(_colorString, 0.25) }
126
+ break
127
+ }
128
+
129
+ let padding: number
130
+ const quarterOfHeight = height / 4
131
+
132
+ if (hasChildren) {
133
+ padding = height / 2
134
+
135
+ switch (iconPosition) {
136
+ case 'left':
137
+ iconWrapperStyle.marginRight = iconMargins[size]
138
+ iconWrapperStyle.marginLeft = -quarterOfHeight
139
+ break
140
+ case 'right':
141
+ iconWrapperStyle.marginLeft = iconMargins[size]
142
+ iconWrapperStyle.marginRight = -quarterOfHeight
143
+ break
144
+ }
145
+ } else {
146
+ padding = quarterOfHeight
147
+ }
148
+
149
+ if (variant === 'outlined') padding -= outlinedBorderWidth
150
+
151
+ rootStyle.paddingLeft = padding
152
+ rootStyle.paddingRight = padding
153
+
154
+ return pug`
155
+ Div.root(
156
+ row
157
+ shape=shape
158
+ style=[rootStyle, style]
159
+ styleName=[
160
+ size,
161
+ { disabled }
162
+ ]
163
+ align='center'
164
+ vAlign='center'
165
+ reverse=iconPosition === 'right'
166
+ variant='highlight'
167
+ hoverStyle=extraHoverStyle ? [extraHoverStyle, hoverStyle] : hoverStyle
168
+ activeStyle=extraActiveStyle ? [extraActiveStyle, activeStyle] : activeStyle
169
+ disabled=asyncActive || disabled
170
+ onPress=onPress ? _onPress : undefined
171
+ ...rootExtraProps
172
+ ...props
173
+ )
174
+ if asyncActive
175
+ Div.loader
176
+ Loader(size='s' color=textColor)
177
+ if icon
178
+ Div.iconWrapper(
179
+ style=iconWrapperStyle
180
+ styleName=[
181
+ { 'with-label': hasChildren },
182
+ iconPosition
183
+ ]
184
+ )
185
+ Icon.icon(
186
+ style=iconStyle
187
+ styleName=[variant, { invisible: asyncActive }]
188
+ icon=icon
189
+ size=size
190
+ )
191
+ if children != null
192
+ Span.label(
193
+ style=[textStyle]
194
+ styleName=[size, { invisible: asyncActive }]
195
+ )= children
196
+ `
197
+ }
package/package.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "@startupjs-ui/button",
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/icon": "^0.1.3",
14
+ "@startupjs-ui/loader": "^0.1.3",
15
+ "@startupjs-ui/span": "^0.1.3"
16
+ },
17
+ "peerDependencies": {
18
+ "react": "*",
19
+ "react-native": "*",
20
+ "startupjs": "*"
21
+ },
22
+ "gitHead": "fd964ebc3892d3dd0a6c85438c0af619cc50c3f0"
23
+ }