@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.
Files changed (105) hide show
  1. package/.turbo/turbo-build.log +30 -0
  2. package/COMPONENT_GUIDELINES.md +610 -0
  3. package/README.md +72 -0
  4. package/dist/ibm-plex-sans-condensed-400-normal-I2XLJNNB.woff2 +0 -0
  5. package/dist/ibm-plex-sans-condensed-500-normal-IEQBNVGX.woff2 +0 -0
  6. package/dist/ibm-plex-sans-condensed-600-normal-UX5ZU5T6.woff2 +0 -0
  7. package/dist/ibm-plex-sans-condensed-700-normal-4PFYFTSO.woff2 +0 -0
  8. package/dist/ida-narrow-500-normal-C6I2PK4T.woff2 +0 -0
  9. package/dist/ida-narrow-700-normal-UPHPRIN6.woff2 +0 -0
  10. package/dist/index.cjs +2686 -0
  11. package/dist/index.cjs.map +1 -0
  12. package/dist/index.d.cts +780 -0
  13. package/dist/index.d.ts +780 -0
  14. package/dist/index.js +2617 -0
  15. package/dist/index.js.map +1 -0
  16. package/eslint.config.js +3 -0
  17. package/llms.txt +458 -0
  18. package/package.json +57 -0
  19. package/src/components/atoms/Avatar/Avatar.stories.tsx +125 -0
  20. package/src/components/atoms/Avatar/Avatar.tsx +159 -0
  21. package/src/components/atoms/Avatar/index.ts +7 -0
  22. package/src/components/atoms/Badge/Badge.stories.tsx +231 -0
  23. package/src/components/atoms/Badge/Badge.tsx +184 -0
  24. package/src/components/atoms/Badge/index.ts +2 -0
  25. package/src/components/atoms/Button/Button.stories.tsx +145 -0
  26. package/src/components/atoms/Button/Button.tsx +261 -0
  27. package/src/components/atoms/Button/index.ts +7 -0
  28. package/src/components/atoms/Hint/Hint.stories.tsx +84 -0
  29. package/src/components/atoms/Hint/Hint.tsx +59 -0
  30. package/src/components/atoms/Hint/index.ts +2 -0
  31. package/src/components/atoms/Icon/Icon.stories.tsx +200 -0
  32. package/src/components/atoms/Icon/Icon.tsx +112 -0
  33. package/src/components/atoms/Icon/index.ts +8 -0
  34. package/src/components/atoms/IconButton/IconButton.stories.tsx +162 -0
  35. package/src/components/atoms/IconButton/IconButton.tsx +227 -0
  36. package/src/components/atoms/IconButton/index.ts +7 -0
  37. package/src/components/atoms/Illustration/Illustration.stories.tsx +167 -0
  38. package/src/components/atoms/Illustration/Illustration.tsx +81 -0
  39. package/src/components/atoms/Illustration/index.ts +6 -0
  40. package/src/components/atoms/Input/Input.stories.tsx +142 -0
  41. package/src/components/atoms/Input/Input.tsx +110 -0
  42. package/src/components/atoms/Input/InputDescription.tsx +49 -0
  43. package/src/components/atoms/Input/InputError.tsx +39 -0
  44. package/src/components/atoms/Input/InputField.tsx +119 -0
  45. package/src/components/atoms/Input/InputLabel.tsx +61 -0
  46. package/src/components/atoms/Input/index.ts +10 -0
  47. package/src/components/atoms/Link/Link.stories.tsx +119 -0
  48. package/src/components/atoms/Link/Link.tsx +118 -0
  49. package/src/components/atoms/Link/index.ts +2 -0
  50. package/src/components/atoms/Logo/Logo.registry.ts +39 -0
  51. package/src/components/atoms/Logo/Logo.tsx +68 -0
  52. package/src/components/atoms/Logo/index.ts +4 -0
  53. package/src/components/atoms/Spinner/Spinner.stories.tsx +98 -0
  54. package/src/components/atoms/Spinner/Spinner.tsx +91 -0
  55. package/src/components/atoms/Spinner/index.ts +2 -0
  56. package/src/components/atoms/Switch/Switch.stories.tsx +120 -0
  57. package/src/components/atoms/Switch/Switch.tsx +196 -0
  58. package/src/components/atoms/Switch/index.ts +2 -0
  59. package/src/components/atoms/Tag/Tag.stories.tsx +89 -0
  60. package/src/components/atoms/Tag/Tag.tsx +122 -0
  61. package/src/components/atoms/Tag/index.ts +2 -0
  62. package/src/components/atoms/Typography/Typography.stories.tsx +315 -0
  63. package/src/components/atoms/Typography/Typography.tsx +284 -0
  64. package/src/components/atoms/Typography/index.ts +2 -0
  65. package/src/components/atoms/index.ts +14 -0
  66. package/src/components/index.ts +2 -0
  67. package/src/components/molecules/ButtonDock/ButtonDock.stories.tsx +95 -0
  68. package/src/components/molecules/ButtonDock/ButtonDock.tsx +148 -0
  69. package/src/components/molecules/ButtonDock/index.ts +2 -0
  70. package/src/components/molecules/ButtonGroup/ButtonGroup.stories.tsx +82 -0
  71. package/src/components/molecules/ButtonGroup/ButtonGroup.tsx +94 -0
  72. package/src/components/molecules/ButtonGroup/index.ts +2 -0
  73. package/src/components/molecules/Checkbox/Checkbox.stories.tsx +148 -0
  74. package/src/components/molecules/Checkbox/Checkbox.tsx +279 -0
  75. package/src/components/molecules/Checkbox/CheckboxGroup.tsx +53 -0
  76. package/src/components/molecules/Checkbox/index.ts +4 -0
  77. package/src/components/molecules/Radio/Radio.stories.tsx +182 -0
  78. package/src/components/molecules/Radio/Radio.tsx +249 -0
  79. package/src/components/molecules/Radio/RadioGroup.tsx +142 -0
  80. package/src/components/molecules/Radio/index.ts +4 -0
  81. package/src/components/molecules/SegmentedControl/SegmentedControl.stories.tsx +151 -0
  82. package/src/components/molecules/SegmentedControl/SegmentedControl.tsx +323 -0
  83. package/src/components/molecules/SegmentedControl/index.ts +5 -0
  84. package/src/components/molecules/Slider/Slider.stories.tsx +144 -0
  85. package/src/components/molecules/Slider/Slider.tsx +303 -0
  86. package/src/components/molecules/Slider/index.ts +2 -0
  87. package/src/components/molecules/index.ts +6 -0
  88. package/src/fonts/ibm-plex-sans-condensed-400-normal.woff2 +0 -0
  89. package/src/fonts/ibm-plex-sans-condensed-500-normal.woff2 +0 -0
  90. package/src/fonts/ibm-plex-sans-condensed-600-normal.woff2 +0 -0
  91. package/src/fonts/ibm-plex-sans-condensed-700-normal.woff2 +0 -0
  92. package/src/fonts/ida-narrow-500-normal.woff2 +0 -0
  93. package/src/fonts/ida-narrow-700-normal.woff2 +0 -0
  94. package/src/fonts/index.ts +49 -0
  95. package/src/index.ts +9 -0
  96. package/src/theme/PawprintProvider.tsx +26 -0
  97. package/src/theme/ThemeProvider.tsx +63 -0
  98. package/src/theme/index.ts +5 -0
  99. package/src/theme/theme.ts +3 -0
  100. package/src/theme/utils.ts +31 -0
  101. package/src/types/fonts.d.ts +4 -0
  102. package/src/types/index.ts +1 -0
  103. package/src/types/theme.ts +24 -0
  104. package/tsconfig.json +5 -0
  105. 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,7 @@
1
+ export { Button } from "./Button"
2
+ export type {
3
+ ButtonProps,
4
+ ButtonVariant,
5
+ ButtonSize,
6
+ ButtonColour
7
+ } from "./Button"
@@ -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"
@@ -0,0 +1,2 @@
1
+ export { Hint } from "./Hint"
2
+ export type { HintProps } from "./Hint"