@ornikar/bumper 3.13.0 → 3.14.0

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 (68) hide show
  1. package/CHANGELOG.md +6 -143
  2. package/dist/definitions/index.d.ts +2 -0
  3. package/dist/definitions/index.d.ts.map +1 -1
  4. package/dist/definitions/system/core/themes/light/light.d.ts.map +1 -1
  5. package/dist/definitions/system/core/themes/types.d.ts +3 -1
  6. package/dist/definitions/system/core/themes/types.d.ts.map +1 -1
  7. package/dist/definitions/system/dataDisplays/Avatar/Avatar.d.ts +52 -0
  8. package/dist/definitions/system/dataDisplays/Avatar/Avatar.d.ts.map +1 -0
  9. package/dist/definitions/system/dataDisplays/Avatar/components/AvatarImage.d.ts +5 -0
  10. package/dist/definitions/system/dataDisplays/Avatar/components/AvatarImage.d.ts.map +1 -0
  11. package/dist/definitions/system/dataDisplays/Avatar/components/AvatarInitial.d.ts +6 -0
  12. package/dist/definitions/system/dataDisplays/Avatar/components/AvatarInitial.d.ts.map +1 -0
  13. package/dist/definitions/system/dataDisplays/Avatar/context.d.ts +3 -0
  14. package/dist/definitions/system/dataDisplays/Avatar/context.d.ts.map +1 -0
  15. package/dist/index-metro.es.android.js +141 -9
  16. package/dist/index-metro.es.android.js.map +1 -1
  17. package/dist/index-metro.es.ios.js +141 -9
  18. package/dist/index-metro.es.ios.js.map +1 -1
  19. package/dist/index-node-22.22.cjs.js +140 -7
  20. package/dist/index-node-22.22.cjs.js.map +1 -1
  21. package/dist/index-node-22.22.cjs.web.js +140 -7
  22. package/dist/index-node-22.22.cjs.web.js.map +1 -1
  23. package/dist/index-node-22.22.es.mjs +140 -9
  24. package/dist/index-node-22.22.es.mjs.map +1 -1
  25. package/dist/index-node-22.22.es.web.mjs +140 -9
  26. package/dist/index-node-22.22.es.web.mjs.map +1 -1
  27. package/dist/index.es.js +140 -9
  28. package/dist/index.es.js.map +1 -1
  29. package/dist/index.es.web.js +140 -9
  30. package/dist/index.es.web.js.map +1 -1
  31. package/dist/storybook-metro.es.android.js +3 -1
  32. package/dist/storybook-metro.es.android.js.map +1 -1
  33. package/dist/storybook-metro.es.ios.js +3 -1
  34. package/dist/storybook-metro.es.ios.js.map +1 -1
  35. package/dist/storybook-node-22.22.cjs.js +3 -1
  36. package/dist/storybook-node-22.22.cjs.js.map +1 -1
  37. package/dist/storybook-node-22.22.cjs.web.js +3 -1
  38. package/dist/storybook-node-22.22.cjs.web.js.map +1 -1
  39. package/dist/storybook-node-22.22.es.mjs +3 -1
  40. package/dist/storybook-node-22.22.es.mjs.map +1 -1
  41. package/dist/storybook-node-22.22.es.web.mjs +3 -1
  42. package/dist/storybook-node-22.22.es.web.mjs.map +1 -1
  43. package/dist/storybook.es.js +3 -1
  44. package/dist/storybook.es.js.map +1 -1
  45. package/dist/storybook.es.web.js +3 -1
  46. package/dist/storybook.es.web.js.map +1 -1
  47. package/dist/tsbuildinfo +1 -1
  48. package/docs/migration/Avatar.md +228 -0
  49. package/package.json +1 -1
  50. package/src/Bumper.mdx +1 -0
  51. package/src/index.ts +2 -0
  52. package/src/system/core/themes/light/__snapshots__/light.stories.tsx.snap +73 -0
  53. package/src/system/core/themes/light/__snapshots_web__/light.stories.tsx.snap +21 -0
  54. package/src/system/core/themes/light/light.ts +3 -0
  55. package/src/system/core/themes/types.ts +5 -1
  56. package/src/system/dataDisplays/Avatar/Avatar.features.stories.tsx +110 -0
  57. package/src/system/dataDisplays/Avatar/Avatar.mdx +73 -0
  58. package/src/system/dataDisplays/Avatar/Avatar.stories.tsx +47 -0
  59. package/src/system/dataDisplays/Avatar/Avatar.tsx +124 -0
  60. package/src/system/dataDisplays/Avatar/__snapshots__/Avatar.features.stories.tsx.snap +891 -0
  61. package/src/system/dataDisplays/Avatar/__snapshots__/Avatar.stories.tsx.snap +50 -0
  62. package/src/system/dataDisplays/Avatar/__snapshots_web__/Avatar.features.stories.tsx.snap +545 -0
  63. package/src/system/dataDisplays/Avatar/__snapshots_web__/Avatar.stories.tsx.snap +37 -0
  64. package/src/system/dataDisplays/Avatar/assets/avatar-placeholder-disabled.webp +0 -0
  65. package/src/system/dataDisplays/Avatar/assets/avatar-placeholder.webp +0 -0
  66. package/src/system/dataDisplays/Avatar/components/AvatarImage.tsx +34 -0
  67. package/src/system/dataDisplays/Avatar/components/AvatarInitial.tsx +30 -0
  68. package/src/system/dataDisplays/Avatar/context.ts +10 -0
@@ -0,0 +1,228 @@
1
+ # Avatar → Avatar Migration Guide
2
+
3
+ > Source: `@ornikar/kitt-universal/Avatar`
4
+ > Target: `@ornikar/bumper/Avatar`
5
+ > Generated: 2026-04-27
6
+
7
+ ## Import Change
8
+
9
+ ```tsx
10
+ // Before
11
+ import { Avatar } from '@ornikar/kitt-universal';
12
+ import type { AvatarProps } from '@ornikar/kitt-universal';
13
+
14
+ // After
15
+ import { Avatar } from '@ornikar/bumper';
16
+ import type { AvatarProps } from '@ornikar/bumper';
17
+ ```
18
+
19
+ ## Props Mapping
20
+
21
+ | Source Prop | Target Prop | Transform | Notes |
22
+ | ------------------------------ | ---------------------- | ----------------------------- | --------------------------------------------- |
23
+ | `src` | `src` | Keep as-is | Same API |
24
+ | `firstname` (`string \| null`) | `firstname` (`string`) | Narrow `null` to `undefined` | See [Null Name Handling](#null-name-handling) |
25
+ | `lastname` (`string \| null`) | `lastname` (`string`) | Narrow `null` to `undefined` | See [Null Name Handling](#null-name-handling) |
26
+ | `disabled` | `disabled` | Keep as-is | Same API |
27
+ | `width` | `width` | Keep as-is | Used only when `size` is omitted |
28
+ | `height` | `height` | Keep as-is | Used only when `size` is omitted |
29
+ | `size` (`number`) | `size` (enum) | Map number to nearest bucket | See [Size Values](#size-values) |
30
+ | `round` (`boolean`) | `shape` (enum) | Convert boolean to enum value | See [Shape Mapping](#shape-mapping) |
31
+ | `alt` | — | Remove prop | Not exposed in bumper AvatarProps |
32
+ | `light` | — | Remove prop | No light/dark theme variant in bumper |
33
+ | `dark` | — | Remove prop | No light/dark theme variant in bumper |
34
+ | `sizeVariant` | — | Remove prop | Only ever `'large'`; merged into `size` enum |
35
+
36
+ Bumper adds new props with no kitt-universal equivalent:
37
+
38
+ - `testID` — testing hook, optional
39
+ - `shape` — explicit replacement for the `round` boolean
40
+
41
+ ## Value Mappings
42
+
43
+ ### Size Values
44
+
45
+ Bumper sizes map to fixed pixel dimensions (square):
46
+
47
+ ```
48
+ small → 32×32
49
+ medium → 40×40
50
+ large → 48×48 (default)
51
+ xlarge → 64×64
52
+ ```
53
+
54
+ Map a numeric `size` to the closest enum bucket:
55
+
56
+ ```
57
+ size={32} → size="small"
58
+ size={40} → size="medium"
59
+ size={48} → (remove prop — large is the default)
60
+ size={64} → size="xlarge"
61
+ ```
62
+
63
+ For numeric values that do **not** match a bucket exactly, prefer preserving the literal pixel value via `width`/`height` to avoid silent visual regressions:
64
+
65
+ ```tsx
66
+ // Before
67
+ <Avatar size={36} firstname="Jane" />
68
+
69
+ // After
70
+ <Avatar width={36} height={36} firstname="Jane" />
71
+ ```
72
+
73
+ For dynamic numeric values (`size={someNumber}`), flag for manual review.
74
+
75
+ ### Shape Mapping
76
+
77
+ ```
78
+ round → shape="circle" (boolean shorthand)
79
+ round={true} → shape="circle"
80
+ round={false} → (remove prop — square is the default)
81
+ round={expr} → shape={expr ? 'circle' : 'square'}
82
+ ```
83
+
84
+ ### sizeVariant Mapping
85
+
86
+ `sizeVariant` only accepted `'large'` and was redundant with `size`. Always remove it:
87
+
88
+ ```
89
+ sizeVariant="large" → (remove prop entirely)
90
+ ```
91
+
92
+ ## Null Name Handling
93
+
94
+ Bumper's `firstname` and `lastname` are `string | undefined`, not `string | null`. Convert literal `null` to an omitted prop, and runtime nullable values via `?? undefined`:
95
+
96
+ ```tsx
97
+ // Before
98
+ <Avatar firstname={null} lastname={null} />
99
+
100
+ // After
101
+ <Avatar />
102
+ ```
103
+
104
+ ```tsx
105
+ // Before — user.firstname / user.lastname typed `string | null`
106
+ <Avatar firstname={user.firstname} lastname={user.lastname} />
107
+
108
+ // After
109
+ <Avatar firstname={user.firstname ?? undefined} lastname={user.lastname ?? undefined} />
110
+ ```
111
+
112
+ When both names resolve to falsy and no `src` is given, bumper falls back to the placeholder image automatically — same behavior as kitt-universal.
113
+
114
+ ## Behavioral Differences
115
+
116
+ | Aspect | kitt-universal | bumper | Notes |
117
+ | --------------------- | ---------------------------- | ---------------------------------------------------- | --------------------------------------- |
118
+ | **Default shape** | square (`round=false`) | `square` | Same |
119
+ | **Default size** | implicit (no enum) | `large` (48×48) | Bumper enforces an explicit default |
120
+ | **Theme variants** | `light` / `dark` props | none | Removed; rely on surrounding theme |
121
+ | **Image alt text** | `alt` prop | none | No accessibility alt at component level |
122
+ | **Border radius** | hardcoded | `$radius.m` (square) / `$radius.circle` (circle) | Token-based, may differ visually |
123
+ | **Disabled state** | swap to disabled placeholder | swap to disabled placeholder + `$bg.disabled.hi` bg | Similar, with explicit token background |
124
+ | **Initials fallback** | `firstname` + `lastname` | `firstname` + `lastname` | Same |
125
+ | **Responsive props** | not supported | Tamagui media query props (`$small`, `$medium`, ...) | New capability |
126
+
127
+ ## Edge Cases
128
+
129
+ ### Conditional `round`
130
+
131
+ ```tsx
132
+ // Before
133
+ <Avatar round={isCircle} firstname="Jane" />
134
+
135
+ // After
136
+ <Avatar shape={isCircle ? 'circle' : 'square'} firstname="Jane" />
137
+ ```
138
+
139
+ ### Spread props from AvatarProps
140
+
141
+ If components spread `AvatarProps`, destructure the removed props out and convert `round`:
142
+
143
+ ```tsx
144
+ // Before
145
+ const { alt, light, dark, sizeVariant, ...rest } = avatarProps;
146
+ <Avatar {...rest} />;
147
+
148
+ // After
149
+ const { alt, light, dark, sizeVariant, round, ...rest } = avatarProps;
150
+ <Avatar {...rest} shape={round ? 'circle' : 'square'} />;
151
+ ```
152
+
153
+ ### Wrapper / re-export migration
154
+
155
+ Components that pick or extend `AvatarProps` need type updates:
156
+
157
+ ```tsx
158
+ // Before
159
+ import type { AvatarProps } from '@ornikar/kitt-universal';
160
+ interface MyAvatarProps extends Pick<AvatarProps, 'src' | 'firstname' | 'lastname'> { ... }
161
+
162
+ // After
163
+ import type { AvatarProps } from '@ornikar/bumper';
164
+ interface MyAvatarProps extends Pick<AvatarProps, 'src' | 'firstname' | 'lastname'> { ... }
165
+ // firstname / lastname narrow from `string | null` to `string` — update callers if any pass null
166
+ ```
167
+
168
+ For wrappers that pick removed props (`alt`, `light`, `dark`, `sizeVariant`, `round`):
169
+
170
+ ```tsx
171
+ // Before
172
+ interface MyProps extends Pick<AvatarProps, 'round' | 'alt'> { ... }
173
+
174
+ // After — these props no longer exist on AvatarProps
175
+ // round → expose `shape` instead
176
+ // alt → drop, or expose only if the wrapper handles it itself
177
+ interface MyProps {
178
+ shape?: 'square' | 'circle';
179
+ }
180
+ ```
181
+
182
+ ### Numeric `size` matching the default
183
+
184
+ When the source explicitly sets `size={48}`, prefer removing the prop entirely over emitting `size="large"` — it is the default and reduces churn:
185
+
186
+ ```tsx
187
+ // Before
188
+ <Avatar size={48} firstname="Jane" />
189
+
190
+ // After
191
+ <Avatar firstname="Jane" />
192
+ ```
193
+
194
+ ## Migration Rules (machine-readable)
195
+
196
+ Apply these rules in order for each `<Avatar>` usage:
197
+
198
+ 1. **Update import**: Replace `'@ornikar/kitt-universal'` with `'@ornikar/bumper'` for `Avatar` and `AvatarProps` imports.
199
+
200
+ 2. **Remove unsupported props**: Delete `alt`, `light`, `dark`, `sizeVariant`.
201
+
202
+ 3. **Convert `round`**:
203
+
204
+ - `round={true}` or bare `round` → `shape="circle"`
205
+ - `round={false}` → remove the prop
206
+ - `round={expr}` → `shape={expr ? 'circle' : 'square'}`
207
+
208
+ 4. **Convert `size`** (numeric literal):
209
+
210
+ - `size={32}` → `size="small"`
211
+ - `size={40}` → `size="medium"`
212
+ - `size={48}` → remove the prop (`large` is the default)
213
+ - `size={64}` → `size="xlarge"`
214
+ - Any other literal → replace with `width={n} height={n}` and remove `size`
215
+ - Variable / expression → flag for manual review
216
+
217
+ 5. **Narrow nullable names**:
218
+ - `firstname={null}` literal → remove prop
219
+ - `lastname={null}` literal → remove prop
220
+ - `firstname={value}` where `value` is typed `string | null` → `firstname={value ?? undefined}`
221
+ - Same for `lastname`
222
+
223
+ ## Not Migratable (requires human review)
224
+
225
+ - **Dynamic numeric `size`**: When `size` receives a runtime variable (not a literal), the bucket cannot be inferred mechanically. Ask the user how to proceed, or fall back to `width`/`height` if the original value is preserved.
226
+ - **`alt` prop usage**: If the call site relies on `alt` for accessibility, decide whether to wrap the Avatar in an aria-labeled element or accept the regression.
227
+ - **`light` / `dark` props**: If the call site visibly depends on the inverted palette, the surrounding theme context may need to change. Review with design before stripping silently.
228
+ - **Components extending `AvatarProps`**: Proceed with the changes, only ask for user input if the linting is failing.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ornikar/bumper",
3
- "version": "3.13.0",
3
+ "version": "3.14.0",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "directory": "@ornikar/bumper",
package/src/Bumper.mdx CHANGED
@@ -34,6 +34,7 @@ spacing, color, radius, and typography.
34
34
 
35
35
  ## Data Displays
36
36
 
37
+ - [Avatar](?path=/docs/bumper-data-displays-avatar--docs) — User representation with image, initials, or placeholder fallback.
37
38
  - [Badge](?path=/docs/bumper-data-displays-badge--docs) — Numeric count or dot indicator.
38
39
 
39
40
  ## Loading
package/src/index.ts CHANGED
@@ -27,6 +27,8 @@ export type { TypographyIconProps } from './system/content/typography/Typography
27
27
  export type { TypographyLinkProps } from './system/content/typography/TypographyLink';
28
28
 
29
29
  // Data Displays
30
+ export type { AvatarProps } from './system/dataDisplays/Avatar/Avatar';
31
+ export { Avatar } from './system/dataDisplays/Avatar/Avatar';
30
32
  export type { BadgeProps } from './system/dataDisplays/Badge/Badge';
31
33
  export { Badge } from './system/dataDisplays/Badge/Badge';
32
34
  export { Sticker } from './system/dataDisplays/Sticker/Sticker';
@@ -5902,6 +5902,79 @@ exports[`Bumper/Core/Themes Light 1`] = `
5902
5902
  #FDE4E3
5903
5903
  </Text>
5904
5904
  </View>
5905
+ <View
5906
+ style={
5907
+ {
5908
+ "alignItems": "center",
5909
+ "flexDirection": "row",
5910
+ "gap": 16,
5911
+ }
5912
+ }
5913
+ >
5914
+ <View
5915
+ style={
5916
+ {
5917
+ "alignItems": "center",
5918
+ "backgroundColor": "#EAE3D6",
5919
+ "borderBottomColor": "#F1ECE4",
5920
+ "borderBottomLeftRadius": 4,
5921
+ "borderBottomRightRadius": 4,
5922
+ "borderBottomWidth": 1,
5923
+ "borderLeftColor": "#F1ECE4",
5924
+ "borderLeftWidth": 1,
5925
+ "borderRightColor": "#F1ECE4",
5926
+ "borderRightWidth": 1,
5927
+ "borderStyle": "solid",
5928
+ "borderTopColor": "#F1ECE4",
5929
+ "borderTopLeftRadius": 4,
5930
+ "borderTopRightRadius": 4,
5931
+ "borderTopWidth": 1,
5932
+ "height": 48,
5933
+ "justifyContent": "center",
5934
+ "width": 48,
5935
+ }
5936
+ }
5937
+ />
5938
+ <View
5939
+ style={
5940
+ {
5941
+ "width": 280,
5942
+ }
5943
+ }
5944
+ >
5945
+ <Text
5946
+ style={
5947
+ {
5948
+ "color": "#101010",
5949
+ "fontFamily": "GTStandardBold",
5950
+ "fontSize": 14,
5951
+ "letterSpacing": 0.3,
5952
+ "lineHeight": 20,
5953
+ "textAlign": "left",
5954
+ }
5955
+ }
5956
+ suppressHighlighting={true}
5957
+ >
5958
+ $
5959
+ avatar.bg
5960
+ </Text>
5961
+ </View>
5962
+ <Text
5963
+ style={
5964
+ {
5965
+ "color": "#505050",
5966
+ "fontFamily": "GTStandardRegular",
5967
+ "fontSize": 14,
5968
+ "letterSpacing": 0.3,
5969
+ "lineHeight": 20,
5970
+ "textAlign": "left",
5971
+ }
5972
+ }
5973
+ suppressHighlighting={true}
5974
+ >
5975
+ #EAE3D6
5976
+ </Text>
5977
+ </View>
5905
5978
  </View>
5906
5979
  </View>
5907
5980
  </RNCSafeAreaProvider>
@@ -1713,6 +1713,27 @@ exports[`Bumper/Core/Themes Light 1`] = `
1713
1713
  #FDE4E3
1714
1714
  </span>
1715
1715
  </div>
1716
+ <div
1717
+ class="is_HStack _dsp-flex _fb-auto _bxs-border-box _pos-relative _minHeight-0px _minWidth-0px _flexShrink-0 _fd-row _gap-t-space-spa1366020313 _alignItems-center"
1718
+ >
1719
+ <div
1720
+ class="_dsp-flex _fd-column _fb-auto _bxs-border-box _pos-relative _minHeight-0px _minWidth-0px _flexShrink-0 _width-t-size-size1385508 _height-t-size-size1385508 _btlr-t-radius-ra1673638410 _btrr-t-radius-ra1673638410 _bbrr-t-radius-ra1673638410 _bblr-t-radius-ra1673638410 _backgroundColor-EAE3D635 _btw-1px _brw-1px _borderBottomWidth-1px _borderLeftWidth-1px _btc-border--bas1360416657 _brc-border--bas1360416657 _borderBottomColor-border--bas1360416657 _borderLeftColor-border--bas1360416657 _alignItems-center _justifyContent-center _borderBottomStyle-solid _borderTopStyle-solid _borderLeftStyle-solid _borderRightStyle-solid"
1721
+ />
1722
+ <div
1723
+ class="_dsp-flex _alignItems-stretch _fd-column _fb-auto _bxs-border-box _pos-relative _minHeight-0px _minWidth-0px _flexShrink-0 _width-280px"
1724
+ >
1725
+ <span
1726
+ class="font_GTStandard _WebkitFontSmoothing-_platformweb_antialiased _dsp-inline _bxs-border-box _ww-break-word _ws-pre-wrap _marginTop-0px _marginRight-0px _marginBottom-0px _marginLeft-0px _ff-f-family _fs-f-size-body1510 _lh-f-lineHeigh201793153 _ls-f-letterSpa1099960310 _fw-f-weight-bo3448 _col-content--ba907952141 _textAlign-left"
1727
+ >
1728
+ $avatar.bg
1729
+ </span>
1730
+ </div>
1731
+ <span
1732
+ class="font_GTStandard _WebkitFontSmoothing-_platformweb_antialiased _dsp-inline _bxs-border-box _ww-break-word _ws-pre-wrap _marginTop-0px _marginRight-0px _marginBottom-0px _marginLeft-0px _ff-f-family _fs-f-size-body1510 _lh-f-lineHeigh201793153 _ls-f-letterSpa1099960310 _fw-f-weight-re98715119 _textAlign-left _col-content--ba1918259606"
1733
+ >
1734
+ #EAE3D6
1735
+ </span>
1736
+ </div>
1716
1737
  </div>
1717
1738
  </div>
1718
1739
  </span>
@@ -92,4 +92,7 @@ export const light: Theme = {
92
92
  'button.bg.danger.pressed': deepPurpleColorPalette['palette.red.1'],
93
93
  'button.bg.danger.onContrasted.default': deepPurpleColorPalette['palette.white'],
94
94
  'button.bg.danger.onContrasted.pressed': deepPurpleColorPalette['palette.red.1'],
95
+
96
+ // Avatar
97
+ 'avatar.bg': deepPurpleColorPalette['palette.beige.3'],
95
98
  };
@@ -102,4 +102,8 @@ export const THEME_BUTTON = [
102
102
  'button.bg.danger.onContrasted.pressed',
103
103
  ] as const;
104
104
 
105
- export type Theme = ThemeContent & ThemeBackground & ThemeBorder & ThemeDivider & ThemeButton;
105
+ export type ThemeAvatar = Record<(typeof THEME_AVATAR)[number], string>;
106
+
107
+ export const THEME_AVATAR = ['avatar.bg'] as const;
108
+
109
+ export type Theme = ThemeContent & ThemeBackground & ThemeBorder & ThemeDivider & ThemeButton & ThemeAvatar;
@@ -0,0 +1,110 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { HStack, VStack } from '../../core/primitives/Stack';
3
+ import { Avatar } from './Avatar';
4
+
5
+ const meta: Meta<typeof Avatar> = {
6
+ title: 'Bumper/Data Displays/Avatar/Features',
7
+ component: Avatar,
8
+ };
9
+
10
+ export default meta;
11
+ type Story = StoryObj<typeof meta>;
12
+
13
+ export const Placeholder: Story = {
14
+ args: {
15
+ size: 'large',
16
+ shape: 'square',
17
+ },
18
+ };
19
+
20
+ export const WithInitials: Story = {
21
+ args: {
22
+ size: 'large',
23
+ shape: 'square',
24
+ firstname: 'John',
25
+ lastname: 'Doe',
26
+ },
27
+ };
28
+
29
+ export const WithImage: Story = {
30
+ args: {
31
+ size: 'large',
32
+ shape: 'square',
33
+ src: 'https://i.pravatar.cc/150?img=5',
34
+ },
35
+ };
36
+
37
+ export const Sizes: Story = {
38
+ render: () => (
39
+ <HStack gap="$space.16" alignItems="center">
40
+ <Avatar size="small" shape="square" firstname="John" lastname="Doe" />
41
+ <Avatar size="medium" shape="square" firstname="John" lastname="Doe" />
42
+ <Avatar size="large" shape="square" firstname="John" lastname="Doe" />
43
+ <Avatar size="xlarge" shape="square" firstname="John" lastname="Doe" />
44
+ </HStack>
45
+ ),
46
+ };
47
+
48
+ export const CircleSizes: Story = {
49
+ render: () => (
50
+ <HStack gap="$space.16" alignItems="center">
51
+ <Avatar size="small" shape="circle" firstname="John" lastname="Doe" />
52
+ <Avatar size="medium" shape="circle" firstname="John" lastname="Doe" />
53
+ <Avatar size="large" shape="circle" firstname="John" lastname="Doe" />
54
+ <Avatar size="xlarge" shape="circle" firstname="John" lastname="Doe" />
55
+ </HStack>
56
+ ),
57
+ };
58
+
59
+ export const Shapes: Story = {
60
+ render: () => (
61
+ <HStack gap="$space.16" alignItems="center">
62
+ <Avatar size="large" shape="square" firstname="John" lastname="Doe" />
63
+ <Avatar size="large" shape="circle" firstname="John" lastname="Doe" />
64
+ </HStack>
65
+ ),
66
+ };
67
+
68
+ export const Disabled: Story = {
69
+ render: () => (
70
+ <HStack gap="$space.16" alignItems="center">
71
+ <Avatar disabled size="large" shape="square" />
72
+ <Avatar disabled size="large" shape="square" firstname="John" lastname="Doe" />
73
+ <Avatar disabled size="large" shape="square" src="https://i.pravatar.cc/150?img=5" />
74
+ </HStack>
75
+ ),
76
+ };
77
+
78
+ export const DisabledShapes: Story = {
79
+ render: () => (
80
+ <HStack gap="$space.16" alignItems="center">
81
+ <Avatar disabled size="large" shape="square" firstname="John" lastname="Doe" />
82
+ <Avatar disabled size="large" shape="circle" firstname="John" lastname="Doe" />
83
+ </HStack>
84
+ ),
85
+ };
86
+
87
+ export const Responsive: Story = {
88
+ render: () => (
89
+ <VStack gap="$space.16">
90
+ <Avatar
91
+ size="small"
92
+ shape="square"
93
+ firstname="John"
94
+ lastname="Doe"
95
+ $medium={{
96
+ size: 'large',
97
+ }}
98
+ />
99
+ <Avatar
100
+ size="large"
101
+ shape="square"
102
+ firstname="John"
103
+ lastname="Doe"
104
+ $medium={{
105
+ shape: 'circle',
106
+ }}
107
+ />
108
+ </VStack>
109
+ ),
110
+ };
@@ -0,0 +1,73 @@
1
+ import { Meta, Title, Subtitle, Primary, Controls, Canvas } from '@storybook/blocks';
2
+ import * as AvatarStories from './Avatar.stories';
3
+ import * as AvatarFeatures from './Avatar.features.stories';
4
+
5
+ <Meta of={AvatarStories} />
6
+ <Title />
7
+ <Subtitle />
8
+
9
+ <Primary />
10
+ <Controls />
11
+
12
+ ## Overview
13
+
14
+ `Avatar` represents a user with an image, initials, or a placeholder fallback. It picks the right content based on which props are passed: `src` wins, then `firstname`/`lastname` fall back to initials, and an empty avatar renders the default placeholder image.
15
+
16
+ ## Placeholder
17
+
18
+ When neither `src` nor `firstname`/`lastname` are provided, the avatar shows the default placeholder image. Use this for unknown or anonymous users.
19
+
20
+ <Canvas of={AvatarFeatures.Placeholder} />
21
+
22
+ ## Initials
23
+
24
+ Pass `firstname` and `lastname` to display the user's initials (e.g. "JD" for John Doe). Use this when no profile picture is available but the user identity is known.
25
+
26
+ <Canvas of={AvatarFeatures.WithInitials} />
27
+
28
+ ## Image
29
+
30
+ Pass `src` to display a user's profile picture. The image is rendered as a background image filling the entire avatar frame, with `overflow: hidden` clipping it to the avatar's shape.
31
+
32
+ <Canvas of={AvatarFeatures.WithImage} />
33
+
34
+ ## Sizes
35
+
36
+ The `size` prop controls the avatar's width and height. Defaults to `large`.
37
+
38
+ | Size | Dimensions |
39
+ | -------- | ---------- |
40
+ | `small` | 32 × 32 px |
41
+ | `medium` | 40 × 40 px |
42
+ | `large` | 48 × 48 px |
43
+ | `xlarge` | 64 × 64 px |
44
+
45
+ If you need a custom size, set `width` and `height` directly instead of `size`.
46
+
47
+ <Canvas of={AvatarFeatures.Sizes} />
48
+
49
+ <Canvas of={AvatarFeatures.CircleSizes} />
50
+
51
+ ## Shapes
52
+
53
+ The `shape` prop controls the corner radius. Defaults to `square` (uses `$radius.m`). Use `circle` (uses `$radius.circle`) for profile pictures in social or chat contexts where a circular crop is the convention.
54
+
55
+ <Canvas of={AvatarFeatures.Shapes} />
56
+
57
+ ## Disabled
58
+
59
+ Set `disabled` to render the avatar in a muted state — the background switches to `$bg.disabled.hi` and content (initials or image) is dimmed. Use this to indicate inactive or unavailable users.
60
+
61
+ <Canvas of={AvatarFeatures.Disabled} />
62
+
63
+ <Canvas of={AvatarFeatures.DisabledShapes} />
64
+
65
+ ## Responsive
66
+
67
+ All props accept Tamagui media query overrides (`$small`, `$medium`, `$large`, `$wide`). Use them to adapt size or shape per breakpoint without rendering multiple avatars.
68
+
69
+ <Canvas of={AvatarFeatures.Responsive} />
70
+
71
+ ## Related
72
+
73
+ - [Badge](?path=/docs/bumper-data-displays-badge--docs)
@@ -0,0 +1,47 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { Avatar } from './Avatar';
3
+
4
+ const meta: Meta<typeof Avatar> = {
5
+ title: 'Bumper/Data Displays/Avatar',
6
+ component: Avatar,
7
+ argTypes: {
8
+ size: {
9
+ control: 'select',
10
+ options: ['small', 'medium', 'large', 'xlarge'],
11
+ description: 'The size of the avatar.',
12
+ },
13
+ shape: {
14
+ control: 'select',
15
+ options: ['square', 'circle'],
16
+ description: 'The shape of the avatar.',
17
+ },
18
+ src: {
19
+ control: 'text',
20
+ description: 'URL of the image to display.',
21
+ },
22
+ firstname: {
23
+ control: 'text',
24
+ description: 'First name used to generate initials.',
25
+ },
26
+ lastname: {
27
+ control: 'text',
28
+ description: 'Last name used to generate initials.',
29
+ },
30
+ disabled: {
31
+ control: 'boolean',
32
+ description: 'Whether the avatar is disabled.',
33
+ },
34
+ },
35
+ };
36
+
37
+ export default meta;
38
+ type Story = StoryObj<typeof meta>;
39
+
40
+ export const Default: Story = {
41
+ args: {
42
+ size: 'large',
43
+ shape: 'square',
44
+ firstname: 'John',
45
+ lastname: 'Doe',
46
+ },
47
+ };