@butternutbox/pawprint-native 0.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.
- package/.turbo/turbo-build.log +30 -0
- package/COMPONENT_GUIDELINES.md +610 -0
- package/README.md +72 -0
- package/dist/ibm-plex-sans-condensed-400-normal-I2XLJNNB.woff2 +0 -0
- package/dist/ibm-plex-sans-condensed-500-normal-IEQBNVGX.woff2 +0 -0
- package/dist/ibm-plex-sans-condensed-600-normal-UX5ZU5T6.woff2 +0 -0
- package/dist/ibm-plex-sans-condensed-700-normal-4PFYFTSO.woff2 +0 -0
- package/dist/ida-narrow-500-normal-C6I2PK4T.woff2 +0 -0
- package/dist/ida-narrow-700-normal-UPHPRIN6.woff2 +0 -0
- package/dist/index.cjs +2686 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +780 -0
- package/dist/index.d.ts +780 -0
- package/dist/index.js +2617 -0
- package/dist/index.js.map +1 -0
- package/eslint.config.js +3 -0
- package/llms.txt +458 -0
- package/package.json +57 -0
- package/src/components/atoms/Avatar/Avatar.stories.tsx +125 -0
- package/src/components/atoms/Avatar/Avatar.tsx +159 -0
- package/src/components/atoms/Avatar/index.ts +7 -0
- package/src/components/atoms/Badge/Badge.stories.tsx +231 -0
- package/src/components/atoms/Badge/Badge.tsx +184 -0
- package/src/components/atoms/Badge/index.ts +2 -0
- package/src/components/atoms/Button/Button.stories.tsx +145 -0
- package/src/components/atoms/Button/Button.tsx +261 -0
- package/src/components/atoms/Button/index.ts +7 -0
- package/src/components/atoms/Hint/Hint.stories.tsx +84 -0
- package/src/components/atoms/Hint/Hint.tsx +59 -0
- package/src/components/atoms/Hint/index.ts +2 -0
- package/src/components/atoms/Icon/Icon.stories.tsx +200 -0
- package/src/components/atoms/Icon/Icon.tsx +112 -0
- package/src/components/atoms/Icon/index.ts +8 -0
- package/src/components/atoms/IconButton/IconButton.stories.tsx +162 -0
- package/src/components/atoms/IconButton/IconButton.tsx +227 -0
- package/src/components/atoms/IconButton/index.ts +7 -0
- package/src/components/atoms/Illustration/Illustration.stories.tsx +167 -0
- package/src/components/atoms/Illustration/Illustration.tsx +81 -0
- package/src/components/atoms/Illustration/index.ts +6 -0
- package/src/components/atoms/Input/Input.stories.tsx +142 -0
- package/src/components/atoms/Input/Input.tsx +110 -0
- package/src/components/atoms/Input/InputDescription.tsx +49 -0
- package/src/components/atoms/Input/InputError.tsx +39 -0
- package/src/components/atoms/Input/InputField.tsx +119 -0
- package/src/components/atoms/Input/InputLabel.tsx +61 -0
- package/src/components/atoms/Input/index.ts +10 -0
- package/src/components/atoms/Link/Link.stories.tsx +119 -0
- package/src/components/atoms/Link/Link.tsx +118 -0
- package/src/components/atoms/Link/index.ts +2 -0
- package/src/components/atoms/Logo/Logo.registry.ts +39 -0
- package/src/components/atoms/Logo/Logo.tsx +68 -0
- package/src/components/atoms/Logo/index.ts +4 -0
- package/src/components/atoms/Spinner/Spinner.stories.tsx +98 -0
- package/src/components/atoms/Spinner/Spinner.tsx +91 -0
- package/src/components/atoms/Spinner/index.ts +2 -0
- package/src/components/atoms/Switch/Switch.stories.tsx +120 -0
- package/src/components/atoms/Switch/Switch.tsx +196 -0
- package/src/components/atoms/Switch/index.ts +2 -0
- package/src/components/atoms/Tag/Tag.stories.tsx +89 -0
- package/src/components/atoms/Tag/Tag.tsx +122 -0
- package/src/components/atoms/Tag/index.ts +2 -0
- package/src/components/atoms/Typography/Typography.stories.tsx +315 -0
- package/src/components/atoms/Typography/Typography.tsx +284 -0
- package/src/components/atoms/Typography/index.ts +2 -0
- package/src/components/atoms/index.ts +14 -0
- package/src/components/index.ts +2 -0
- package/src/components/molecules/ButtonDock/ButtonDock.stories.tsx +95 -0
- package/src/components/molecules/ButtonDock/ButtonDock.tsx +148 -0
- package/src/components/molecules/ButtonDock/index.ts +2 -0
- package/src/components/molecules/ButtonGroup/ButtonGroup.stories.tsx +82 -0
- package/src/components/molecules/ButtonGroup/ButtonGroup.tsx +94 -0
- package/src/components/molecules/ButtonGroup/index.ts +2 -0
- package/src/components/molecules/Checkbox/Checkbox.stories.tsx +148 -0
- package/src/components/molecules/Checkbox/Checkbox.tsx +279 -0
- package/src/components/molecules/Checkbox/CheckboxGroup.tsx +53 -0
- package/src/components/molecules/Checkbox/index.ts +4 -0
- package/src/components/molecules/Radio/Radio.stories.tsx +182 -0
- package/src/components/molecules/Radio/Radio.tsx +249 -0
- package/src/components/molecules/Radio/RadioGroup.tsx +142 -0
- package/src/components/molecules/Radio/index.ts +4 -0
- package/src/components/molecules/SegmentedControl/SegmentedControl.stories.tsx +151 -0
- package/src/components/molecules/SegmentedControl/SegmentedControl.tsx +323 -0
- package/src/components/molecules/SegmentedControl/index.ts +5 -0
- package/src/components/molecules/Slider/Slider.stories.tsx +144 -0
- package/src/components/molecules/Slider/Slider.tsx +303 -0
- package/src/components/molecules/Slider/index.ts +2 -0
- package/src/components/molecules/index.ts +6 -0
- package/src/fonts/ibm-plex-sans-condensed-400-normal.woff2 +0 -0
- package/src/fonts/ibm-plex-sans-condensed-500-normal.woff2 +0 -0
- package/src/fonts/ibm-plex-sans-condensed-600-normal.woff2 +0 -0
- package/src/fonts/ibm-plex-sans-condensed-700-normal.woff2 +0 -0
- package/src/fonts/ida-narrow-500-normal.woff2 +0 -0
- package/src/fonts/ida-narrow-700-normal.woff2 +0 -0
- package/src/fonts/index.ts +49 -0
- package/src/index.ts +9 -0
- package/src/theme/PawprintProvider.tsx +26 -0
- package/src/theme/ThemeProvider.tsx +63 -0
- package/src/theme/index.ts +5 -0
- package/src/theme/theme.ts +3 -0
- package/src/theme/utils.ts +31 -0
- package/src/types/fonts.d.ts +4 -0
- package/src/types/index.ts +1 -0
- package/src/types/theme.ts +24 -0
- package/tsconfig.json +5 -0
- package/tsup.config.ts +11 -0
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import { View, StyleSheet } from "react-native"
|
|
3
|
+
import { Button } from "./Button"
|
|
4
|
+
import type { ButtonProps } from "./Button"
|
|
5
|
+
import { Typography } from "../Typography"
|
|
6
|
+
|
|
7
|
+
export default {
|
|
8
|
+
title: "Atoms/Button",
|
|
9
|
+
component: Button,
|
|
10
|
+
argTypes: {
|
|
11
|
+
variant: {
|
|
12
|
+
control: { type: "select" },
|
|
13
|
+
options: ["filled", "outlined", "text"],
|
|
14
|
+
description: "Visual style variant"
|
|
15
|
+
},
|
|
16
|
+
size: {
|
|
17
|
+
control: { type: "select" },
|
|
18
|
+
options: ["sm", "md", "lg"],
|
|
19
|
+
description: "Size of the button"
|
|
20
|
+
},
|
|
21
|
+
colour: {
|
|
22
|
+
control: { type: "select" },
|
|
23
|
+
options: ["primary", "secondary"],
|
|
24
|
+
description: "Colour scheme"
|
|
25
|
+
},
|
|
26
|
+
loading: {
|
|
27
|
+
control: { type: "boolean" },
|
|
28
|
+
description: "Shows spinner and disables interaction"
|
|
29
|
+
},
|
|
30
|
+
fullWidth: {
|
|
31
|
+
control: { type: "boolean" },
|
|
32
|
+
description: "Stretches to fill container width"
|
|
33
|
+
},
|
|
34
|
+
disabled: {
|
|
35
|
+
control: { type: "boolean" },
|
|
36
|
+
description: "Prevents interaction"
|
|
37
|
+
},
|
|
38
|
+
children: {
|
|
39
|
+
control: { type: "text" },
|
|
40
|
+
description: "Button label text"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export const Playground = (args: ButtonProps) => <Button {...args} />
|
|
46
|
+
Playground.args = {
|
|
47
|
+
children: "Button",
|
|
48
|
+
variant: "filled",
|
|
49
|
+
size: "md",
|
|
50
|
+
colour: "primary",
|
|
51
|
+
loading: false,
|
|
52
|
+
fullWidth: false,
|
|
53
|
+
disabled: false
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export const AllVariants = () => (
|
|
57
|
+
<View style={styles.column}>
|
|
58
|
+
{(["filled", "outlined", "text"] as const).map((variant) => (
|
|
59
|
+
<View key={variant} style={styles.section}>
|
|
60
|
+
<Typography size="sm" weight="semiBold" color="tertiary">
|
|
61
|
+
{variant}
|
|
62
|
+
</Typography>
|
|
63
|
+
<View style={styles.row}>
|
|
64
|
+
{(["primary", "secondary"] as const).map((colour) => (
|
|
65
|
+
<Button key={colour} variant={variant} colour={colour}>
|
|
66
|
+
{colour}
|
|
67
|
+
</Button>
|
|
68
|
+
))}
|
|
69
|
+
</View>
|
|
70
|
+
</View>
|
|
71
|
+
))}
|
|
72
|
+
</View>
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
export const AllSizes = () => (
|
|
76
|
+
<View style={styles.column}>
|
|
77
|
+
{(["filled", "outlined", "text"] as const).map((variant) => (
|
|
78
|
+
<View key={variant} style={styles.section}>
|
|
79
|
+
<Typography size="sm" weight="semiBold" color="tertiary">
|
|
80
|
+
{variant}
|
|
81
|
+
</Typography>
|
|
82
|
+
<View style={styles.row}>
|
|
83
|
+
{(["sm", "md", "lg"] as const).map((size) => (
|
|
84
|
+
<Button key={size} variant={variant} size={size}>
|
|
85
|
+
{size}
|
|
86
|
+
</Button>
|
|
87
|
+
))}
|
|
88
|
+
</View>
|
|
89
|
+
</View>
|
|
90
|
+
))}
|
|
91
|
+
</View>
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
export const AllStates = () => (
|
|
95
|
+
<View style={styles.column}>
|
|
96
|
+
{(["filled", "outlined", "text"] as const).map((variant) => (
|
|
97
|
+
<View key={variant} style={styles.section}>
|
|
98
|
+
{(["primary", "secondary"] as const).map((colour) => (
|
|
99
|
+
<View key={colour} style={styles.section}>
|
|
100
|
+
<Typography size="sm" weight="semiBold" color="tertiary">
|
|
101
|
+
{variant} / {colour}
|
|
102
|
+
</Typography>
|
|
103
|
+
<View style={styles.row}>
|
|
104
|
+
<Button variant={variant} colour={colour}>
|
|
105
|
+
Default
|
|
106
|
+
</Button>
|
|
107
|
+
<Button variant={variant} colour={colour} disabled>
|
|
108
|
+
Disabled
|
|
109
|
+
</Button>
|
|
110
|
+
<Button variant={variant} colour={colour} loading>
|
|
111
|
+
Loading
|
|
112
|
+
</Button>
|
|
113
|
+
</View>
|
|
114
|
+
</View>
|
|
115
|
+
))}
|
|
116
|
+
</View>
|
|
117
|
+
))}
|
|
118
|
+
</View>
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
export const FullWidth = () => (
|
|
122
|
+
<View style={styles.column}>
|
|
123
|
+
{(["filled", "outlined", "text"] as const).map((variant) => (
|
|
124
|
+
<Button key={variant} variant={variant} fullWidth>
|
|
125
|
+
{variant} full width
|
|
126
|
+
</Button>
|
|
127
|
+
))}
|
|
128
|
+
</View>
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
const styles = StyleSheet.create({
|
|
132
|
+
column: {
|
|
133
|
+
flexDirection: "column",
|
|
134
|
+
gap: 24
|
|
135
|
+
},
|
|
136
|
+
section: {
|
|
137
|
+
flexDirection: "column",
|
|
138
|
+
gap: 8
|
|
139
|
+
},
|
|
140
|
+
row: {
|
|
141
|
+
flexDirection: "row",
|
|
142
|
+
gap: 12,
|
|
143
|
+
alignItems: "center"
|
|
144
|
+
}
|
|
145
|
+
})
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import { Pressable, View, PressableProps } from "react-native"
|
|
3
|
+
import styled from "@emotion/native"
|
|
4
|
+
import { useTheme } from "@emotion/react"
|
|
5
|
+
import { Typography } from "../Typography"
|
|
6
|
+
import { Spinner } from "../Spinner"
|
|
7
|
+
import { resolveFont } from "../../../fonts"
|
|
8
|
+
|
|
9
|
+
type ButtonVariant = "filled" | "outlined" | "text"
|
|
10
|
+
type ButtonSize = "sm" | "md" | "lg"
|
|
11
|
+
type ButtonColour = "primary" | "secondary"
|
|
12
|
+
|
|
13
|
+
type ButtonOwnProps = {
|
|
14
|
+
variant?: ButtonVariant
|
|
15
|
+
size?: ButtonSize
|
|
16
|
+
colour?: ButtonColour
|
|
17
|
+
loading?: boolean
|
|
18
|
+
fullWidth?: boolean
|
|
19
|
+
startIcon?: React.ReactNode
|
|
20
|
+
endIcon?: React.ReactNode
|
|
21
|
+
children?: React.ReactNode
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
type ButtonProps = ButtonOwnProps &
|
|
25
|
+
Omit<PressableProps, keyof ButtonOwnProps | "children">
|
|
26
|
+
|
|
27
|
+
const parseTokenValue = (value: string): number => parseFloat(value)
|
|
28
|
+
|
|
29
|
+
const sizeToTypographyKey: Record<ButtonSize, string> = {
|
|
30
|
+
lg: "large",
|
|
31
|
+
md: "medium",
|
|
32
|
+
sm: "small"
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const IconWrapper = styled(View)({
|
|
36
|
+
alignItems: "center",
|
|
37
|
+
justifyContent: "center",
|
|
38
|
+
flexShrink: 0
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
const StyledButton = styled(Pressable)<{
|
|
42
|
+
buttonHeight: number
|
|
43
|
+
buttonMinWidth: number
|
|
44
|
+
buttonPaddingHorizontal: number
|
|
45
|
+
buttonPaddingVertical: number
|
|
46
|
+
buttonGap: number
|
|
47
|
+
buttonBorderRadius: number
|
|
48
|
+
buttonBgColor: string
|
|
49
|
+
buttonOpacity: number
|
|
50
|
+
buttonBorderWidth?: number
|
|
51
|
+
buttonBorderColor?: string
|
|
52
|
+
buttonFullWidth: boolean
|
|
53
|
+
}>(
|
|
54
|
+
({
|
|
55
|
+
buttonHeight,
|
|
56
|
+
buttonMinWidth,
|
|
57
|
+
buttonPaddingHorizontal,
|
|
58
|
+
buttonPaddingVertical,
|
|
59
|
+
buttonGap,
|
|
60
|
+
buttonBorderRadius,
|
|
61
|
+
buttonBgColor,
|
|
62
|
+
buttonOpacity,
|
|
63
|
+
buttonBorderWidth,
|
|
64
|
+
buttonBorderColor,
|
|
65
|
+
buttonFullWidth
|
|
66
|
+
}) => ({
|
|
67
|
+
flexDirection: "row",
|
|
68
|
+
alignItems: "center",
|
|
69
|
+
justifyContent: "center",
|
|
70
|
+
position: "relative",
|
|
71
|
+
height: buttonHeight,
|
|
72
|
+
minWidth: buttonMinWidth,
|
|
73
|
+
paddingHorizontal: buttonPaddingHorizontal,
|
|
74
|
+
paddingVertical: buttonPaddingVertical,
|
|
75
|
+
gap: buttonGap,
|
|
76
|
+
borderRadius: buttonBorderRadius,
|
|
77
|
+
backgroundColor: buttonBgColor,
|
|
78
|
+
opacity: buttonOpacity,
|
|
79
|
+
...(buttonBorderWidth
|
|
80
|
+
? { borderWidth: buttonBorderWidth, borderColor: buttonBorderColor }
|
|
81
|
+
: {}),
|
|
82
|
+
...(buttonFullWidth ? { width: "100%" } : {})
|
|
83
|
+
})
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
const StyledTextWrapper = styled(View)<{
|
|
87
|
+
textOpacity: number
|
|
88
|
+
}>(({ textOpacity }) => ({
|
|
89
|
+
opacity: textOpacity
|
|
90
|
+
}))
|
|
91
|
+
|
|
92
|
+
const StyledSpinnerWrapper = styled(View)({
|
|
93
|
+
position: "absolute",
|
|
94
|
+
alignItems: "center",
|
|
95
|
+
justifyContent: "center"
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* A button component with token-based styling.
|
|
100
|
+
*
|
|
101
|
+
* Supports `filled` (default), `outlined`, and `text` variants.
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* ```tsx
|
|
105
|
+
* import { Button } from "@butternutbox/pawprint-native"
|
|
106
|
+
*
|
|
107
|
+
* <Button variant="filled" colour="primary" size="md">Click me</Button>
|
|
108
|
+
* <Button variant="text">Text button</Button>
|
|
109
|
+
* ```
|
|
110
|
+
*
|
|
111
|
+
* @param variant - *(optional)* Visual variant: filled (default), outlined, or text.
|
|
112
|
+
* @param size - *(optional)* Size variant: sm, md (default), lg
|
|
113
|
+
* @param colour - *(optional)* Colour variant: primary (default), secondary
|
|
114
|
+
* @param loading - *(optional)* Shows spinner and disables interaction
|
|
115
|
+
* @param fullWidth - *(optional)* Stretches button to fill its container
|
|
116
|
+
* @param startIcon - *(optional)* Leading icon element
|
|
117
|
+
* @param endIcon - *(optional)* Trailing icon element
|
|
118
|
+
*/
|
|
119
|
+
const Button = React.forwardRef<View, ButtonProps>(
|
|
120
|
+
(
|
|
121
|
+
{
|
|
122
|
+
variant = "filled",
|
|
123
|
+
size = "md",
|
|
124
|
+
colour = "primary",
|
|
125
|
+
loading = false,
|
|
126
|
+
fullWidth = false,
|
|
127
|
+
startIcon,
|
|
128
|
+
endIcon,
|
|
129
|
+
children,
|
|
130
|
+
disabled,
|
|
131
|
+
...rest
|
|
132
|
+
},
|
|
133
|
+
ref
|
|
134
|
+
) => {
|
|
135
|
+
const theme = useTheme()
|
|
136
|
+
const isDisabled = disabled || loading
|
|
137
|
+
const buttons = theme.tokens.components.buttons
|
|
138
|
+
const sizeTokens = buttons.size[size]
|
|
139
|
+
const spacingTokens = buttons.spacing[size]
|
|
140
|
+
const typographyKey = sizeToTypographyKey[size]
|
|
141
|
+
const typography = buttons.text[
|
|
142
|
+
typographyKey as keyof typeof buttons.text
|
|
143
|
+
] as unknown as Record<string, string>
|
|
144
|
+
|
|
145
|
+
resolveFont(typography.fontFamily, typography.fontWeight)
|
|
146
|
+
|
|
147
|
+
const [pressed, setPressed] = React.useState(false)
|
|
148
|
+
|
|
149
|
+
const getVariantStyles = (
|
|
150
|
+
isPressed: boolean
|
|
151
|
+
): {
|
|
152
|
+
backgroundColor: string
|
|
153
|
+
borderWidth?: number
|
|
154
|
+
borderColor?: string
|
|
155
|
+
textColor: string
|
|
156
|
+
spinnerColor: string
|
|
157
|
+
} => {
|
|
158
|
+
if (variant === "filled") {
|
|
159
|
+
const bgTokens = buttons.filledButton.colour.background[colour]
|
|
160
|
+
const textToken = buttons.filledButton.colour.text[colour].default
|
|
161
|
+
return {
|
|
162
|
+
backgroundColor: isDisabled
|
|
163
|
+
? bgTokens.disabled
|
|
164
|
+
: isPressed
|
|
165
|
+
? bgTokens.selected
|
|
166
|
+
: bgTokens.default,
|
|
167
|
+
textColor: textToken,
|
|
168
|
+
spinnerColor: textToken
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (variant === "outlined") {
|
|
173
|
+
const outline = buttons.outlineButton
|
|
174
|
+
return {
|
|
175
|
+
backgroundColor:
|
|
176
|
+
loading || isPressed
|
|
177
|
+
? outline.colour.background.selected
|
|
178
|
+
: "transparent",
|
|
179
|
+
borderWidth: parseTokenValue(outline.border.default),
|
|
180
|
+
borderColor: outline.colour.border.default,
|
|
181
|
+
textColor:
|
|
182
|
+
loading || isPressed
|
|
183
|
+
? outline.colour.text.selected
|
|
184
|
+
: outline.colour.text.default,
|
|
185
|
+
spinnerColor: outline.colour.text.selected
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// text variant
|
|
190
|
+
const text = buttons.textButton
|
|
191
|
+
return {
|
|
192
|
+
backgroundColor:
|
|
193
|
+
loading || isPressed ? text.background.selected : "transparent",
|
|
194
|
+
textColor:
|
|
195
|
+
loading || isPressed ? text.text.selected : text.text.default,
|
|
196
|
+
spinnerColor: text.text.selected
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const variantStyles = getVariantStyles(pressed)
|
|
201
|
+
|
|
202
|
+
return (
|
|
203
|
+
<StyledButton
|
|
204
|
+
ref={ref}
|
|
205
|
+
disabled={isDisabled}
|
|
206
|
+
accessibilityState={{ disabled: isDisabled, busy: loading }}
|
|
207
|
+
onPressIn={() => setPressed(true)}
|
|
208
|
+
onPressOut={() => setPressed(false)}
|
|
209
|
+
buttonHeight={parseTokenValue(sizeTokens.height)}
|
|
210
|
+
buttonMinWidth={parseTokenValue(sizeTokens.minWidth)}
|
|
211
|
+
buttonPaddingHorizontal={parseTokenValue(
|
|
212
|
+
spacingTokens.horizontalPadding
|
|
213
|
+
)}
|
|
214
|
+
buttonPaddingVertical={parseTokenValue(spacingTokens.verticalPadding)}
|
|
215
|
+
buttonGap={parseTokenValue(spacingTokens.gap)}
|
|
216
|
+
buttonBorderRadius={parseTokenValue(buttons.borderRadius.default)}
|
|
217
|
+
buttonBgColor={variantStyles.backgroundColor}
|
|
218
|
+
buttonOpacity={
|
|
219
|
+
isDisabled && !loading ? parseFloat(buttons.opacity.disabled) : 1
|
|
220
|
+
}
|
|
221
|
+
buttonBorderWidth={variantStyles.borderWidth}
|
|
222
|
+
buttonBorderColor={variantStyles.borderColor}
|
|
223
|
+
buttonFullWidth={fullWidth}
|
|
224
|
+
{...rest}
|
|
225
|
+
>
|
|
226
|
+
{startIcon && <IconWrapper>{startIcon}</IconWrapper>}
|
|
227
|
+
<StyledTextWrapper textOpacity={loading ? 0 : 1}>
|
|
228
|
+
<Typography
|
|
229
|
+
token={{
|
|
230
|
+
fontFamily: typography.fontFamily,
|
|
231
|
+
fontWeight: typography.fontWeight,
|
|
232
|
+
fontSize: typography.fontSize,
|
|
233
|
+
lineHeight: typography.fontSize,
|
|
234
|
+
letterSpacing: typography.letterSpacing
|
|
235
|
+
}}
|
|
236
|
+
color={variantStyles.textColor as never}
|
|
237
|
+
>
|
|
238
|
+
{children}
|
|
239
|
+
</Typography>
|
|
240
|
+
</StyledTextWrapper>
|
|
241
|
+
{endIcon && <IconWrapper>{endIcon}</IconWrapper>}
|
|
242
|
+
{loading && (
|
|
243
|
+
<StyledSpinnerWrapper>
|
|
244
|
+
<Spinner
|
|
245
|
+
size={size}
|
|
246
|
+
style={{
|
|
247
|
+
borderColor: "transparent",
|
|
248
|
+
borderTopColor: variantStyles.spinnerColor
|
|
249
|
+
}}
|
|
250
|
+
/>
|
|
251
|
+
</StyledSpinnerWrapper>
|
|
252
|
+
)}
|
|
253
|
+
</StyledButton>
|
|
254
|
+
)
|
|
255
|
+
}
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
Button.displayName = "Button"
|
|
259
|
+
|
|
260
|
+
export { Button }
|
|
261
|
+
export type { ButtonProps, ButtonVariant, ButtonSize, ButtonColour }
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import { View, StyleSheet, Text } from "react-native"
|
|
3
|
+
import { Hint } from "./Hint"
|
|
4
|
+
import type { HintProps } from "./Hint"
|
|
5
|
+
|
|
6
|
+
export default {
|
|
7
|
+
title: "Atoms/Hint",
|
|
8
|
+
component: Hint,
|
|
9
|
+
argTypes: {
|
|
10
|
+
variant: {
|
|
11
|
+
control: { type: "select" },
|
|
12
|
+
options: ["default", "success", "warning", "error"],
|
|
13
|
+
description: "Visual style variant indicating status"
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const Default = (args: HintProps) => <Hint {...args} />
|
|
19
|
+
Default.args = {
|
|
20
|
+
variant: "default"
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const variants = ["default", "success", "warning", "error"] as const
|
|
24
|
+
|
|
25
|
+
export const AllVariants = () => (
|
|
26
|
+
<View style={styles.row}>
|
|
27
|
+
{variants.map((variant) => (
|
|
28
|
+
<View key={variant} style={styles.item}>
|
|
29
|
+
<Hint variant={variant} />
|
|
30
|
+
<Text style={styles.label}>{variant}</Text>
|
|
31
|
+
</View>
|
|
32
|
+
))}
|
|
33
|
+
</View>
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
export const StatusIndicators = () => (
|
|
37
|
+
<View style={styles.column}>
|
|
38
|
+
<View style={styles.statusRow}>
|
|
39
|
+
<Hint variant="success" />
|
|
40
|
+
<Text style={styles.text}>Online</Text>
|
|
41
|
+
</View>
|
|
42
|
+
<View style={styles.statusRow}>
|
|
43
|
+
<Hint variant="warning" />
|
|
44
|
+
<Text style={styles.text}>Away</Text>
|
|
45
|
+
</View>
|
|
46
|
+
<View style={styles.statusRow}>
|
|
47
|
+
<Hint variant="error" />
|
|
48
|
+
<Text style={styles.text}>Offline</Text>
|
|
49
|
+
</View>
|
|
50
|
+
<View style={styles.statusRow}>
|
|
51
|
+
<Hint variant="default" />
|
|
52
|
+
<Text style={styles.text}>Unknown</Text>
|
|
53
|
+
</View>
|
|
54
|
+
</View>
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
const styles = StyleSheet.create({
|
|
58
|
+
column: {
|
|
59
|
+
flexDirection: "column",
|
|
60
|
+
gap: 12
|
|
61
|
+
},
|
|
62
|
+
row: {
|
|
63
|
+
flexDirection: "row",
|
|
64
|
+
gap: 24,
|
|
65
|
+
alignItems: "center"
|
|
66
|
+
},
|
|
67
|
+
item: {
|
|
68
|
+
flexDirection: "column",
|
|
69
|
+
alignItems: "center",
|
|
70
|
+
gap: 8
|
|
71
|
+
},
|
|
72
|
+
statusRow: {
|
|
73
|
+
flexDirection: "row",
|
|
74
|
+
gap: 8,
|
|
75
|
+
alignItems: "center"
|
|
76
|
+
},
|
|
77
|
+
label: {
|
|
78
|
+
fontSize: 12,
|
|
79
|
+
color: "#666"
|
|
80
|
+
},
|
|
81
|
+
text: {
|
|
82
|
+
fontSize: 14
|
|
83
|
+
}
|
|
84
|
+
})
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import { View, ViewProps } from "react-native"
|
|
3
|
+
import styled from "@emotion/native"
|
|
4
|
+
|
|
5
|
+
type HintVariant = "default" | "success" | "warning" | "error"
|
|
6
|
+
|
|
7
|
+
type HintOwnProps = {
|
|
8
|
+
variant?: HintVariant
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type HintProps = HintOwnProps & Omit<ViewProps, keyof HintOwnProps>
|
|
12
|
+
|
|
13
|
+
const parseTokenValue = (value: string): number => parseFloat(value)
|
|
14
|
+
|
|
15
|
+
const StyledHint = styled(View)<{ hintVariant: HintVariant }>(({
|
|
16
|
+
theme,
|
|
17
|
+
hintVariant
|
|
18
|
+
}) => {
|
|
19
|
+
const {
|
|
20
|
+
badge,
|
|
21
|
+
hint: {
|
|
22
|
+
colour: { background }
|
|
23
|
+
}
|
|
24
|
+
} = theme.tokens.components.badges
|
|
25
|
+
|
|
26
|
+
const hintVariantMap = {
|
|
27
|
+
default: background.default,
|
|
28
|
+
success: background.success,
|
|
29
|
+
warning: background.warning,
|
|
30
|
+
error: background.error
|
|
31
|
+
} as const
|
|
32
|
+
|
|
33
|
+
const size = parseTokenValue(badge.hint.sizing.default)
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
width: size,
|
|
37
|
+
height: size,
|
|
38
|
+
borderRadius: size / 2,
|
|
39
|
+
backgroundColor: hintVariantMap[hintVariant]
|
|
40
|
+
}
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Hint component for displaying notification indicators.
|
|
45
|
+
* A small circular dot used to draw attention or indicate status.
|
|
46
|
+
*
|
|
47
|
+
* @param {"default" | "success" | "warning" | "error"} [variant="default"] - Visual style variant.
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* <Hint variant="success" />
|
|
51
|
+
* <Hint variant="error" />
|
|
52
|
+
*/
|
|
53
|
+
export const Hint = React.forwardRef<View, HintProps>(
|
|
54
|
+
({ variant = "default", ...rest }, ref) => {
|
|
55
|
+
return <StyledHint ref={ref} hintVariant={variant} {...rest} />
|
|
56
|
+
}
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
Hint.displayName = "Hint"
|