@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,323 @@
1
+ import React from "react"
2
+ import { View, Pressable, ScrollView, ViewProps } from "react-native"
3
+ import styled from "@emotion/native"
4
+ import { useTheme } from "@emotion/react"
5
+ import { Typography } from "../../atoms/Typography"
6
+
7
+ type SegmentedControlLayout = "fixed" | "intrinsic"
8
+
9
+ type SegmentedControlOption = {
10
+ value: string
11
+ label: React.ReactNode
12
+ disabled?: boolean
13
+ }
14
+
15
+ type SegmentedControlOwnProps = {
16
+ options?: SegmentedControlOption[]
17
+ value?: string
18
+ defaultValue?: string
19
+ onValueChange?: (value: string) => void
20
+ layout?: SegmentedControlLayout
21
+ disabled?: boolean
22
+ }
23
+
24
+ export type SegmentedControlProps = SegmentedControlOwnProps &
25
+ Omit<ViewProps, keyof SegmentedControlOwnProps>
26
+
27
+ type SegmentedControlItemOwnProps = {
28
+ value: string
29
+ disabled?: boolean
30
+ children: React.ReactNode
31
+ }
32
+
33
+ export type SegmentedControlItemProps = SegmentedControlItemOwnProps &
34
+ Omit<ViewProps, keyof SegmentedControlItemOwnProps>
35
+
36
+ const parseTokenValue = (value: string): number => parseFloat(value)
37
+
38
+ const StyledSegmentItem = styled(Pressable)<{
39
+ itemHeight: number
40
+ itemMinWidth: number
41
+ itemPaddingVertical: number
42
+ itemPaddingHorizontal: number
43
+ itemBorderRadius: number
44
+ itemBgColor: string
45
+ itemOpacity: number
46
+ itemFlex?: number
47
+ }>(
48
+ ({
49
+ itemHeight,
50
+ itemMinWidth,
51
+ itemPaddingVertical,
52
+ itemPaddingHorizontal,
53
+ itemBorderRadius,
54
+ itemBgColor,
55
+ itemOpacity,
56
+ itemFlex
57
+ }) => ({
58
+ height: itemHeight,
59
+ minWidth: itemMinWidth,
60
+ paddingVertical: itemPaddingVertical,
61
+ paddingHorizontal: itemPaddingHorizontal,
62
+ borderRadius: itemBorderRadius,
63
+ alignItems: "center",
64
+ justifyContent: "center",
65
+ backgroundColor: itemBgColor,
66
+ opacity: itemOpacity,
67
+ ...(itemFlex !== undefined ? { flex: itemFlex } : { flexShrink: 0 })
68
+ })
69
+ )
70
+
71
+ const StyledContainer = styled(View)<{
72
+ containerGap: number
73
+ containerPaddingVertical: number
74
+ containerPaddingHorizontal: number
75
+ containerBorderRadius: number
76
+ containerBgColor: string
77
+ containerFullWidth: boolean
78
+ }>(
79
+ ({
80
+ containerGap,
81
+ containerPaddingVertical,
82
+ containerPaddingHorizontal,
83
+ containerBorderRadius,
84
+ containerBgColor,
85
+ containerFullWidth
86
+ }) => ({
87
+ flexDirection: "row",
88
+ alignItems: "stretch",
89
+ gap: containerGap,
90
+ paddingVertical: containerPaddingVertical,
91
+ paddingHorizontal: containerPaddingHorizontal,
92
+ borderRadius: containerBorderRadius,
93
+ backgroundColor: containerBgColor,
94
+ ...(containerFullWidth ? { width: "100%" } : {})
95
+ })
96
+ )
97
+
98
+ /**
99
+ * Internal item rendered within SegmentedControl.
100
+ */
101
+ const SegmentedControlItemBase = ({
102
+ value,
103
+ disabled = false,
104
+ selected = false,
105
+ layout,
106
+ onSelect,
107
+ children,
108
+ theme
109
+ }: {
110
+ value: string
111
+ disabled?: boolean
112
+ selected?: boolean
113
+ layout: SegmentedControlLayout
114
+ onSelect?: (value: string) => void
115
+ children: React.ReactNode
116
+ theme: ReturnType<typeof useTheme>
117
+ }) => {
118
+ const { controlItem } = theme.tokens.components.segmentedControl
119
+ const { size, spacing, borderRadius, colour, opacity } = controlItem
120
+ const { typography } = controlItem
121
+
122
+ const handlePress = () => {
123
+ if (disabled) return
124
+ onSelect?.(value)
125
+ }
126
+
127
+ const { token, color } = selected
128
+ ? {
129
+ token: typography.label.default,
130
+ color: colour.text.selected
131
+ }
132
+ : {
133
+ token: typography.label.inactive,
134
+ color: colour.text.unselected
135
+ }
136
+
137
+ return (
138
+ <StyledSegmentItem
139
+ onPress={handlePress}
140
+ disabled={disabled}
141
+ accessibilityRole="button"
142
+ accessibilityState={{ selected, disabled }}
143
+ itemHeight={parseTokenValue(size.small.height)}
144
+ itemMinWidth={parseTokenValue(size.small.minWidth)}
145
+ itemPaddingVertical={parseTokenValue(spacing.verticalPadding)}
146
+ itemPaddingHorizontal={parseTokenValue(spacing.horizontalPadding)}
147
+ itemBorderRadius={parseTokenValue(borderRadius.default)}
148
+ itemBgColor={selected ? colour.background.selected : "transparent"}
149
+ itemOpacity={disabled ? parseFloat(opacity.disabled.default) : 1}
150
+ itemFlex={layout === "fixed" ? 1 : undefined}
151
+ >
152
+ <Typography token={token} color={color}>
153
+ {children}
154
+ </Typography>
155
+ </StyledSegmentItem>
156
+ )
157
+ }
158
+
159
+ /**
160
+ * SegmentedControl.Item for compound component API.
161
+ */
162
+ const SegmentedControlItem = React.forwardRef<View, SegmentedControlItemProps>(
163
+ (_props, _ref) => {
164
+ return null
165
+ }
166
+ )
167
+
168
+ SegmentedControlItem.displayName = "SegmentedControl.Item"
169
+
170
+ /**
171
+ * Segmented control component for selecting exactly one option from a set.
172
+ *
173
+ * @param {Array} [options] - Options to render as segments.
174
+ * @param {string} [value] - Controlled selected value.
175
+ * @param {string} [defaultValue] - Uncontrolled initial selected value.
176
+ * @param {(value: string) => void} [onValueChange] - Called when the selected value changes.
177
+ * @param {"fixed" | "intrinsic"} [layout="intrinsic"] - Layout mode.
178
+ * @param {boolean} [disabled=false] - Whether the entire control is disabled.
179
+ *
180
+ * @example
181
+ * <SegmentedControl
182
+ * options={[{ value: "left", label: "Left" }, { value: "right", label: "Right" }]}
183
+ * defaultValue="left"
184
+ * />
185
+ *
186
+ * @example
187
+ * <SegmentedControl value={value} onValueChange={setValue}>
188
+ * <SegmentedControl.Item value="left">Left</SegmentedControl.Item>
189
+ * <SegmentedControl.Item value="right">Right</SegmentedControl.Item>
190
+ * </SegmentedControl>
191
+ */
192
+ const SegmentedControlRoot = React.forwardRef<View, SegmentedControlProps>(
193
+ (
194
+ {
195
+ options,
196
+ value,
197
+ defaultValue,
198
+ onValueChange,
199
+ layout = "intrinsic",
200
+ disabled = false,
201
+ children,
202
+ ...rest
203
+ },
204
+ ref
205
+ ) => {
206
+ const theme = useTheme()
207
+ const { spacing, borderRadius, colour } =
208
+ theme.tokens.components.segmentedControl
209
+
210
+ const optionsFirstValue = options?.[0]?.value
211
+ const childrenArray = React.Children.toArray(children)
212
+ const firstChildValue = childrenArray
213
+ .map((child) => {
214
+ if (!React.isValidElement(child)) return undefined
215
+ return (child.props as { value?: string }).value
216
+ })
217
+ .find((v): v is string => typeof v === "string")
218
+
219
+ const derivedDefaultValue =
220
+ defaultValue ?? optionsFirstValue ?? firstChildValue
221
+
222
+ const isControlled = typeof value === "string"
223
+ const [uncontrolledValue, setUncontrolledValue] = React.useState(
224
+ () => derivedDefaultValue ?? ""
225
+ )
226
+ const currentValue = isControlled ? value : uncontrolledValue
227
+
228
+ const handleSelect = React.useCallback(
229
+ (newValue: string) => {
230
+ if (!isControlled) {
231
+ setUncontrolledValue(newValue)
232
+ }
233
+ onValueChange?.(newValue)
234
+ },
235
+ [isControlled, onValueChange]
236
+ )
237
+
238
+ const renderItems = () => {
239
+ if (options) {
240
+ return options.map((option) => (
241
+ <SegmentedControlItemBase
242
+ key={option.value}
243
+ value={option.value}
244
+ disabled={disabled || option.disabled}
245
+ selected={currentValue === option.value}
246
+ layout={layout}
247
+ onSelect={handleSelect}
248
+ theme={theme}
249
+ >
250
+ {option.label}
251
+ </SegmentedControlItemBase>
252
+ ))
253
+ }
254
+
255
+ return childrenArray.map((child) => {
256
+ if (!React.isValidElement(child)) return child
257
+ const childProps = child.props as SegmentedControlItemOwnProps
258
+ return (
259
+ <SegmentedControlItemBase
260
+ key={childProps.value}
261
+ value={childProps.value}
262
+ disabled={disabled || childProps.disabled}
263
+ selected={currentValue === childProps.value}
264
+ layout={layout}
265
+ onSelect={handleSelect}
266
+ theme={theme}
267
+ >
268
+ {childProps.children}
269
+ </SegmentedControlItemBase>
270
+ )
271
+ })
272
+ }
273
+
274
+ const containerProps = {
275
+ containerGap: parseTokenValue(spacing.gap),
276
+ containerPaddingVertical: parseTokenValue(spacing.verticalPadding),
277
+ containerPaddingHorizontal: parseTokenValue(spacing.horizontalPadding),
278
+ containerBorderRadius: parseTokenValue(borderRadius.default),
279
+ containerBgColor: colour.background.default,
280
+ containerFullWidth: layout === "fixed"
281
+ }
282
+
283
+ if (layout === "intrinsic") {
284
+ return (
285
+ <View ref={ref} {...rest}>
286
+ <ScrollView
287
+ horizontal
288
+ showsHorizontalScrollIndicator={false}
289
+ contentContainerStyle={{
290
+ flexDirection: "row",
291
+ alignItems: "stretch",
292
+ gap: containerProps.containerGap,
293
+ paddingVertical: containerProps.containerPaddingVertical,
294
+ paddingHorizontal: containerProps.containerPaddingHorizontal,
295
+ borderRadius: containerProps.containerBorderRadius,
296
+ backgroundColor: containerProps.containerBgColor
297
+ }}
298
+ >
299
+ {renderItems()}
300
+ </ScrollView>
301
+ </View>
302
+ )
303
+ }
304
+
305
+ return (
306
+ <StyledContainer ref={ref} {...containerProps} {...rest}>
307
+ {renderItems()}
308
+ </StyledContainer>
309
+ )
310
+ }
311
+ )
312
+
313
+ SegmentedControlRoot.displayName = "SegmentedControl"
314
+
315
+ type SegmentedControlComponent = React.ForwardRefExoticComponent<
316
+ SegmentedControlProps & React.RefAttributes<View>
317
+ > & {
318
+ Item: typeof SegmentedControlItem
319
+ }
320
+
321
+ export const SegmentedControl = Object.assign(SegmentedControlRoot, {
322
+ Item: SegmentedControlItem
323
+ }) as SegmentedControlComponent
@@ -0,0 +1,5 @@
1
+ export { SegmentedControl } from "./SegmentedControl"
2
+ export type {
3
+ SegmentedControlProps,
4
+ SegmentedControlItemProps
5
+ } from "./SegmentedControl"
@@ -0,0 +1,144 @@
1
+ import React, { useState } from "react"
2
+ import { View, StyleSheet } from "react-native"
3
+ import { Slider } from "./Slider"
4
+ import type { SliderProps } from "./Slider"
5
+ import { Typography } from "../../atoms/Typography"
6
+
7
+ export default {
8
+ title: "Molecules/Slider",
9
+ component: Slider,
10
+ argTypes: {
11
+ min: {
12
+ control: { type: "number" },
13
+ description: "Minimum value"
14
+ },
15
+ max: {
16
+ control: { type: "number" },
17
+ description: "Maximum value"
18
+ },
19
+ step: {
20
+ control: { type: "number" },
21
+ description: "Step increment"
22
+ },
23
+ disabled: {
24
+ control: { type: "boolean" },
25
+ description: "Prevents interaction"
26
+ },
27
+ leadingLabel: {
28
+ control: { type: "text" },
29
+ description: "Label to the left of track"
30
+ },
31
+ trailingLabel: {
32
+ control: { type: "text" },
33
+ description: "Label to the right of track"
34
+ },
35
+ description: {
36
+ control: { type: "text" },
37
+ description: "Descriptive text below control"
38
+ }
39
+ }
40
+ }
41
+
42
+ export const Default = () => (
43
+ <View style={styles.column}>
44
+ <View style={styles.section}>
45
+ <Typography size="sm" weight="semiBold" color="tertiary">
46
+ No labels
47
+ </Typography>
48
+ <Slider defaultValue={50} />
49
+ </View>
50
+
51
+ <View style={styles.section}>
52
+ <Typography size="sm" weight="semiBold" color="tertiary">
53
+ With labels
54
+ </Typography>
55
+ <Slider defaultValue={25} leadingLabel="0kg" trailingLabel="50kg+" />
56
+ </View>
57
+
58
+ <View style={styles.section}>
59
+ <Typography size="sm" weight="semiBold" color="tertiary">
60
+ With description
61
+ </Typography>
62
+ <Slider
63
+ defaultValue={40}
64
+ leadingLabel="Light"
65
+ trailingLabel="Heavy"
66
+ description="Adjust the weight of your dog's meals."
67
+ />
68
+ </View>
69
+ </View>
70
+ )
71
+
72
+ export const States = () => (
73
+ <View style={styles.column}>
74
+ <View style={styles.section}>
75
+ <Typography size="sm" weight="semiBold" color="tertiary">
76
+ 25%
77
+ </Typography>
78
+ <Slider defaultValue={25} leadingLabel="0" trailingLabel="100" />
79
+ </View>
80
+
81
+ <View style={styles.section}>
82
+ <Typography size="sm" weight="semiBold" color="tertiary">
83
+ 50%
84
+ </Typography>
85
+ <Slider defaultValue={50} leadingLabel="0" trailingLabel="100" />
86
+ </View>
87
+
88
+ <View style={styles.section}>
89
+ <Typography size="sm" weight="semiBold" color="tertiary">
90
+ 100%
91
+ </Typography>
92
+ <Slider defaultValue={100} leadingLabel="0" trailingLabel="100" />
93
+ </View>
94
+
95
+ <View style={styles.section}>
96
+ <Typography size="sm" weight="semiBold" color="tertiary">
97
+ Disabled
98
+ </Typography>
99
+ <Slider defaultValue={50} leadingLabel="0" trailingLabel="100" disabled />
100
+ </View>
101
+ </View>
102
+ )
103
+
104
+ export const Controlled = () => {
105
+ const [value, setValue] = useState(30)
106
+
107
+ return (
108
+ <View style={styles.column}>
109
+ <Typography size="sm" weight="semiBold" color="tertiary">
110
+ Value: {value}
111
+ </Typography>
112
+ <Slider
113
+ value={value}
114
+ onValueChange={setValue}
115
+ leadingLabel="0kg"
116
+ trailingLabel="50kg+"
117
+ />
118
+ </View>
119
+ )
120
+ }
121
+
122
+ export const Playground = {
123
+ args: {
124
+ min: 0,
125
+ max: 100,
126
+ step: 1,
127
+ disabled: false,
128
+ leadingLabel: "Min",
129
+ trailingLabel: "Max",
130
+ description: ""
131
+ },
132
+ render: (args: SliderProps) => <Slider {...args} defaultValue={50} />
133
+ }
134
+
135
+ const styles = StyleSheet.create({
136
+ column: {
137
+ flexDirection: "column",
138
+ gap: 32
139
+ },
140
+ section: {
141
+ flexDirection: "column",
142
+ gap: 12
143
+ }
144
+ })