@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,91 @@
|
|
|
1
|
+
import React, { useEffect, useRef } from "react"
|
|
2
|
+
import { Animated, Easing, View, ViewProps } from "react-native"
|
|
3
|
+
import styled from "@emotion/native"
|
|
4
|
+
import { useTheme } from "@emotion/react"
|
|
5
|
+
|
|
6
|
+
type SpinnerSize = "sm" | "md" | "lg"
|
|
7
|
+
type SpinnerVariant = "dark" | "light"
|
|
8
|
+
|
|
9
|
+
type SpinnerOwnProps = {
|
|
10
|
+
size?: SpinnerSize
|
|
11
|
+
variant?: SpinnerVariant
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
type SpinnerProps = SpinnerOwnProps & Omit<ViewProps, keyof SpinnerOwnProps>
|
|
15
|
+
|
|
16
|
+
const parseTokenValue = (value: string): number => parseFloat(value)
|
|
17
|
+
|
|
18
|
+
const StyledSpinner = styled(View)({})
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* A spinner component for indicating indeterminate loading states.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```tsx
|
|
25
|
+
* import { Spinner } from "@butternutbox/pawprint-native"
|
|
26
|
+
*
|
|
27
|
+
* <Spinner />
|
|
28
|
+
* <Spinner size="lg" variant="light" />
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* @param size - *(optional)* Size variant: sm, md (default), lg
|
|
32
|
+
* @param variant - *(optional)* Colour variant: dark (default), light
|
|
33
|
+
*/
|
|
34
|
+
const Spinner = React.forwardRef<View, SpinnerProps>(
|
|
35
|
+
({ size = "md", variant = "dark", style, ...rest }, ref) => {
|
|
36
|
+
const theme = useTheme()
|
|
37
|
+
const spinAnim = useRef(new Animated.Value(0)).current
|
|
38
|
+
|
|
39
|
+
const { size: sizeTokens, colour } = theme.tokens.components.spinner
|
|
40
|
+
const borderWidth = parseTokenValue(
|
|
41
|
+
theme.tokens.semantics.dimensions.borderWidth.md
|
|
42
|
+
)
|
|
43
|
+
const baseColor = colour.background.base[variant]
|
|
44
|
+
const progressColor = colour.background.progress[variant]
|
|
45
|
+
const dimension = parseTokenValue(sizeTokens[size])
|
|
46
|
+
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
const animation = Animated.loop(
|
|
49
|
+
Animated.timing(spinAnim, {
|
|
50
|
+
toValue: 1,
|
|
51
|
+
duration: 600,
|
|
52
|
+
easing: Easing.linear,
|
|
53
|
+
useNativeDriver: true
|
|
54
|
+
})
|
|
55
|
+
)
|
|
56
|
+
animation.start()
|
|
57
|
+
return () => animation.stop()
|
|
58
|
+
}, [spinAnim])
|
|
59
|
+
|
|
60
|
+
const spin = spinAnim.interpolate({
|
|
61
|
+
inputRange: [0, 1],
|
|
62
|
+
outputRange: ["0deg", "360deg"]
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<StyledSpinner ref={ref} {...rest}>
|
|
67
|
+
<Animated.View
|
|
68
|
+
accessibilityRole="progressbar"
|
|
69
|
+
accessibilityLabel="Loading"
|
|
70
|
+
style={[
|
|
71
|
+
{
|
|
72
|
+
width: dimension,
|
|
73
|
+
height: dimension,
|
|
74
|
+
borderRadius: dimension / 2,
|
|
75
|
+
borderWidth,
|
|
76
|
+
borderColor: baseColor,
|
|
77
|
+
borderTopColor: progressColor,
|
|
78
|
+
transform: [{ rotate: spin }]
|
|
79
|
+
},
|
|
80
|
+
style
|
|
81
|
+
]}
|
|
82
|
+
/>
|
|
83
|
+
</StyledSpinner>
|
|
84
|
+
)
|
|
85
|
+
}
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
Spinner.displayName = "Spinner"
|
|
89
|
+
|
|
90
|
+
export { Spinner }
|
|
91
|
+
export type { SpinnerProps, SpinnerSize, SpinnerVariant }
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import React, { useState } from "react"
|
|
2
|
+
import { View, StyleSheet } from "react-native"
|
|
3
|
+
import { Switch } from "./Switch"
|
|
4
|
+
import type { SwitchProps } from "./Switch"
|
|
5
|
+
import { Typography } from "../Typography"
|
|
6
|
+
|
|
7
|
+
export default {
|
|
8
|
+
title: "Atoms/Switch",
|
|
9
|
+
component: Switch,
|
|
10
|
+
argTypes: {
|
|
11
|
+
label: {
|
|
12
|
+
control: { type: "text" },
|
|
13
|
+
description: "Main label text"
|
|
14
|
+
},
|
|
15
|
+
subText: {
|
|
16
|
+
control: { type: "text" },
|
|
17
|
+
description: "Optional descriptive subtext"
|
|
18
|
+
},
|
|
19
|
+
disabled: {
|
|
20
|
+
control: { type: "boolean" },
|
|
21
|
+
description: "Prevents interaction"
|
|
22
|
+
},
|
|
23
|
+
defaultChecked: {
|
|
24
|
+
control: { type: "boolean" },
|
|
25
|
+
description: "Initial checked state (uncontrolled)"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const Playground = (args: SwitchProps) => <Switch {...args} />
|
|
31
|
+
Playground.args = {
|
|
32
|
+
label: "Notifications",
|
|
33
|
+
subText: "Receive push notifications",
|
|
34
|
+
disabled: false,
|
|
35
|
+
defaultChecked: false
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const ContentVariants = () => (
|
|
39
|
+
<View style={styles.column}>
|
|
40
|
+
<View style={styles.section}>
|
|
41
|
+
<Typography size="sm" weight="semiBold" color="tertiary">
|
|
42
|
+
No label
|
|
43
|
+
</Typography>
|
|
44
|
+
<Switch />
|
|
45
|
+
</View>
|
|
46
|
+
<View style={styles.section}>
|
|
47
|
+
<Typography size="sm" weight="semiBold" color="tertiary">
|
|
48
|
+
Label only
|
|
49
|
+
</Typography>
|
|
50
|
+
<Switch label="SMS Delivery Notifications" />
|
|
51
|
+
</View>
|
|
52
|
+
<View style={styles.section}>
|
|
53
|
+
<Typography size="sm" weight="semiBold" color="tertiary">
|
|
54
|
+
Label + subtext
|
|
55
|
+
</Typography>
|
|
56
|
+
<Switch
|
|
57
|
+
label="SMS Delivery Notifications"
|
|
58
|
+
subText="I want to receive SMS notifications about my deliveries"
|
|
59
|
+
/>
|
|
60
|
+
</View>
|
|
61
|
+
</View>
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
export const States = () => (
|
|
65
|
+
<View style={styles.column}>
|
|
66
|
+
<View style={styles.section}>
|
|
67
|
+
<Typography size="sm" weight="semiBold" color="tertiary">
|
|
68
|
+
Enabled (off)
|
|
69
|
+
</Typography>
|
|
70
|
+
<Switch label="Enabled switch" />
|
|
71
|
+
</View>
|
|
72
|
+
<View style={styles.section}>
|
|
73
|
+
<Typography size="sm" weight="semiBold" color="tertiary">
|
|
74
|
+
Enabled (on)
|
|
75
|
+
</Typography>
|
|
76
|
+
<Switch label="Enabled switch" defaultChecked />
|
|
77
|
+
</View>
|
|
78
|
+
<View style={styles.section}>
|
|
79
|
+
<Typography size="sm" weight="semiBold" color="tertiary">
|
|
80
|
+
Disabled (off)
|
|
81
|
+
</Typography>
|
|
82
|
+
<Switch label="Disabled switch" disabled />
|
|
83
|
+
</View>
|
|
84
|
+
<View style={styles.section}>
|
|
85
|
+
<Typography size="sm" weight="semiBold" color="tertiary">
|
|
86
|
+
Disabled (on)
|
|
87
|
+
</Typography>
|
|
88
|
+
<Switch label="Disabled switch" disabled defaultChecked />
|
|
89
|
+
</View>
|
|
90
|
+
</View>
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
export const Controlled = () => {
|
|
94
|
+
const [checked, setChecked] = useState(false)
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
<View style={styles.column}>
|
|
98
|
+
<Typography size="sm" weight="semiBold" color="tertiary">
|
|
99
|
+
Controlled: {checked ? "ON" : "OFF"}
|
|
100
|
+
</Typography>
|
|
101
|
+
<Switch
|
|
102
|
+
label="Controlled switch"
|
|
103
|
+
subText="Toggle me to see state change"
|
|
104
|
+
checked={checked}
|
|
105
|
+
onCheckedChange={setChecked}
|
|
106
|
+
/>
|
|
107
|
+
</View>
|
|
108
|
+
)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const styles = StyleSheet.create({
|
|
112
|
+
column: {
|
|
113
|
+
flexDirection: "column",
|
|
114
|
+
gap: 24
|
|
115
|
+
},
|
|
116
|
+
section: {
|
|
117
|
+
flexDirection: "column",
|
|
118
|
+
gap: 8
|
|
119
|
+
}
|
|
120
|
+
})
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import React, { useEffect, useRef } from "react"
|
|
2
|
+
import { View, Animated, ViewProps } from "react-native"
|
|
3
|
+
import styled from "@emotion/native"
|
|
4
|
+
import { useTheme } from "@emotion/react"
|
|
5
|
+
import * as SwitchPrimitive from "@rn-primitives/switch"
|
|
6
|
+
import { Typography } from "../Typography"
|
|
7
|
+
|
|
8
|
+
type SwitchOwnProps = {
|
|
9
|
+
label?: React.ReactNode
|
|
10
|
+
subText?: React.ReactNode
|
|
11
|
+
disabled?: boolean
|
|
12
|
+
checked?: boolean
|
|
13
|
+
defaultChecked?: boolean
|
|
14
|
+
onCheckedChange?: (checked: boolean) => void
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type SwitchProps = SwitchOwnProps & Omit<ViewProps, keyof SwitchOwnProps>
|
|
18
|
+
|
|
19
|
+
const parseTokenValue = (value: string): number => parseFloat(value)
|
|
20
|
+
|
|
21
|
+
const StyledContainer = styled(View)<{
|
|
22
|
+
switchGap: number
|
|
23
|
+
switchOpacity: number
|
|
24
|
+
}>(({ switchGap, switchOpacity }) => ({
|
|
25
|
+
flexDirection: "row",
|
|
26
|
+
alignItems: "flex-start",
|
|
27
|
+
gap: switchGap,
|
|
28
|
+
opacity: switchOpacity
|
|
29
|
+
}))
|
|
30
|
+
|
|
31
|
+
const StyledControl = styled(SwitchPrimitive.Root)<{
|
|
32
|
+
switchChecked: boolean
|
|
33
|
+
controlWidth: number
|
|
34
|
+
controlHeight: number
|
|
35
|
+
controlBorderWidth: number
|
|
36
|
+
controlBorderColor: string
|
|
37
|
+
controlBgChecked: string
|
|
38
|
+
controlBgDefault: string
|
|
39
|
+
}>(
|
|
40
|
+
({
|
|
41
|
+
switchChecked,
|
|
42
|
+
controlWidth,
|
|
43
|
+
controlHeight,
|
|
44
|
+
controlBorderWidth,
|
|
45
|
+
controlBorderColor,
|
|
46
|
+
controlBgChecked,
|
|
47
|
+
controlBgDefault
|
|
48
|
+
}) => ({
|
|
49
|
+
position: "relative",
|
|
50
|
+
width: controlWidth,
|
|
51
|
+
height: controlHeight,
|
|
52
|
+
minWidth: controlWidth,
|
|
53
|
+
minHeight: controlHeight,
|
|
54
|
+
borderRadius: controlHeight,
|
|
55
|
+
borderWidth: switchChecked ? 0 : controlBorderWidth,
|
|
56
|
+
borderColor: switchChecked ? "transparent" : controlBorderColor,
|
|
57
|
+
backgroundColor: switchChecked ? controlBgChecked : controlBgDefault,
|
|
58
|
+
justifyContent: "center"
|
|
59
|
+
})
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
const StyledThumb = styled(Animated.View)<{
|
|
63
|
+
thumbSize: number
|
|
64
|
+
thumbBgColor: string
|
|
65
|
+
}>(({ thumbSize, thumbBgColor }) => ({
|
|
66
|
+
width: thumbSize,
|
|
67
|
+
height: thumbSize,
|
|
68
|
+
borderRadius: thumbSize / 2,
|
|
69
|
+
backgroundColor: thumbBgColor,
|
|
70
|
+
position: "absolute"
|
|
71
|
+
}))
|
|
72
|
+
|
|
73
|
+
const StyledContent = styled(View)<{
|
|
74
|
+
contentGap: number
|
|
75
|
+
}>(({ contentGap }) => ({
|
|
76
|
+
gap: contentGap
|
|
77
|
+
}))
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Switch component for toggling between two states, on and off.
|
|
81
|
+
*
|
|
82
|
+
* @param {React.ReactNode} [label] - Main label text or content.
|
|
83
|
+
* @param {React.ReactNode} [subText] - Optional descriptive subtext.
|
|
84
|
+
* @param {boolean} [disabled=false] - Whether the switch is disabled.
|
|
85
|
+
* @param {boolean} [checked] - Controlled checked state.
|
|
86
|
+
* @param {(checked: boolean) => void} [onCheckedChange] - Controlled change handler.
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* <Switch label="SMS Delivery Notifications" subText="I want to receive SMS notifications" />
|
|
90
|
+
*/
|
|
91
|
+
export const Switch = React.forwardRef<View, SwitchProps>(
|
|
92
|
+
(
|
|
93
|
+
{
|
|
94
|
+
label,
|
|
95
|
+
subText,
|
|
96
|
+
disabled = false,
|
|
97
|
+
checked: controlledChecked,
|
|
98
|
+
defaultChecked = false,
|
|
99
|
+
onCheckedChange,
|
|
100
|
+
...rest
|
|
101
|
+
},
|
|
102
|
+
ref
|
|
103
|
+
) => {
|
|
104
|
+
const theme = useTheme()
|
|
105
|
+
const { size, colour, opacity, borderWidth, spacing, typography } =
|
|
106
|
+
theme.tokens.components.switch
|
|
107
|
+
|
|
108
|
+
const isControlled = controlledChecked !== undefined
|
|
109
|
+
const [internalChecked, setInternalChecked] = React.useState(defaultChecked)
|
|
110
|
+
const isChecked = isControlled ? controlledChecked : internalChecked
|
|
111
|
+
|
|
112
|
+
const controlWidth = parseTokenValue(size.control.width)
|
|
113
|
+
const controlHeight = parseTokenValue(size.control.height)
|
|
114
|
+
const thumbSize = parseTokenValue(size.thumb.default)
|
|
115
|
+
const borderWidthValue = parseTokenValue(borderWidth.control.default)
|
|
116
|
+
const outerInset = 5
|
|
117
|
+
const inset = outerInset - borderWidthValue
|
|
118
|
+
const translateX = controlWidth - thumbSize - outerInset * 2
|
|
119
|
+
|
|
120
|
+
const animValue = useRef(new Animated.Value(isChecked ? 1 : 0)).current
|
|
121
|
+
|
|
122
|
+
useEffect(() => {
|
|
123
|
+
Animated.timing(animValue, {
|
|
124
|
+
toValue: isChecked ? 1 : 0,
|
|
125
|
+
duration: 200,
|
|
126
|
+
useNativeDriver: true
|
|
127
|
+
}).start()
|
|
128
|
+
}, [isChecked, animValue])
|
|
129
|
+
|
|
130
|
+
const thumbTranslateX = animValue.interpolate({
|
|
131
|
+
inputRange: [0, 1],
|
|
132
|
+
outputRange: [inset, inset + translateX]
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
const handleCheckedChange = (checked: boolean) => {
|
|
136
|
+
if (!isControlled) {
|
|
137
|
+
setInternalChecked(checked)
|
|
138
|
+
}
|
|
139
|
+
onCheckedChange?.(checked)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return (
|
|
143
|
+
<StyledContainer
|
|
144
|
+
ref={ref}
|
|
145
|
+
switchGap={parseTokenValue(spacing.gap)}
|
|
146
|
+
switchOpacity={disabled ? parseFloat(opacity.disabled) : 1}
|
|
147
|
+
{...rest}
|
|
148
|
+
>
|
|
149
|
+
<StyledControl
|
|
150
|
+
checked={isChecked}
|
|
151
|
+
onCheckedChange={handleCheckedChange}
|
|
152
|
+
disabled={disabled}
|
|
153
|
+
switchChecked={isChecked}
|
|
154
|
+
controlWidth={controlWidth}
|
|
155
|
+
controlHeight={controlHeight}
|
|
156
|
+
controlBorderWidth={borderWidthValue}
|
|
157
|
+
controlBorderColor={colour.control.border.default}
|
|
158
|
+
controlBgChecked={colour.control.background.selected}
|
|
159
|
+
controlBgDefault={colour.control.background.default}
|
|
160
|
+
>
|
|
161
|
+
<SwitchPrimitive.Thumb asChild>
|
|
162
|
+
<StyledThumb
|
|
163
|
+
thumbSize={thumbSize}
|
|
164
|
+
thumbBgColor={
|
|
165
|
+
isChecked
|
|
166
|
+
? colour.thumb.background.selected
|
|
167
|
+
: colour.thumb.background.default
|
|
168
|
+
}
|
|
169
|
+
style={{ transform: [{ translateX: thumbTranslateX }] }}
|
|
170
|
+
/>
|
|
171
|
+
</SwitchPrimitive.Thumb>
|
|
172
|
+
</StyledControl>
|
|
173
|
+
|
|
174
|
+
{(label || subText) && (
|
|
175
|
+
<StyledContent contentGap={parseTokenValue(spacing.content.gap)}>
|
|
176
|
+
{label && (
|
|
177
|
+
<Typography token={typography.label} color={colour.text.title}>
|
|
178
|
+
{label}
|
|
179
|
+
</Typography>
|
|
180
|
+
)}
|
|
181
|
+
{subText && (
|
|
182
|
+
<Typography
|
|
183
|
+
token={typography.subText}
|
|
184
|
+
color={colour.text.subtext}
|
|
185
|
+
>
|
|
186
|
+
{subText}
|
|
187
|
+
</Typography>
|
|
188
|
+
)}
|
|
189
|
+
</StyledContent>
|
|
190
|
+
)}
|
|
191
|
+
</StyledContainer>
|
|
192
|
+
)
|
|
193
|
+
}
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
Switch.displayName = "Switch"
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import { View, StyleSheet, Text } from "react-native"
|
|
3
|
+
import { Tag } from "./Tag"
|
|
4
|
+
import type { TagProps } from "./Tag"
|
|
5
|
+
|
|
6
|
+
export default {
|
|
7
|
+
title: "Atoms/Tag",
|
|
8
|
+
component: Tag,
|
|
9
|
+
argTypes: {
|
|
10
|
+
variant: {
|
|
11
|
+
control: { type: "select" },
|
|
12
|
+
options: [
|
|
13
|
+
"primary",
|
|
14
|
+
"secondary",
|
|
15
|
+
"tertiary",
|
|
16
|
+
"promo",
|
|
17
|
+
"success",
|
|
18
|
+
"warning",
|
|
19
|
+
"error"
|
|
20
|
+
],
|
|
21
|
+
description: "Visual style variant"
|
|
22
|
+
},
|
|
23
|
+
size: {
|
|
24
|
+
control: { type: "select" },
|
|
25
|
+
options: ["small", "medium", "large"],
|
|
26
|
+
description: "Size of the tag"
|
|
27
|
+
},
|
|
28
|
+
children: {
|
|
29
|
+
control: { type: "text" },
|
|
30
|
+
description: "Tag label text"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const Playground = (args: TagProps) => <Tag {...args} />
|
|
36
|
+
Playground.args = {
|
|
37
|
+
children: "Tag",
|
|
38
|
+
variant: "primary",
|
|
39
|
+
size: "medium"
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const tagVariants = [
|
|
43
|
+
"primary",
|
|
44
|
+
"secondary",
|
|
45
|
+
"tertiary",
|
|
46
|
+
"promo",
|
|
47
|
+
"success",
|
|
48
|
+
"warning",
|
|
49
|
+
"error"
|
|
50
|
+
] as const
|
|
51
|
+
const tagSizes = ["small", "medium", "large"] as const
|
|
52
|
+
|
|
53
|
+
export const AllVariants = () => (
|
|
54
|
+
<View style={styles.column}>
|
|
55
|
+
{tagVariants.map((variant) => (
|
|
56
|
+
<View key={variant} style={styles.section}>
|
|
57
|
+
<Text style={styles.label}>{variant}</Text>
|
|
58
|
+
<View style={styles.row}>
|
|
59
|
+
{tagSizes.map((size) => (
|
|
60
|
+
<Tag key={size} variant={variant} size={size}>
|
|
61
|
+
{size}
|
|
62
|
+
</Tag>
|
|
63
|
+
))}
|
|
64
|
+
</View>
|
|
65
|
+
</View>
|
|
66
|
+
))}
|
|
67
|
+
</View>
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
const styles = StyleSheet.create({
|
|
71
|
+
column: {
|
|
72
|
+
flexDirection: "column",
|
|
73
|
+
gap: 24
|
|
74
|
+
},
|
|
75
|
+
section: {
|
|
76
|
+
flexDirection: "column",
|
|
77
|
+
gap: 8
|
|
78
|
+
},
|
|
79
|
+
row: {
|
|
80
|
+
flexDirection: "row",
|
|
81
|
+
gap: 12,
|
|
82
|
+
alignItems: "center"
|
|
83
|
+
},
|
|
84
|
+
label: {
|
|
85
|
+
fontSize: 13,
|
|
86
|
+
fontWeight: "600",
|
|
87
|
+
color: "#666"
|
|
88
|
+
}
|
|
89
|
+
})
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import { View, ViewProps } from "react-native"
|
|
3
|
+
import styled from "@emotion/native"
|
|
4
|
+
import { useTheme } from "@emotion/react"
|
|
5
|
+
import { Typography } from "../Typography"
|
|
6
|
+
import { Icon, type PawprintIcon } from "../Icon"
|
|
7
|
+
|
|
8
|
+
type TagVariant =
|
|
9
|
+
| "primary"
|
|
10
|
+
| "secondary"
|
|
11
|
+
| "tertiary"
|
|
12
|
+
| "promo"
|
|
13
|
+
| "success"
|
|
14
|
+
| "warning"
|
|
15
|
+
| "error"
|
|
16
|
+
|
|
17
|
+
type TagSize = "small" | "medium" | "large"
|
|
18
|
+
|
|
19
|
+
type TagOwnProps = {
|
|
20
|
+
children: React.ReactNode
|
|
21
|
+
variant?: TagVariant
|
|
22
|
+
size?: TagSize
|
|
23
|
+
icon?: PawprintIcon
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type TagProps = TagOwnProps & Omit<ViewProps, keyof TagOwnProps>
|
|
27
|
+
|
|
28
|
+
const parseTokenValue = (value: string): number => parseFloat(value)
|
|
29
|
+
|
|
30
|
+
const StyledTag = styled(View)<{
|
|
31
|
+
tagVariant: TagVariant
|
|
32
|
+
tagSize: TagSize
|
|
33
|
+
}>(({ theme, tagVariant, tagSize }) => {
|
|
34
|
+
const { sizing, spacing, colour, badge } = theme.tokens.components.tags
|
|
35
|
+
|
|
36
|
+
const backgroundColorMap = {
|
|
37
|
+
primary: colour.primary.background,
|
|
38
|
+
secondary: colour.primary.secondary,
|
|
39
|
+
tertiary: colour.primary.tertiery,
|
|
40
|
+
promo: colour.primary.promo,
|
|
41
|
+
success: colour.primary.success,
|
|
42
|
+
warning: colour.primary.warning,
|
|
43
|
+
error: colour.primary.error
|
|
44
|
+
} as const
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
flexDirection: "row",
|
|
48
|
+
alignItems: "center",
|
|
49
|
+
justifyContent: "center",
|
|
50
|
+
height: parseTokenValue(sizing[tagSize].height),
|
|
51
|
+
minWidth: parseTokenValue(sizing[tagSize].minWidth),
|
|
52
|
+
paddingHorizontal: parseTokenValue(spacing.horizontalPadding),
|
|
53
|
+
gap: parseTokenValue(spacing[tagSize].gap),
|
|
54
|
+
borderRadius: parseTokenValue(badge.borderRadius.default),
|
|
55
|
+
backgroundColor: backgroundColorMap[tagVariant]
|
|
56
|
+
}
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Tag component for labelling and categorising content.
|
|
61
|
+
*
|
|
62
|
+
* @param {React.ReactNode} children - Tag label text.
|
|
63
|
+
* @param {"primary" | "secondary" | "tertiary" | "promo" | "success" | "warning" | "error"} [variant="primary"] - Visual style variant.
|
|
64
|
+
* @param {"small" | "medium" | "large"} [size="medium"] - Size of the tag.
|
|
65
|
+
* @param {PawprintIcon} [icon] - Optional leading icon from @butternutbox/pawprint-icons.
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* <Tag>Company news</Tag>
|
|
69
|
+
* <Tag variant="success" size="large" icon={CheckCircle}>Verified</Tag>
|
|
70
|
+
*/
|
|
71
|
+
export const Tag = React.forwardRef<View, TagProps>(
|
|
72
|
+
({ variant = "primary", size = "medium", icon, children, ...rest }, ref) => {
|
|
73
|
+
const theme = useTheme()
|
|
74
|
+
const { typography, colour } = theme.tokens.components.tags
|
|
75
|
+
|
|
76
|
+
const iconSizeMap = {
|
|
77
|
+
small: "xs",
|
|
78
|
+
medium: "xs",
|
|
79
|
+
large: "sm"
|
|
80
|
+
} as const
|
|
81
|
+
|
|
82
|
+
const iconColourMap = {
|
|
83
|
+
primary: "primary",
|
|
84
|
+
secondary: "primary",
|
|
85
|
+
tertiary: "primary",
|
|
86
|
+
promo: "promo",
|
|
87
|
+
success: "success",
|
|
88
|
+
warning: "warning",
|
|
89
|
+
error: "error"
|
|
90
|
+
} as const
|
|
91
|
+
|
|
92
|
+
const textColorMap = {
|
|
93
|
+
primary: colour.text.default,
|
|
94
|
+
secondary: colour.text.default,
|
|
95
|
+
tertiary: colour.text.default,
|
|
96
|
+
promo: colour.text.promo,
|
|
97
|
+
success: colour.text.success,
|
|
98
|
+
warning: colour.text.warning,
|
|
99
|
+
error: colour.text.error
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<StyledTag ref={ref} tagVariant={variant} tagSize={size} {...rest}>
|
|
104
|
+
{icon && (
|
|
105
|
+
<Icon
|
|
106
|
+
icon={icon}
|
|
107
|
+
size={iconSizeMap[size]}
|
|
108
|
+
colour={iconColourMap[variant]}
|
|
109
|
+
/>
|
|
110
|
+
)}
|
|
111
|
+
<Typography
|
|
112
|
+
token={typography[size].default}
|
|
113
|
+
color={textColorMap[variant]}
|
|
114
|
+
>
|
|
115
|
+
{children}
|
|
116
|
+
</Typography>
|
|
117
|
+
</StyledTag>
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
Tag.displayName = "Tag"
|