@ankhorage/zora 1.0.4 → 1.0.6

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 (154) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +315 -5
  3. package/dist/components/avatar/Avatar.d.ts +4 -0
  4. package/dist/components/avatar/Avatar.d.ts.map +1 -0
  5. package/dist/components/avatar/Avatar.js +80 -0
  6. package/dist/components/avatar/Avatar.js.map +1 -0
  7. package/dist/components/avatar/index.d.ts +4 -0
  8. package/dist/components/avatar/index.d.ts.map +1 -0
  9. package/dist/components/avatar/index.js +3 -0
  10. package/dist/components/avatar/index.js.map +1 -0
  11. package/dist/components/avatar/resolveAvatarInitials.d.ts +2 -0
  12. package/dist/components/avatar/resolveAvatarInitials.d.ts.map +1 -0
  13. package/dist/components/avatar/resolveAvatarInitials.js +44 -0
  14. package/dist/components/avatar/resolveAvatarInitials.js.map +1 -0
  15. package/dist/components/avatar/types.d.ts +17 -0
  16. package/dist/components/avatar/types.d.ts.map +1 -0
  17. package/dist/components/avatar/types.js +2 -0
  18. package/dist/components/avatar/types.js.map +1 -0
  19. package/dist/components/avatar-group/AvatarGroup.d.ts +4 -0
  20. package/dist/components/avatar-group/AvatarGroup.d.ts.map +1 -0
  21. package/dist/components/avatar-group/AvatarGroup.js +26 -0
  22. package/dist/components/avatar-group/AvatarGroup.js.map +1 -0
  23. package/dist/components/avatar-group/index.d.ts +3 -0
  24. package/dist/components/avatar-group/index.d.ts.map +1 -0
  25. package/dist/components/avatar-group/index.js +2 -0
  26. package/dist/components/avatar-group/index.js.map +1 -0
  27. package/dist/components/avatar-group/types.d.ts +22 -0
  28. package/dist/components/avatar-group/types.d.ts.map +1 -0
  29. package/dist/components/avatar-group/types.js +2 -0
  30. package/dist/components/avatar-group/types.js.map +1 -0
  31. package/dist/components/chip/Chip.d.ts +4 -0
  32. package/dist/components/chip/Chip.d.ts.map +1 -0
  33. package/dist/components/chip/Chip.js +54 -0
  34. package/dist/components/chip/Chip.js.map +1 -0
  35. package/dist/components/chip/index.d.ts +3 -0
  36. package/dist/components/chip/index.d.ts.map +1 -0
  37. package/dist/components/chip/index.js +2 -0
  38. package/dist/components/chip/index.js.map +1 -0
  39. package/dist/components/chip/resolveChipColors.d.ts +10 -0
  40. package/dist/components/chip/resolveChipColors.d.ts.map +1 -0
  41. package/dist/components/chip/resolveChipColors.js +47 -0
  42. package/dist/components/chip/resolveChipColors.js.map +1 -0
  43. package/dist/components/chip/types.d.ts +26 -0
  44. package/dist/components/chip/types.d.ts.map +1 -0
  45. package/dist/components/chip/types.js +2 -0
  46. package/dist/components/chip/types.js.map +1 -0
  47. package/dist/components/chip-group/ChipGroup.d.ts +4 -0
  48. package/dist/components/chip-group/ChipGroup.d.ts.map +1 -0
  49. package/dist/components/chip-group/ChipGroup.js +32 -0
  50. package/dist/components/chip-group/ChipGroup.js.map +1 -0
  51. package/dist/components/chip-group/index.d.ts +3 -0
  52. package/dist/components/chip-group/index.d.ts.map +1 -0
  53. package/dist/components/chip-group/index.js +2 -0
  54. package/dist/components/chip-group/index.js.map +1 -0
  55. package/dist/components/chip-group/types.d.ts +31 -0
  56. package/dist/components/chip-group/types.d.ts.map +1 -0
  57. package/dist/components/chip-group/types.js +2 -0
  58. package/dist/components/chip-group/types.js.map +1 -0
  59. package/dist/components/input/Input.d.ts.map +1 -1
  60. package/dist/components/input/Input.js +3 -2
  61. package/dist/components/input/Input.js.map +1 -1
  62. package/dist/components/input/index.d.ts +1 -1
  63. package/dist/components/input/index.d.ts.map +1 -1
  64. package/dist/components/input/index.js.map +1 -1
  65. package/dist/components/input/types.d.ts +15 -2
  66. package/dist/components/input/types.d.ts.map +1 -1
  67. package/dist/components/input/types.js.map +1 -1
  68. package/dist/components/search-bar/SearchBar.d.ts +4 -0
  69. package/dist/components/search-bar/SearchBar.d.ts.map +1 -0
  70. package/dist/components/search-bar/SearchBar.js +18 -0
  71. package/dist/components/search-bar/SearchBar.js.map +1 -0
  72. package/dist/components/search-bar/index.d.ts +3 -0
  73. package/dist/components/search-bar/index.d.ts.map +1 -0
  74. package/dist/components/search-bar/index.js +2 -0
  75. package/dist/components/search-bar/index.js.map +1 -0
  76. package/dist/components/search-bar/types.d.ts +14 -0
  77. package/dist/components/search-bar/types.d.ts.map +1 -0
  78. package/dist/components/search-bar/types.js +2 -0
  79. package/dist/components/search-bar/types.js.map +1 -0
  80. package/dist/index.d.ts +15 -1
  81. package/dist/index.d.ts.map +1 -1
  82. package/dist/index.js +7 -0
  83. package/dist/index.js.map +1 -1
  84. package/dist/patterns/filter-bar/FilterBar.d.ts +4 -0
  85. package/dist/patterns/filter-bar/FilterBar.d.ts.map +1 -0
  86. package/dist/patterns/filter-bar/FilterBar.js +12 -0
  87. package/dist/patterns/filter-bar/FilterBar.js.map +1 -0
  88. package/dist/patterns/filter-bar/index.d.ts +3 -0
  89. package/dist/patterns/filter-bar/index.d.ts.map +1 -0
  90. package/dist/patterns/filter-bar/index.js +2 -0
  91. package/dist/patterns/filter-bar/index.js.map +1 -0
  92. package/dist/patterns/filter-bar/types.d.ts +9 -0
  93. package/dist/patterns/filter-bar/types.d.ts.map +1 -0
  94. package/dist/patterns/filter-bar/types.js +2 -0
  95. package/dist/patterns/filter-bar/types.js.map +1 -0
  96. package/dist/patterns/list/List.d.ts +4 -0
  97. package/dist/patterns/list/List.d.ts.map +1 -0
  98. package/dist/patterns/list/List.js +35 -0
  99. package/dist/patterns/list/List.js.map +1 -0
  100. package/dist/patterns/list/ListRow.d.ts +4 -0
  101. package/dist/patterns/list/ListRow.d.ts.map +1 -0
  102. package/dist/patterns/list/ListRow.js +108 -0
  103. package/dist/patterns/list/ListRow.js.map +1 -0
  104. package/dist/patterns/list/ListSection.d.ts +4 -0
  105. package/dist/patterns/list/ListSection.d.ts.map +1 -0
  106. package/dist/patterns/list/ListSection.js +14 -0
  107. package/dist/patterns/list/ListSection.js.map +1 -0
  108. package/dist/patterns/list/index.d.ts +5 -0
  109. package/dist/patterns/list/index.d.ts.map +1 -0
  110. package/dist/patterns/list/index.js +4 -0
  111. package/dist/patterns/list/index.js.map +1 -0
  112. package/dist/patterns/list/resolveListSeparator.d.ts +5 -0
  113. package/dist/patterns/list/resolveListSeparator.d.ts.map +1 -0
  114. package/dist/patterns/list/resolveListSeparator.js +6 -0
  115. package/dist/patterns/list/resolveListSeparator.js.map +1 -0
  116. package/dist/patterns/list/types.d.ts +55 -0
  117. package/dist/patterns/list/types.d.ts.map +1 -0
  118. package/dist/patterns/list/types.js +2 -0
  119. package/dist/patterns/list/types.js.map +1 -0
  120. package/package.json +1 -1
  121. package/src/components/avatar/Avatar.tsx +133 -0
  122. package/src/components/avatar/index.ts +3 -0
  123. package/src/components/avatar/resolveAvatarInitials.test.ts +27 -0
  124. package/src/components/avatar/resolveAvatarInitials.ts +46 -0
  125. package/src/components/avatar/types.ts +20 -0
  126. package/src/components/avatar-group/AvatarGroup.tsx +74 -0
  127. package/src/components/avatar-group/index.ts +2 -0
  128. package/src/components/avatar-group/types.ts +24 -0
  129. package/src/components/chip/Chip.tsx +95 -0
  130. package/src/components/chip/index.ts +2 -0
  131. package/src/components/chip/resolveChipColors.ts +65 -0
  132. package/src/components/chip/types.ts +29 -0
  133. package/src/components/chip-group/ChipGroup.tsx +66 -0
  134. package/src/components/chip-group/index.ts +2 -0
  135. package/src/components/chip-group/types.ts +36 -0
  136. package/src/components/input/Input.tsx +17 -1
  137. package/src/components/input/index.ts +1 -1
  138. package/src/components/input/types.ts +19 -2
  139. package/src/components/search-bar/SearchBar.tsx +50 -0
  140. package/src/components/search-bar/index.ts +2 -0
  141. package/src/components/search-bar/types.ts +14 -0
  142. package/src/index.ts +22 -1
  143. package/src/patterns/filter-bar/FilterBar.tsx +25 -0
  144. package/src/patterns/filter-bar/index.ts +2 -0
  145. package/src/patterns/filter-bar/types.ts +10 -0
  146. package/src/patterns/list/List.tsx +72 -0
  147. package/src/patterns/list/ListRow.tsx +193 -0
  148. package/src/patterns/list/ListSection.tsx +36 -0
  149. package/src/patterns/list/index.ts +11 -0
  150. package/src/patterns/list/resolveListSeparator.test.ts +18 -0
  151. package/src/patterns/list/resolveListSeparator.ts +8 -0
  152. package/src/patterns/list/types.ts +67 -0
  153. package/src/showcaseCoverage.test.ts +9 -0
  154. package/src/theme/themeScopeStructure.test.ts +4 -0
@@ -3,7 +3,23 @@ import type * as Surface from '@ankhorage/surface';
3
3
  import type { ZoraControlSize } from '../../internal/recipes';
4
4
  import type { ZoraBaseProps } from '../../theme/ZoraBaseProps';
5
5
 
6
- export interface InputProps
6
+ export interface InputTrailingAction {
7
+ icon: Surface.ButtonIconSpec;
8
+ label: string;
9
+ onPress: () => void;
10
+ }
11
+
12
+ type InputTrailingProps =
13
+ | {
14
+ trailingIcon?: Surface.ButtonIconSpec;
15
+ trailingAction?: never;
16
+ }
17
+ | {
18
+ trailingIcon?: never;
19
+ trailingAction?: InputTrailingAction;
20
+ };
21
+
22
+ export interface InputBaseProps
7
23
  extends
8
24
  ZoraBaseProps,
9
25
  Omit<
@@ -12,5 +28,6 @@ export interface InputProps
12
28
  > {
13
29
  size?: ZoraControlSize;
14
30
  leadingIcon?: Surface.ButtonIconSpec;
15
- trailingIcon?: Surface.ButtonIconSpec;
16
31
  }
32
+
33
+ export type InputProps = InputBaseProps & InputTrailingProps;
@@ -0,0 +1,50 @@
1
+ import React from 'react';
2
+
3
+ import { withZoraThemeScope } from '../../theme/withZoraThemeScope';
4
+ import { Input, type InputTrailingAction } from '../input';
5
+ import type { SearchBarProps } from './types';
6
+
7
+ function SearchBarInner({
8
+ themeId: _themeId,
9
+ mode: _mode,
10
+ testID,
11
+ value,
12
+ onValueChange,
13
+ placeholder = 'Search',
14
+ onSubmit,
15
+ onClear,
16
+ clearable = true,
17
+ size = 'l',
18
+ disabled,
19
+ readOnly,
20
+ }: SearchBarProps) {
21
+ const trailingAction: InputTrailingAction | undefined =
22
+ clearable && value.length > 0
23
+ ? {
24
+ icon: { name: 'close-circle' },
25
+ label: 'Clear search',
26
+ onPress: () => {
27
+ onValueChange('');
28
+ onClear?.();
29
+ },
30
+ }
31
+ : undefined;
32
+
33
+ return (
34
+ <Input
35
+ disabled={disabled}
36
+ leadingIcon={{ name: 'search-outline' }}
37
+ onChangeText={onValueChange}
38
+ onSubmitEditing={onSubmit ? () => onSubmit(value) : undefined}
39
+ placeholder={placeholder}
40
+ readOnly={readOnly}
41
+ returnKeyType="search"
42
+ size={size}
43
+ testID={testID}
44
+ trailingAction={trailingAction}
45
+ value={value}
46
+ />
47
+ );
48
+ }
49
+
50
+ export const SearchBar = withZoraThemeScope(SearchBarInner);
@@ -0,0 +1,2 @@
1
+ export { SearchBar } from './SearchBar';
2
+ export type { SearchBarProps } from './types';
@@ -0,0 +1,14 @@
1
+ import type { ZoraControlSize } from '../../internal/recipes';
2
+ import type { ZoraBaseProps } from '../../theme/ZoraBaseProps';
3
+
4
+ export interface SearchBarProps extends ZoraBaseProps {
5
+ value: string;
6
+ onValueChange: (value: string) => void;
7
+ placeholder?: string;
8
+ onSubmit?: (value: string) => void;
9
+ onClear?: () => void;
10
+ clearable?: boolean;
11
+ size?: ZoraControlSize;
12
+ disabled?: boolean;
13
+ readOnly?: boolean;
14
+ }
package/src/index.ts CHANGED
@@ -1,3 +1,7 @@
1
+ export type { AvatarProps, AvatarShape, AvatarSize } from './components/avatar';
2
+ export { Avatar, resolveAvatarInitials } from './components/avatar';
3
+ export type { AvatarGroupItem, AvatarGroupProps } from './components/avatar-group';
4
+ export { AvatarGroup } from './components/avatar-group';
1
5
  export type { BadgeProps } from './components/badge';
2
6
  export { Badge } from './components/badge';
3
7
  export type { ButtonProps } from './components/button';
@@ -6,6 +10,10 @@ export type { CardProps } from './components/card';
6
10
  export { Card } from './components/card';
7
11
  export type { CheckboxGroupOption, CheckboxGroupProps, CheckboxProps } from './components/checkbox';
8
12
  export { Checkbox, CheckboxGroup } from './components/checkbox';
13
+ export type { ChipProps } from './components/chip';
14
+ export { Chip } from './components/chip';
15
+ export type { ChipGroupItem, ChipGroupProps } from './components/chip-group';
16
+ export { ChipGroup } from './components/chip-group';
9
17
  export type { DrawerProps } from './components/drawer';
10
18
  export { Drawer } from './components/drawer';
11
19
  export type {
@@ -50,12 +58,14 @@ export type { IconProps } from './components/icon';
50
58
  export { Icon } from './components/icon';
51
59
  export type { IconButtonProps } from './components/icon-button';
52
60
  export { IconButton } from './components/icon-button';
53
- export type { InputProps } from './components/input';
61
+ export type { InputProps, InputTrailingAction } from './components/input';
54
62
  export { Input } from './components/input';
55
63
  export type { ModalProps } from './components/modal';
56
64
  export { Modal } from './components/modal';
57
65
  export type { RadioGroupOption, RadioGroupProps, RadioProps } from './components/radio';
58
66
  export { Radio, RadioGroup } from './components/radio';
67
+ export type { SearchBarProps } from './components/search-bar';
68
+ export { SearchBar } from './components/search-bar';
59
69
  export type { SelectOption, SelectProps } from './components/select';
60
70
  export { Select } from './components/select';
61
71
  export type { TabItem, TabsProps } from './components/tabs';
@@ -132,8 +142,19 @@ export type { DisclosureSectionProps } from './patterns/disclosure-section';
132
142
  export { DisclosureSection } from './patterns/disclosure-section';
133
143
  export type { EmptyStateAction, EmptyStateProps } from './patterns/empty-state';
134
144
  export { EmptyState } from './patterns/empty-state';
145
+ export type { FilterBarProps } from './patterns/filter-bar';
146
+ export { FilterBar } from './patterns/filter-bar';
135
147
  export type { InspectorFieldProps } from './patterns/inspector-field';
136
148
  export { InspectorField } from './patterns/inspector-field';
149
+ export type {
150
+ ListChildrenProps,
151
+ ListItemsProps,
152
+ ListProps,
153
+ ListRowProps,
154
+ ListRowVariant,
155
+ ListSectionProps,
156
+ } from './patterns/list';
157
+ export { List, ListRow, ListSection } from './patterns/list';
137
158
  export type { NoticeProps } from './patterns/notice';
138
159
  export { Notice } from './patterns/notice';
139
160
  export type { PanelProps } from './patterns/panel';
@@ -0,0 +1,25 @@
1
+ import React from 'react';
2
+
3
+ import { Box, Inline } from '../../foundation';
4
+ import { withZoraThemeScope } from '../../theme/withZoraThemeScope';
5
+ import type { FilterBarProps } from './types';
6
+
7
+ function FilterBarInner({
8
+ themeId: _themeId,
9
+ mode: _mode,
10
+ testID,
11
+ leading,
12
+ trailing,
13
+ children,
14
+ wrap = true,
15
+ }: FilterBarProps) {
16
+ return (
17
+ <Inline align="center" gap="s" testID={testID} wrap={wrap ? 'wrap' : 'nowrap'}>
18
+ {leading ? <Box>{leading}</Box> : null}
19
+ <Box flex={1}>{children}</Box>
20
+ {trailing ? <Box>{trailing}</Box> : null}
21
+ </Inline>
22
+ );
23
+ }
24
+
25
+ export const FilterBar = withZoraThemeScope(FilterBarInner);
@@ -0,0 +1,2 @@
1
+ export { FilterBar } from './FilterBar';
2
+ export type { FilterBarProps } from './types';
@@ -0,0 +1,10 @@
1
+ import type React from 'react';
2
+
3
+ import type { ZoraBaseProps } from '../../theme/ZoraBaseProps';
4
+
5
+ export interface FilterBarProps extends ZoraBaseProps {
6
+ leading?: React.ReactNode;
7
+ trailing?: React.ReactNode;
8
+ children: React.ReactNode;
9
+ wrap?: boolean;
10
+ }
@@ -0,0 +1,72 @@
1
+ import React from 'react';
2
+
3
+ import { Divider, Spacer, Stack } from '../../foundation';
4
+ import { withZoraThemeScope } from '../../theme/withZoraThemeScope';
5
+ import { ListRow } from './ListRow';
6
+ import { resolveListSeparator } from './resolveListSeparator';
7
+ import type { ListItemsProps, ListProps, ListRowProps, ListRowVariant } from './types';
8
+
9
+ function resolveRowVariant({
10
+ item,
11
+ defaultVariant,
12
+ }: {
13
+ item: ListRowProps;
14
+ defaultVariant: ListRowVariant;
15
+ }): ListRowVariant {
16
+ return item.variant ?? defaultVariant;
17
+ }
18
+
19
+ function resolveRowCompact({
20
+ item,
21
+ compact,
22
+ }: {
23
+ item: ListRowProps;
24
+ compact: boolean | undefined;
25
+ }): boolean {
26
+ return item.compact ?? compact ?? false;
27
+ }
28
+
29
+ function ListItemsInner({
30
+ themeId: _themeId,
31
+ mode: _mode,
32
+ testID,
33
+ items,
34
+ rowVariant = 'divider',
35
+ compact,
36
+ }: ListItemsProps) {
37
+ return (
38
+ <Stack gap="none" testID={testID}>
39
+ {items.map((item, index) => {
40
+ const effectiveVariant = resolveRowVariant({ item, defaultVariant: rowVariant });
41
+ const separator = resolveListSeparator(effectiveVariant, index);
42
+
43
+ return (
44
+ <React.Fragment key={`${index}`}>
45
+ {separator === 'divider' ? <Divider /> : null}
46
+ {separator === 'spacer' ? <Spacer size="s" /> : null}
47
+ <ListRow
48
+ {...item}
49
+ compact={resolveRowCompact({ item, compact })}
50
+ variant={effectiveVariant}
51
+ />
52
+ </React.Fragment>
53
+ );
54
+ })}
55
+ </Stack>
56
+ );
57
+ }
58
+
59
+ function ListInner(props: ListProps) {
60
+ if ('items' in props) {
61
+ return <ListItemsInner {...props} />;
62
+ }
63
+
64
+ const { themeId: _themeId, mode: _mode, children, testID } = props;
65
+ return (
66
+ <Stack gap="none" testID={testID}>
67
+ {children}
68
+ </Stack>
69
+ );
70
+ }
71
+
72
+ export const List = withZoraThemeScope(ListInner);
@@ -0,0 +1,193 @@
1
+ import { ButtonBase } from '@ankhorage/surface';
2
+ import React from 'react';
3
+
4
+ import { Text } from '../../components/text';
5
+ import { Box, Inline, Spacer, Stack } from '../../foundation';
6
+ import { useZoraTheme } from '../../theme/useZoraTheme';
7
+ import { withZoraThemeScope } from '../../theme/withZoraThemeScope';
8
+ import type { ListRowProps, ListRowVariant } from './types';
9
+
10
+ function resolvePadding(compact: boolean) {
11
+ return compact ? { px: 'm' as const, py: 's' as const } : { px: 'm' as const, py: 'm' as const };
12
+ }
13
+
14
+ function resolveContainerStyles({
15
+ variant,
16
+ theme,
17
+ selected,
18
+ pressed,
19
+ hovered,
20
+ disabled,
21
+ }: {
22
+ variant: ListRowVariant;
23
+ theme: ReturnType<typeof useZoraTheme>['theme'];
24
+ selected: boolean;
25
+ pressed: boolean;
26
+ hovered: boolean;
27
+ disabled: boolean;
28
+ }) {
29
+ const borderColor = selected ? theme.semantics.border.focus : theme.semantics.border.default;
30
+ const dividerBorderColor = selected ? theme.semantics.border.focus : 'transparent';
31
+
32
+ if (variant === 'card') {
33
+ return {
34
+ bg: pressed
35
+ ? theme.semantics.neutral.surfaceActive
36
+ : hovered
37
+ ? theme.semantics.neutral.surfaceHover
38
+ : theme.semantics.surface.raised,
39
+ borderColor,
40
+ borderWidth: 1,
41
+ radius: 'l' as const,
42
+ opacity: disabled ? 0.72 : 1,
43
+ };
44
+ }
45
+
46
+ return {
47
+ bg: pressed
48
+ ? theme.semantics.neutral.surfaceActive
49
+ : hovered
50
+ ? theme.semantics.neutral.surfaceHover
51
+ : selected
52
+ ? theme.semantics.neutral.surface
53
+ : 'transparent',
54
+ borderColor: dividerBorderColor,
55
+ borderWidth: selected ? 1 : 0,
56
+ radius: 'm' as const,
57
+ opacity: disabled ? 0.72 : 1,
58
+ };
59
+ }
60
+
61
+ function ListRowInner({
62
+ themeId: _themeId,
63
+ mode: _mode,
64
+ testID,
65
+ title,
66
+ description,
67
+ meta,
68
+ leading,
69
+ trailing,
70
+ action,
71
+ onPress,
72
+ selected = false,
73
+ disabled = false,
74
+ compact = false,
75
+ variant = 'divider',
76
+ }: ListRowProps) {
77
+ const { theme } = useZoraTheme();
78
+ const padding = resolvePadding(compact);
79
+ const isInteractive = Boolean(onPress) && !action;
80
+
81
+ const content = (
82
+ <Stack
83
+ align="center"
84
+ direction="row"
85
+ style={{
86
+ flex: 1,
87
+ }}
88
+ >
89
+ {leading ? (
90
+ <>
91
+ <Box>{leading}</Box>
92
+ <Spacer axis="horizontal" size="m" />
93
+ </>
94
+ ) : null}
95
+
96
+ <Box flex={1}>
97
+ <Stack gap="xxs">
98
+ <Text variant="body" weight={selected ? 'semiBold' : 'medium'}>
99
+ {title}
100
+ </Text>
101
+ {description ? (
102
+ <Text tone="muted" variant="bodySmall">
103
+ {description}
104
+ </Text>
105
+ ) : null}
106
+ {meta ? (
107
+ <Text tone="subtle" variant="caption">
108
+ {meta}
109
+ </Text>
110
+ ) : null}
111
+ </Stack>
112
+ </Box>
113
+
114
+ {trailing || action ? (
115
+ <>
116
+ <Spacer axis="horizontal" size="m" />
117
+ <Inline align="center" gap="s" wrap="nowrap">
118
+ {trailing}
119
+ {action}
120
+ </Inline>
121
+ </>
122
+ ) : null}
123
+ </Stack>
124
+ );
125
+
126
+ if (!isInteractive) {
127
+ const styles = resolveContainerStyles({
128
+ variant,
129
+ theme,
130
+ selected,
131
+ pressed: false,
132
+ hovered: false,
133
+ disabled,
134
+ });
135
+
136
+ return (
137
+ <Box
138
+ bg={styles.bg}
139
+ borderColor={styles.borderColor}
140
+ borderWidth={styles.borderWidth}
141
+ px={padding.px}
142
+ py={padding.py}
143
+ radius={styles.radius}
144
+ testID={testID}
145
+ style={{
146
+ opacity: styles.opacity,
147
+ }}
148
+ >
149
+ {content}
150
+ </Box>
151
+ );
152
+ }
153
+
154
+ return (
155
+ <ButtonBase
156
+ accessibilityRole="button"
157
+ accessibilityState={{ disabled, selected }}
158
+ disabled={disabled}
159
+ onPress={onPress}
160
+ radius={variant === 'card' ? 'l' : 'm'}
161
+ testID={testID}
162
+ >
163
+ {(state) => {
164
+ const styles = resolveContainerStyles({
165
+ variant,
166
+ theme,
167
+ selected,
168
+ pressed: state.pressed,
169
+ hovered: state.hovered,
170
+ disabled: state.disabled,
171
+ });
172
+
173
+ return (
174
+ <Box
175
+ bg={styles.bg}
176
+ borderColor={styles.borderColor}
177
+ borderWidth={styles.borderWidth}
178
+ px={padding.px}
179
+ py={padding.py}
180
+ radius={styles.radius}
181
+ style={{
182
+ opacity: styles.opacity,
183
+ }}
184
+ >
185
+ {content}
186
+ </Box>
187
+ );
188
+ }}
189
+ </ButtonBase>
190
+ );
191
+ }
192
+
193
+ export const ListRow = withZoraThemeScope(ListRowInner);
@@ -0,0 +1,36 @@
1
+ import React from 'react';
2
+
3
+ import { Stack } from '../../foundation';
4
+ import { withZoraThemeScope } from '../../theme/withZoraThemeScope';
5
+ import { SectionHeader } from '../section-header';
6
+ import { List } from './List';
7
+ import type { ListSectionProps } from './types';
8
+
9
+ function ListSectionInner({
10
+ themeId: _themeId,
11
+ mode: _mode,
12
+ testID,
13
+ title,
14
+ description,
15
+ eyebrow,
16
+ actions,
17
+ ...props
18
+ }: ListSectionProps) {
19
+ const hasHeader = title !== undefined;
20
+
21
+ return (
22
+ <Stack gap="s" testID={testID}>
23
+ {hasHeader ? (
24
+ <SectionHeader
25
+ actions={actions}
26
+ description={description}
27
+ eyebrow={eyebrow}
28
+ title={title}
29
+ />
30
+ ) : null}
31
+ <List {...props} />
32
+ </Stack>
33
+ );
34
+ }
35
+
36
+ export const ListSection = withZoraThemeScope(ListSectionInner);
@@ -0,0 +1,11 @@
1
+ export { List } from './List';
2
+ export { ListRow } from './ListRow';
3
+ export { ListSection } from './ListSection';
4
+ export type {
5
+ ListChildrenProps,
6
+ ListItemsProps,
7
+ ListProps,
8
+ ListRowProps,
9
+ ListRowVariant,
10
+ ListSectionProps,
11
+ } from './types';
@@ -0,0 +1,18 @@
1
+ import { describe, expect, it } from 'bun:test';
2
+
3
+ import { resolveListSeparator } from './resolveListSeparator';
4
+
5
+ describe('resolveListSeparator', () => {
6
+ it('returns none for the first item', () => {
7
+ expect(resolveListSeparator('divider', 0)).toBe('none');
8
+ expect(resolveListSeparator('card', 0)).toBe('none');
9
+ });
10
+
11
+ it('returns divider for divider rows', () => {
12
+ expect(resolveListSeparator('divider', 1)).toBe('divider');
13
+ });
14
+
15
+ it('returns spacer for card rows', () => {
16
+ expect(resolveListSeparator('card', 1)).toBe('spacer');
17
+ });
18
+ });
@@ -0,0 +1,8 @@
1
+ import type { ListRowVariant } from './types';
2
+
3
+ type ListSeparatorKind = 'none' | 'divider' | 'spacer';
4
+
5
+ export function resolveListSeparator(variant: ListRowVariant, index: number): ListSeparatorKind {
6
+ if (index === 0) return 'none';
7
+ return variant === 'divider' ? 'divider' : 'spacer';
8
+ }
@@ -0,0 +1,67 @@
1
+ import type React from 'react';
2
+
3
+ import type { ZoraBaseProps } from '../../theme/ZoraBaseProps';
4
+
5
+ export type ListRowVariant = 'divider' | 'card';
6
+
7
+ interface ListRowBaseProps extends ZoraBaseProps {
8
+ title: React.ReactNode;
9
+ description?: React.ReactNode;
10
+ meta?: React.ReactNode;
11
+ leading?: React.ReactNode;
12
+ trailing?: React.ReactNode;
13
+ selected?: boolean;
14
+ disabled?: boolean;
15
+ compact?: boolean;
16
+ variant?: ListRowVariant;
17
+ }
18
+
19
+ interface ListRowPressableProps {
20
+ onPress: () => void;
21
+ action?: never;
22
+ }
23
+
24
+ interface ListRowActionProps {
25
+ action: React.ReactNode;
26
+ onPress?: never;
27
+ }
28
+
29
+ interface ListRowStaticProps {
30
+ action?: never;
31
+ onPress?: never;
32
+ }
33
+
34
+ export type ListRowProps = ListRowBaseProps &
35
+ (ListRowPressableProps | ListRowActionProps | ListRowStaticProps);
36
+
37
+ export interface ListItemsProps extends ZoraBaseProps {
38
+ items: readonly ListRowProps[];
39
+ rowVariant?: ListRowVariant;
40
+ compact?: boolean;
41
+ }
42
+
43
+ export interface ListChildrenProps extends ZoraBaseProps {
44
+ children: React.ReactNode;
45
+ }
46
+
47
+ export type ListProps = ListItemsProps | ListChildrenProps;
48
+
49
+ export interface ListSectionItemsProps extends ZoraBaseProps {
50
+ title?: React.ReactNode;
51
+ description?: React.ReactNode;
52
+ eyebrow?: React.ReactNode;
53
+ actions?: React.ReactNode;
54
+ items: readonly ListRowProps[];
55
+ rowVariant?: ListRowVariant;
56
+ compact?: boolean;
57
+ }
58
+
59
+ export interface ListSectionChildrenProps extends ZoraBaseProps {
60
+ title?: React.ReactNode;
61
+ description?: React.ReactNode;
62
+ eyebrow?: React.ReactNode;
63
+ actions?: React.ReactNode;
64
+ children: React.ReactNode;
65
+ }
66
+
67
+ export type ListSectionProps = ListSectionItemsProps | ListSectionChildrenProps;
@@ -17,11 +17,15 @@ const IGNORED_DIRECTORY_NAMES = new Set([
17
17
 
18
18
  const REQUIRED_SHOWCASE_COVERAGE = {
19
19
  components: [
20
+ 'Avatar',
21
+ 'AvatarGroup',
20
22
  'Badge',
21
23
  'Button',
22
24
  'Card',
23
25
  'Checkbox',
24
26
  'CheckboxGroup',
27
+ 'Chip',
28
+ 'ChipGroup',
25
29
  'Drawer',
26
30
  'Form',
27
31
  'FormActions',
@@ -34,6 +38,7 @@ const REQUIRED_SHOWCASE_COVERAGE = {
34
38
  'Modal',
35
39
  'Radio',
36
40
  'RadioGroup',
41
+ 'SearchBar',
37
42
  'Select',
38
43
  'Tabs',
39
44
  'Text',
@@ -72,6 +77,10 @@ const REQUIRED_SHOWCASE_COVERAGE = {
72
77
  'ConfirmDialog',
73
78
  'DisclosureSection',
74
79
  'EmptyState',
80
+ 'FilterBar',
81
+ 'List',
82
+ 'ListRow',
83
+ 'ListSection',
75
84
  'InspectorField',
76
85
  'Notice',
77
86
  'Panel',
@@ -96,6 +96,9 @@ const scopedComponentFiles = [
96
96
  join(srcDir, 'patterns', 'empty-state', 'EmptyState.tsx'),
97
97
  join(srcDir, 'patterns', 'form-field', 'FormField.tsx'),
98
98
  join(srcDir, 'patterns', 'inspector-field', 'InspectorField.tsx'),
99
+ join(srcDir, 'patterns', 'list', 'List.tsx'),
100
+ join(srcDir, 'patterns', 'list', 'ListRow.tsx'),
101
+ join(srcDir, 'patterns', 'list', 'ListSection.tsx'),
99
102
  join(srcDir, 'patterns', 'notice', 'Notice.tsx'),
100
103
  join(srcDir, 'patterns', 'panel', 'Panel.tsx'),
101
104
  join(srcDir, 'patterns', 'responsive-panel', 'ResponsivePanel.tsx'),
@@ -154,6 +157,7 @@ const scopedPropTypeFiles = [
154
157
  join(srcDir, 'patterns', 'empty-state', 'types.ts'),
155
158
  join(srcDir, 'patterns', 'form-field', 'types.ts'),
156
159
  join(srcDir, 'patterns', 'inspector-field', 'types.ts'),
160
+ join(srcDir, 'patterns', 'list', 'types.ts'),
157
161
  join(srcDir, 'patterns', 'notice', 'types.ts'),
158
162
  join(srcDir, 'patterns', 'panel', 'types.ts'),
159
163
  join(srcDir, 'patterns', 'responsive-panel', 'types.ts'),