@ankhorage/zora 0.3.6 → 0.3.8

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 (229) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +0 -90
  3. package/dist/components/badge/Badge.js.map +1 -1
  4. package/dist/components/badge/index.js.map +1 -1
  5. package/dist/components/badge/types.js.map +1 -1
  6. package/dist/components/button/Button.js.map +1 -1
  7. package/dist/components/button/index.js.map +1 -1
  8. package/dist/components/button/types.js.map +1 -1
  9. package/dist/components/card/Card.js.map +1 -1
  10. package/dist/components/card/index.js.map +1 -1
  11. package/dist/components/card/types.js.map +1 -1
  12. package/dist/components/drawer/Drawer.js.map +1 -1
  13. package/dist/components/drawer/index.js.map +1 -1
  14. package/dist/components/drawer/types.js.map +1 -1
  15. package/dist/components/icon/Icon.js.map +1 -1
  16. package/dist/components/icon/index.js.map +1 -1
  17. package/dist/components/icon-button/IconButton.js.map +1 -1
  18. package/dist/components/icon-button/index.js.map +1 -1
  19. package/dist/components/icon-button/types.js.map +1 -1
  20. package/dist/components/input/Input.js.map +1 -1
  21. package/dist/components/input/index.js.map +1 -1
  22. package/dist/components/input/types.js.map +1 -1
  23. package/dist/components/modal/Modal.js.map +1 -1
  24. package/dist/components/modal/index.js.map +1 -1
  25. package/dist/components/modal/types.js.map +1 -1
  26. package/dist/components/select/Select.js.map +1 -1
  27. package/dist/components/select/index.js.map +1 -1
  28. package/dist/components/select/types.js.map +1 -1
  29. package/dist/components/tabs/Tabs.js.map +1 -1
  30. package/dist/components/tabs/index.js.map +1 -1
  31. package/dist/components/tabs/types.js.map +1 -1
  32. package/dist/components/textarea/Textarea.js.map +1 -1
  33. package/dist/components/textarea/index.js.map +1 -1
  34. package/dist/components/textarea/types.js.map +1 -1
  35. package/dist/components/toolbar/Toolbar.js.map +1 -1
  36. package/dist/components/toolbar/ToolbarAction.js.map +1 -1
  37. package/dist/components/toolbar/index.js.map +1 -1
  38. package/dist/components/toolbar/types.js.map +1 -1
  39. package/dist/index.js.map +1 -1
  40. package/dist/internal/deepMerge.js.map +1 -1
  41. package/dist/internal/recipes.js.map +1 -1
  42. package/dist/layout/app-shell/AppShell.js.map +1 -1
  43. package/dist/layout/app-shell/index.js.map +1 -1
  44. package/dist/layout/app-shell/types.js.map +1 -1
  45. package/dist/layout/auth-layout/AuthLayout.js.map +1 -1
  46. package/dist/layout/auth-layout/index.js.map +1 -1
  47. package/dist/layout/auth-layout/types.js.map +1 -1
  48. package/dist/layout/page/Page.js.map +1 -1
  49. package/dist/layout/page/index.js.map +1 -1
  50. package/dist/layout/page/types.js.map +1 -1
  51. package/dist/layout/page-header/PageHeader.js.map +1 -1
  52. package/dist/layout/page-header/index.js.map +1 -1
  53. package/dist/layout/page-header/types.js.map +1 -1
  54. package/dist/layout/page-section/PageSection.js.map +1 -1
  55. package/dist/layout/page-section/index.js.map +1 -1
  56. package/dist/layout/page-section/types.js.map +1 -1
  57. package/dist/layout/settings-layout/SettingsLayout.js.map +1 -1
  58. package/dist/layout/settings-layout/index.js.map +1 -1
  59. package/dist/layout/settings-layout/types.js.map +1 -1
  60. package/dist/layout/sidebar-layout/SidebarLayout.js.map +1 -1
  61. package/dist/layout/sidebar-layout/index.js.map +1 -1
  62. package/dist/layout/sidebar-layout/types.js.map +1 -1
  63. package/dist/layout/topbar-layout/TopbarLayout.js.map +1 -1
  64. package/dist/layout/topbar-layout/index.js.map +1 -1
  65. package/dist/layout/topbar-layout/types.js.map +1 -1
  66. package/dist/patterns/collection-editor/CollectionEditor.js.map +1 -1
  67. package/dist/patterns/collection-editor/index.js.map +1 -1
  68. package/dist/patterns/collection-editor/types.js.map +1 -1
  69. package/dist/patterns/confirm-dialog/ConfirmDialog.js.map +1 -1
  70. package/dist/patterns/confirm-dialog/index.js.map +1 -1
  71. package/dist/patterns/confirm-dialog/types.js.map +1 -1
  72. package/dist/patterns/disclosure-section/DisclosureSection.js.map +1 -1
  73. package/dist/patterns/disclosure-section/index.js.map +1 -1
  74. package/dist/patterns/disclosure-section/types.js.map +1 -1
  75. package/dist/patterns/empty-state/EmptyState.js.map +1 -1
  76. package/dist/patterns/empty-state/index.js.map +1 -1
  77. package/dist/patterns/empty-state/types.js.map +1 -1
  78. package/dist/patterns/form-field/FormField.js.map +1 -1
  79. package/dist/patterns/form-field/index.js.map +1 -1
  80. package/dist/patterns/form-field/types.js.map +1 -1
  81. package/dist/patterns/inspector-field/InspectorField.js.map +1 -1
  82. package/dist/patterns/inspector-field/index.js.map +1 -1
  83. package/dist/patterns/inspector-field/types.js.map +1 -1
  84. package/dist/patterns/notice/Notice.js.map +1 -1
  85. package/dist/patterns/notice/index.js.map +1 -1
  86. package/dist/patterns/notice/types.js.map +1 -1
  87. package/dist/patterns/panel/Panel.js.map +1 -1
  88. package/dist/patterns/panel/index.js.map +1 -1
  89. package/dist/patterns/panel/types.js.map +1 -1
  90. package/dist/patterns/responsive-panel/ResponsivePanel.js.map +1 -1
  91. package/dist/patterns/responsive-panel/index.js.map +1 -1
  92. package/dist/patterns/responsive-panel/types.js.map +1 -1
  93. package/dist/patterns/section-header/SectionHeader.js.map +1 -1
  94. package/dist/patterns/section-header/index.js.map +1 -1
  95. package/dist/patterns/section-header/types.js.map +1 -1
  96. package/dist/patterns/settings-row/SettingsRow.js.map +1 -1
  97. package/dist/patterns/settings-row/index.js.map +1 -1
  98. package/dist/patterns/settings-row/types.js.map +1 -1
  99. package/dist/patterns/switch-field/SwitchField.js.map +1 -1
  100. package/dist/patterns/switch-field/index.js.map +1 -1
  101. package/dist/patterns/switch-field/types.js.map +1 -1
  102. package/dist/patterns/tile-grid/PaletteItem.js.map +1 -1
  103. package/dist/patterns/tile-grid/TileGrid.js.map +1 -1
  104. package/dist/patterns/tile-grid/index.js.map +1 -1
  105. package/dist/patterns/tile-grid/types.js.map +1 -1
  106. package/dist/patterns/tree-view/TreeItem.js.map +1 -1
  107. package/dist/patterns/tree-view/TreeView.js.map +1 -1
  108. package/dist/patterns/tree-view/index.js.map +1 -1
  109. package/dist/patterns/tree-view/types.js.map +1 -1
  110. package/dist/theme/ZoraProvider.js.map +1 -1
  111. package/dist/theme/createZoraTheme.js.map +1 -1
  112. package/dist/theme/index.js.map +1 -1
  113. package/dist/theme/useZoraTheme.js.map +1 -1
  114. package/dist/theme/zoraTheme.js.map +1 -1
  115. package/package.json +15 -11
  116. package/src/components/badge/Badge.tsx +19 -0
  117. package/src/components/badge/index.ts +2 -0
  118. package/src/components/badge/types.ts +14 -0
  119. package/src/components/button/Button.tsx +13 -0
  120. package/src/components/button/index.ts +2 -0
  121. package/src/components/button/types.ts +16 -0
  122. package/src/components/card/Card.tsx +65 -0
  123. package/src/components/card/index.ts +2 -0
  124. package/src/components/card/types.ts +15 -0
  125. package/src/components/drawer/Drawer.tsx +27 -0
  126. package/src/components/drawer/index.ts +2 -0
  127. package/src/components/drawer/types.ts +12 -0
  128. package/src/components/icon/Icon.tsx +8 -0
  129. package/src/components/icon/index.ts +2 -0
  130. package/src/components/icon-button/IconButton.tsx +27 -0
  131. package/src/components/icon-button/index.ts +2 -0
  132. package/src/components/icon-button/types.ts +15 -0
  133. package/src/components/input/Input.tsx +38 -0
  134. package/src/components/input/index.ts +2 -0
  135. package/src/components/input/types.ts +12 -0
  136. package/src/components/modal/Modal.tsx +37 -0
  137. package/src/components/modal/index.ts +2 -0
  138. package/src/components/modal/types.ts +15 -0
  139. package/src/components/select/Select.tsx +49 -0
  140. package/src/components/select/index.ts +2 -0
  141. package/src/components/select/types.ts +14 -0
  142. package/src/components/tabs/Tabs.tsx +103 -0
  143. package/src/components/tabs/index.ts +2 -0
  144. package/src/components/tabs/types.ts +25 -0
  145. package/src/components/textarea/Textarea.tsx +38 -0
  146. package/src/components/textarea/index.ts +2 -0
  147. package/src/components/textarea/types.ts +12 -0
  148. package/src/components/toolbar/Toolbar.tsx +38 -0
  149. package/src/components/toolbar/ToolbarAction.tsx +15 -0
  150. package/src/components/toolbar/index.ts +3 -0
  151. package/src/components/toolbar/types.ts +21 -0
  152. package/src/index.ts +72 -0
  153. package/src/internal/deepMerge.ts +23 -0
  154. package/src/internal/recipes.test.ts +46 -0
  155. package/src/internal/recipes.ts +92 -0
  156. package/src/layout/app-shell/AppShell.tsx +15 -0
  157. package/src/layout/app-shell/index.ts +2 -0
  158. package/src/layout/app-shell/types.ts +7 -0
  159. package/src/layout/auth-layout/AuthLayout.tsx +29 -0
  160. package/src/layout/auth-layout/index.ts +2 -0
  161. package/src/layout/auth-layout/types.ts +10 -0
  162. package/src/layout/page/Page.tsx +17 -0
  163. package/src/layout/page/index.ts +2 -0
  164. package/src/layout/page/types.ts +11 -0
  165. package/src/layout/page-header/PageHeader.tsx +41 -0
  166. package/src/layout/page-header/index.ts +2 -0
  167. package/src/layout/page-header/types.ts +10 -0
  168. package/src/layout/page-section/PageSection.tsx +14 -0
  169. package/src/layout/page-section/index.ts +2 -0
  170. package/src/layout/page-section/types.ts +9 -0
  171. package/src/layout/settings-layout/SettingsLayout.tsx +26 -0
  172. package/src/layout/settings-layout/index.ts +2 -0
  173. package/src/layout/settings-layout/types.ts +10 -0
  174. package/src/layout/sidebar-layout/SidebarLayout.tsx +23 -0
  175. package/src/layout/sidebar-layout/index.ts +2 -0
  176. package/src/layout/sidebar-layout/types.ts +10 -0
  177. package/src/layout/topbar-layout/TopbarLayout.tsx +14 -0
  178. package/src/layout/topbar-layout/index.ts +2 -0
  179. package/src/layout/topbar-layout/types.ts +8 -0
  180. package/src/patterns/collection-editor/CollectionEditor.tsx +100 -0
  181. package/src/patterns/collection-editor/index.ts +2 -0
  182. package/src/patterns/collection-editor/types.ts +25 -0
  183. package/src/patterns/confirm-dialog/ConfirmDialog.tsx +46 -0
  184. package/src/patterns/confirm-dialog/index.ts +2 -0
  185. package/src/patterns/confirm-dialog/types.ts +19 -0
  186. package/src/patterns/disclosure-section/DisclosureSection.tsx +61 -0
  187. package/src/patterns/disclosure-section/index.ts +2 -0
  188. package/src/patterns/disclosure-section/types.ts +15 -0
  189. package/src/patterns/empty-state/EmptyState.tsx +53 -0
  190. package/src/patterns/empty-state/index.ts +2 -0
  191. package/src/patterns/empty-state/types.ts +20 -0
  192. package/src/patterns/form-field/FormField.tsx +27 -0
  193. package/src/patterns/form-field/index.ts +2 -0
  194. package/src/patterns/form-field/types.ts +11 -0
  195. package/src/patterns/inspector-field/InspectorField.tsx +16 -0
  196. package/src/patterns/inspector-field/index.ts +2 -0
  197. package/src/patterns/inspector-field/types.ts +15 -0
  198. package/src/patterns/notice/Notice.tsx +30 -0
  199. package/src/patterns/notice/index.ts +2 -0
  200. package/src/patterns/notice/types.ts +12 -0
  201. package/src/patterns/panel/Panel.tsx +8 -0
  202. package/src/patterns/panel/index.ts +2 -0
  203. package/src/patterns/panel/types.ts +15 -0
  204. package/src/patterns/responsive-panel/ResponsivePanel.tsx +70 -0
  205. package/src/patterns/responsive-panel/index.ts +2 -0
  206. package/src/patterns/responsive-panel/types.ts +20 -0
  207. package/src/patterns/section-header/SectionHeader.tsx +39 -0
  208. package/src/patterns/section-header/index.ts +2 -0
  209. package/src/patterns/section-header/types.ts +9 -0
  210. package/src/patterns/settings-row/SettingsRow.tsx +40 -0
  211. package/src/patterns/settings-row/index.ts +2 -0
  212. package/src/patterns/settings-row/types.ts +27 -0
  213. package/src/patterns/switch-field/SwitchField.tsx +24 -0
  214. package/src/patterns/switch-field/index.ts +2 -0
  215. package/src/patterns/switch-field/types.ts +10 -0
  216. package/src/patterns/tile-grid/PaletteItem.tsx +49 -0
  217. package/src/patterns/tile-grid/TileGrid.tsx +44 -0
  218. package/src/patterns/tile-grid/index.ts +3 -0
  219. package/src/patterns/tile-grid/types.ts +20 -0
  220. package/src/patterns/tree-view/TreeItem.tsx +86 -0
  221. package/src/patterns/tree-view/TreeView.tsx +50 -0
  222. package/src/patterns/tree-view/index.ts +3 -0
  223. package/src/patterns/tree-view/types.ts +31 -0
  224. package/src/theme/ZoraProvider.tsx +22 -0
  225. package/src/theme/createZoraTheme.test.ts +25 -0
  226. package/src/theme/createZoraTheme.ts +10 -0
  227. package/src/theme/index.ts +6 -0
  228. package/src/theme/useZoraTheme.ts +5 -0
  229. package/src/theme/zoraTheme.ts +16 -0
@@ -0,0 +1,21 @@
1
+ import type { ButtonIconSpec } from '@ankhorage/surface';
2
+ import type React from 'react';
3
+
4
+ export type ToolbarPosition = 'top' | 'bottom' | 'inline';
5
+
6
+ export interface ToolbarProps {
7
+ children?: React.ReactNode;
8
+ position?: ToolbarPosition;
9
+ floating?: boolean;
10
+ compact?: boolean;
11
+ testID?: string;
12
+ }
13
+
14
+ export interface ToolbarActionProps {
15
+ label: string;
16
+ icon: ButtonIconSpec;
17
+ active?: boolean;
18
+ disabled?: boolean;
19
+ onPress?: () => void;
20
+ testID?: string;
21
+ }
package/src/index.ts ADDED
@@ -0,0 +1,72 @@
1
+ export type { BadgeProps } from './components/badge';
2
+ export { Badge } from './components/badge';
3
+ export type { ButtonProps } from './components/button';
4
+ export { Button } from './components/button';
5
+ export type { CardProps } from './components/card';
6
+ export { Card } from './components/card';
7
+ export type { DrawerProps } from './components/drawer';
8
+ export { Drawer } from './components/drawer';
9
+ export type { IconProps } from './components/icon';
10
+ export { Icon } from './components/icon';
11
+ export type { IconButtonProps } from './components/icon-button';
12
+ export { IconButton } from './components/icon-button';
13
+ export type { InputProps } from './components/input';
14
+ export { Input } from './components/input';
15
+ export type { ModalProps } from './components/modal';
16
+ export { Modal } from './components/modal';
17
+ export type { SelectOption, SelectProps } from './components/select';
18
+ export { Select } from './components/select';
19
+ export type { TabItem, TabsProps } from './components/tabs';
20
+ export { Tabs } from './components/tabs';
21
+ export type { TextareaProps } from './components/textarea';
22
+ export { Textarea } from './components/textarea';
23
+ export type { ToolbarActionProps, ToolbarProps } from './components/toolbar';
24
+ export { Toolbar, ToolbarAction } from './components/toolbar';
25
+ export type { AppShellProps } from './layout/app-shell';
26
+ export { AppShell } from './layout/app-shell';
27
+ export type { AuthLayoutProps } from './layout/auth-layout';
28
+ export { AuthLayout } from './layout/auth-layout';
29
+ export type { PageProps } from './layout/page';
30
+ export { Page } from './layout/page';
31
+ export type { PageHeaderProps } from './layout/page-header';
32
+ export { PageHeader } from './layout/page-header';
33
+ export type { PageSectionProps } from './layout/page-section';
34
+ export { PageSection } from './layout/page-section';
35
+ export type { SettingsLayoutProps } from './layout/settings-layout';
36
+ export { SettingsLayout } from './layout/settings-layout';
37
+ export type { SidebarLayoutProps } from './layout/sidebar-layout';
38
+ export { SidebarLayout } from './layout/sidebar-layout';
39
+ export type { TopbarLayoutProps } from './layout/topbar-layout';
40
+ export { TopbarLayout } from './layout/topbar-layout';
41
+ export type {
42
+ CollectionEditorProps,
43
+ CollectionEditorRenderItemProps,
44
+ } from './patterns/collection-editor';
45
+ export { CollectionEditor } from './patterns/collection-editor';
46
+ export type { ConfirmDialogProps } from './patterns/confirm-dialog';
47
+ export { ConfirmDialog } from './patterns/confirm-dialog';
48
+ export type { DisclosureSectionProps } from './patterns/disclosure-section';
49
+ export { DisclosureSection } from './patterns/disclosure-section';
50
+ export type { EmptyStateAction, EmptyStateProps } from './patterns/empty-state';
51
+ export { EmptyState } from './patterns/empty-state';
52
+ export type { FormFieldProps } from './patterns/form-field';
53
+ export { FormField } from './patterns/form-field';
54
+ export type { InspectorFieldProps } from './patterns/inspector-field';
55
+ export { InspectorField } from './patterns/inspector-field';
56
+ export type { NoticeProps } from './patterns/notice';
57
+ export { Notice } from './patterns/notice';
58
+ export type { PanelProps } from './patterns/panel';
59
+ export { Panel } from './patterns/panel';
60
+ export type { ResponsivePanelProps } from './patterns/responsive-panel';
61
+ export { ResponsivePanel } from './patterns/responsive-panel';
62
+ export type { SectionHeaderProps } from './patterns/section-header';
63
+ export { SectionHeader } from './patterns/section-header';
64
+ export type { SettingsRowProps } from './patterns/settings-row';
65
+ export { SettingsRow } from './patterns/settings-row';
66
+ export type { SwitchFieldProps } from './patterns/switch-field';
67
+ export { SwitchField } from './patterns/switch-field';
68
+ export type { PaletteItemProps, TileGridProps } from './patterns/tile-grid';
69
+ export { PaletteItem, TileGrid } from './patterns/tile-grid';
70
+ export type { TreeItemNode, TreeItemRenderProps, TreeViewProps } from './patterns/tree-view';
71
+ export { TreeItem, TreeView } from './patterns/tree-view';
72
+ export * from './theme';
@@ -0,0 +1,23 @@
1
+ export function deepMerge<T extends object>(target: T, source: Partial<T>): T {
2
+ const result = { ...target };
3
+
4
+ (Object.keys(source) as (keyof T)[]).forEach((key) => {
5
+ const sourceValue = source[key];
6
+ const targetValue = target[key];
7
+
8
+ if (
9
+ sourceValue &&
10
+ typeof sourceValue === 'object' &&
11
+ !Array.isArray(sourceValue) &&
12
+ targetValue &&
13
+ typeof targetValue === 'object' &&
14
+ !Array.isArray(targetValue)
15
+ ) {
16
+ result[key] = deepMerge(targetValue as object, sourceValue as object) as T[keyof T];
17
+ } else if (sourceValue !== undefined) {
18
+ result[key] = sourceValue as T[keyof T];
19
+ }
20
+ });
21
+
22
+ return result;
23
+ }
@@ -0,0 +1,46 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+
3
+ import {
4
+ resolveBadgeRecipe,
5
+ resolveButtonRecipe,
6
+ resolveCardVariant,
7
+ resolveDialogWidth,
8
+ resolveIconSize,
9
+ resolvePageMaxWidth,
10
+ } from './recipes';
11
+
12
+ describe('recipes', () => {
13
+ test('maps button defaults to a more opinionated large solid primary button', () => {
14
+ expect(resolveButtonRecipe({})).toEqual({
15
+ size: 'l',
16
+ tone: 'primary',
17
+ variant: 'solid',
18
+ });
19
+ });
20
+
21
+ test('maps badge defaults to a soft medium primary badge', () => {
22
+ expect(resolveBadgeRecipe({})).toEqual({
23
+ size: 'm',
24
+ tone: 'primary',
25
+ variant: 'soft',
26
+ });
27
+ });
28
+
29
+ test('resolves elevated cards by default', () => {
30
+ expect(resolveCardVariant()).toBe('raised');
31
+ expect(resolveCardVariant('outline')).toBe('outline');
32
+ expect(resolveCardVariant('subtle')).toBe('subtle');
33
+ });
34
+
35
+ test('keeps width presets stable for layouts and dialogs', () => {
36
+ expect(resolveDialogWidth('narrow')).toBeLessThan(resolveDialogWidth('default'));
37
+ expect(resolveDialogWidth('wide')).toBeGreaterThanOrEqual(resolveDialogWidth('default'));
38
+ expect(resolvePageMaxWidth('wide')).toBeGreaterThan(resolvePageMaxWidth('default'));
39
+ });
40
+
41
+ test('keeps icon sizes aligned with control sizes', () => {
42
+ expect(resolveIconSize('s')).toBe(16);
43
+ expect(resolveIconSize('m')).toBe(18);
44
+ expect(resolveIconSize('l')).toBe(20);
45
+ });
46
+ });
@@ -0,0 +1,92 @@
1
+ import type {
2
+ BadgeProps as SurfaceBadgeProps,
3
+ ButtonProps as SurfaceButtonProps,
4
+ CardProps as SurfaceCardProps,
5
+ } from '@ankhorage/surface';
6
+
7
+ export type ZoraTone = NonNullable<SurfaceButtonProps['tone']>;
8
+ export type ZoraEmphasis = NonNullable<SurfaceButtonProps['variant']>;
9
+ export type ZoraControlSize = NonNullable<SurfaceButtonProps['size']>;
10
+ export type ZoraBadgeEmphasis = NonNullable<SurfaceBadgeProps['variant']>;
11
+ export type ZoraCardTone = 'default' | 'subtle' | 'outline';
12
+ export type ZoraContentWidth = 'narrow' | 'default' | 'wide';
13
+
14
+ export function resolveButtonRecipe({
15
+ tone = 'primary',
16
+ emphasis = 'solid',
17
+ size = 'l',
18
+ }: {
19
+ tone?: ZoraTone;
20
+ emphasis?: ZoraEmphasis;
21
+ size?: ZoraControlSize;
22
+ }): Pick<SurfaceButtonProps, 'size' | 'tone' | 'variant'> {
23
+ return {
24
+ size,
25
+ tone,
26
+ variant: emphasis,
27
+ };
28
+ }
29
+
30
+ export function resolveBadgeRecipe({
31
+ tone = 'primary',
32
+ emphasis = 'soft',
33
+ size = 'm',
34
+ }: {
35
+ tone?: ZoraTone;
36
+ emphasis?: ZoraBadgeEmphasis;
37
+ size?: ZoraControlSize;
38
+ }): Pick<SurfaceBadgeProps, 'size' | 'tone' | 'variant'> {
39
+ return {
40
+ size,
41
+ tone,
42
+ variant: emphasis,
43
+ };
44
+ }
45
+
46
+ export function resolveCardVariant(tone: ZoraCardTone = 'default'): SurfaceCardProps['variant'] {
47
+ switch (tone) {
48
+ case 'outline':
49
+ return 'outline';
50
+ case 'subtle':
51
+ return 'subtle';
52
+ case 'default':
53
+ default:
54
+ return 'raised';
55
+ }
56
+ }
57
+
58
+ export function resolveDialogWidth(width: ZoraContentWidth = 'default'): number {
59
+ switch (width) {
60
+ case 'narrow':
61
+ return 420;
62
+ case 'wide':
63
+ return 560;
64
+ case 'default':
65
+ default:
66
+ return 520;
67
+ }
68
+ }
69
+
70
+ export function resolvePageMaxWidth(width: ZoraContentWidth = 'default'): number {
71
+ switch (width) {
72
+ case 'narrow':
73
+ return 760;
74
+ case 'wide':
75
+ return 1280;
76
+ case 'default':
77
+ default:
78
+ return 1040;
79
+ }
80
+ }
81
+
82
+ export function resolveIconSize(size: ZoraControlSize = 'l'): number {
83
+ switch (size) {
84
+ case 's':
85
+ return 16;
86
+ case 'm':
87
+ return 18;
88
+ case 'l':
89
+ default:
90
+ return 20;
91
+ }
92
+ }
@@ -0,0 +1,15 @@
1
+ import { Box, Stack } from '@ankhorage/surface';
2
+ import React from 'react';
3
+
4
+ import type { AppShellProps } from './types';
5
+
6
+ export function AppShell({ children, topbar, testID }: AppShellProps) {
7
+ return (
8
+ <Box bg="background" flex={1} testID={testID}>
9
+ <Stack gap="none">
10
+ {topbar ? <Box>{topbar}</Box> : null}
11
+ <Box flex={1}>{children}</Box>
12
+ </Stack>
13
+ </Box>
14
+ );
15
+ }
@@ -0,0 +1,2 @@
1
+ export { AppShell } from './AppShell';
2
+ export type { AppShellProps } from './types';
@@ -0,0 +1,7 @@
1
+ import type React from 'react';
2
+
3
+ export interface AppShellProps {
4
+ children?: React.ReactNode;
5
+ topbar?: React.ReactNode;
6
+ testID?: string;
7
+ }
@@ -0,0 +1,29 @@
1
+ import { Center, Stack } from '@ankhorage/surface';
2
+ import React from 'react';
3
+
4
+ import { Card } from '../../components/card';
5
+ import type { AuthLayoutProps } from './types';
6
+
7
+ export function AuthLayout({
8
+ title,
9
+ description,
10
+ eyebrow,
11
+ children,
12
+ footer,
13
+ testID,
14
+ }: AuthLayoutProps) {
15
+ return (
16
+ <Center py="xl" testID={testID}>
17
+ <Card
18
+ compact
19
+ description={description}
20
+ eyebrow={eyebrow}
21
+ footer={footer}
22
+ title={title}
23
+ tone="default"
24
+ >
25
+ <Stack gap="m">{children}</Stack>
26
+ </Card>
27
+ </Center>
28
+ );
29
+ }
@@ -0,0 +1,2 @@
1
+ export { AuthLayout } from './AuthLayout';
2
+ export type { AuthLayoutProps } from './types';
@@ -0,0 +1,10 @@
1
+ import type React from 'react';
2
+
3
+ export interface AuthLayoutProps {
4
+ title?: React.ReactNode;
5
+ description?: React.ReactNode;
6
+ eyebrow?: React.ReactNode;
7
+ children?: React.ReactNode;
8
+ footer?: React.ReactNode;
9
+ testID?: string;
10
+ }
@@ -0,0 +1,17 @@
1
+ import { Container, Stack } from '@ankhorage/surface';
2
+ import React from 'react';
3
+
4
+ import { resolvePageMaxWidth } from '../../internal/recipes';
5
+ import type { PageProps } from './types';
6
+
7
+ export function Page({ children, header, footer, width = 'default', testID }: PageProps) {
8
+ return (
9
+ <Container maxWidth={resolvePageMaxWidth(width)} py="xl" testID={testID}>
10
+ <Stack gap="l">
11
+ {header}
12
+ {children}
13
+ {footer}
14
+ </Stack>
15
+ </Container>
16
+ );
17
+ }
@@ -0,0 +1,2 @@
1
+ export { Page } from './Page';
2
+ export type { PageProps } from './types';
@@ -0,0 +1,11 @@
1
+ import type React from 'react';
2
+
3
+ import type { ZoraContentWidth } from '../../internal/recipes';
4
+
5
+ export interface PageProps {
6
+ children?: React.ReactNode;
7
+ header?: React.ReactNode;
8
+ footer?: React.ReactNode;
9
+ width?: ZoraContentWidth;
10
+ testID?: string;
11
+ }
@@ -0,0 +1,41 @@
1
+ import { Box, Heading, Stack, Text } from '@ankhorage/surface';
2
+ import React from 'react';
3
+
4
+ import type { PageHeaderProps } from './types';
5
+
6
+ export function PageHeader({
7
+ title,
8
+ description,
9
+ eyebrow,
10
+ actions,
11
+ meta,
12
+ testID,
13
+ }: PageHeaderProps) {
14
+ return (
15
+ <Stack
16
+ align={{ base: 'flex-start', md: 'center' }}
17
+ direction={{ base: 'column', md: 'row' }}
18
+ gap="l"
19
+ justify="space-between"
20
+ testID={testID}
21
+ >
22
+ <Box flex={1}>
23
+ <Stack gap="s">
24
+ {eyebrow ? (
25
+ <Text tone="muted" variant="caption" weight="semiBold">
26
+ {eyebrow}
27
+ </Text>
28
+ ) : null}
29
+ <Heading level={1}>{title}</Heading>
30
+ {description ? (
31
+ <Text tone="muted" variant="body">
32
+ {description}
33
+ </Text>
34
+ ) : null}
35
+ {meta ? <Box pt="xs">{meta}</Box> : null}
36
+ </Stack>
37
+ </Box>
38
+ {actions ? <Box>{actions}</Box> : null}
39
+ </Stack>
40
+ );
41
+ }
@@ -0,0 +1,2 @@
1
+ export { PageHeader } from './PageHeader';
2
+ export type { PageHeaderProps } from './types';
@@ -0,0 +1,10 @@
1
+ import type React from 'react';
2
+
3
+ export interface PageHeaderProps {
4
+ title: React.ReactNode;
5
+ description?: React.ReactNode;
6
+ eyebrow?: React.ReactNode;
7
+ actions?: React.ReactNode;
8
+ meta?: React.ReactNode;
9
+ testID?: string;
10
+ }
@@ -0,0 +1,14 @@
1
+ import { Stack } from '@ankhorage/surface';
2
+ import React from 'react';
3
+
4
+ import { SectionHeader } from '../../patterns/section-header';
5
+ import type { PageSectionProps } from './types';
6
+
7
+ export function PageSection({ title, description, actions, children, testID }: PageSectionProps) {
8
+ return (
9
+ <Stack gap="m" testID={testID}>
10
+ {title ? <SectionHeader actions={actions} description={description} title={title} /> : null}
11
+ {children}
12
+ </Stack>
13
+ );
14
+ }
@@ -0,0 +1,2 @@
1
+ export { PageSection } from './PageSection';
2
+ export type { PageSectionProps } from './types';
@@ -0,0 +1,9 @@
1
+ import type React from 'react';
2
+
3
+ export interface PageSectionProps {
4
+ title?: React.ReactNode;
5
+ description?: React.ReactNode;
6
+ actions?: React.ReactNode;
7
+ children?: React.ReactNode;
8
+ testID?: string;
9
+ }
@@ -0,0 +1,26 @@
1
+ import React from 'react';
2
+
3
+ import { Page } from '../page';
4
+ import { PageHeader } from '../page-header';
5
+ import { SidebarLayout } from '../sidebar-layout';
6
+ import type { SettingsLayoutProps } from './types';
7
+
8
+ export function SettingsLayout({
9
+ title,
10
+ description,
11
+ sidebar,
12
+ children,
13
+ actions,
14
+ testID,
15
+ }: SettingsLayoutProps) {
16
+ return (
17
+ <Page
18
+ header={
19
+ title ? <PageHeader actions={actions} description={description} title={title} /> : null
20
+ }
21
+ testID={testID}
22
+ >
23
+ <SidebarLayout sidebar={sidebar}>{children}</SidebarLayout>
24
+ </Page>
25
+ );
26
+ }
@@ -0,0 +1,2 @@
1
+ export { SettingsLayout } from './SettingsLayout';
2
+ export type { SettingsLayoutProps } from './types';
@@ -0,0 +1,10 @@
1
+ import type React from 'react';
2
+
3
+ export interface SettingsLayoutProps {
4
+ title?: React.ReactNode;
5
+ description?: React.ReactNode;
6
+ sidebar: React.ReactNode;
7
+ children?: React.ReactNode;
8
+ actions?: React.ReactNode;
9
+ testID?: string;
10
+ }
@@ -0,0 +1,23 @@
1
+ import { Box, Stack } from '@ankhorage/surface';
2
+ import React from 'react';
3
+
4
+ import type { SidebarLayoutProps } from './types';
5
+
6
+ export function SidebarLayout({
7
+ sidebar,
8
+ children,
9
+ aside,
10
+ sidebarWidth = 280,
11
+ asideWidth = 280,
12
+ testID,
13
+ }: SidebarLayoutProps) {
14
+ return (
15
+ <Stack direction={{ base: 'column', lg: 'row' }} gap="l" testID={testID} align="flex-start">
16
+ <Box width={{ base: '100%', lg: sidebarWidth }}>{sidebar}</Box>
17
+ <Box flex={1} width="100%">
18
+ {children}
19
+ </Box>
20
+ {aside ? <Box width={{ base: '100%', lg: asideWidth }}>{aside}</Box> : null}
21
+ </Stack>
22
+ );
23
+ }
@@ -0,0 +1,2 @@
1
+ export { SidebarLayout } from './SidebarLayout';
2
+ export type { SidebarLayoutProps } from './types';
@@ -0,0 +1,10 @@
1
+ import type React from 'react';
2
+
3
+ export interface SidebarLayoutProps {
4
+ sidebar: React.ReactNode;
5
+ children?: React.ReactNode;
6
+ aside?: React.ReactNode;
7
+ sidebarWidth?: number;
8
+ asideWidth?: number;
9
+ testID?: string;
10
+ }
@@ -0,0 +1,14 @@
1
+ import { Box, Stack } from '@ankhorage/surface';
2
+ import React from 'react';
3
+
4
+ import { SidebarLayout } from '../sidebar-layout';
5
+ import type { TopbarLayoutProps } from './types';
6
+
7
+ export function TopbarLayout({ topbar, children, sidebar, testID }: TopbarLayoutProps) {
8
+ return (
9
+ <Stack gap="l" testID={testID}>
10
+ <Box>{topbar}</Box>
11
+ {sidebar ? <SidebarLayout sidebar={sidebar}>{children}</SidebarLayout> : children}
12
+ </Stack>
13
+ );
14
+ }
@@ -0,0 +1,2 @@
1
+ export { TopbarLayout } from './TopbarLayout';
2
+ export type { TopbarLayoutProps } from './types';
@@ -0,0 +1,8 @@
1
+ import type React from 'react';
2
+
3
+ export interface TopbarLayoutProps {
4
+ topbar: React.ReactNode;
5
+ children?: React.ReactNode;
6
+ sidebar?: React.ReactNode;
7
+ testID?: string;
8
+ }
@@ -0,0 +1,100 @@
1
+ import { Box, Stack, Text } from '@ankhorage/surface';
2
+ import React from 'react';
3
+
4
+ import { Button } from '../../components/button';
5
+ import { IconButton } from '../../components/icon-button';
6
+ import { Panel } from '../panel';
7
+ import type { CollectionEditorProps } from './types';
8
+
9
+ export function CollectionEditor<TItem>({
10
+ title,
11
+ description,
12
+ items,
13
+ renderItem,
14
+ onAdd,
15
+ onRemove,
16
+ onMove,
17
+ addLabel = 'Add Item',
18
+ emptyLabel = 'No items yet.',
19
+ disabled,
20
+ testID,
21
+ }: CollectionEditorProps<TItem>) {
22
+ const isEmpty = items.length === 0;
23
+
24
+ return (
25
+ <Panel
26
+ compact
27
+ description={description}
28
+ testID={testID}
29
+ title={title}
30
+ actions={
31
+ onAdd ? (
32
+ <Button emphasis="soft" size="s" disabled={disabled} onPress={onAdd}>
33
+ {addLabel}
34
+ </Button>
35
+ ) : null
36
+ }
37
+ >
38
+ <Stack gap="s">
39
+ {isEmpty ? (
40
+ <Box py="m">
41
+ <Text align="center" tone="muted">
42
+ {emptyLabel}
43
+ </Text>
44
+ </Box>
45
+ ) : (
46
+ items.map((item, index) => (
47
+ <Box key={index} bg="subtle" p="s" radius="m" borderColor="border" borderWidth={1}>
48
+ <Stack direction="row" gap="m" align="center">
49
+ <Box flex={1}>
50
+ {renderItem({
51
+ item,
52
+ index,
53
+ remove: () => onRemove?.(index),
54
+ moveUp: () => onMove?.(index, index - 1),
55
+ moveDown: () => onMove?.(index, index + 1),
56
+ canMoveUp: index > 0,
57
+ canMoveDown: index < items.length - 1,
58
+ })}
59
+ </Box>
60
+ <Stack direction="row" gap="xs">
61
+ {onMove ? (
62
+ <>
63
+ <IconButton
64
+ icon={{ name: 'arrow-up-outline' }}
65
+ label="Move Up"
66
+ disabled={disabled ?? index === 0}
67
+ onPress={() => onMove(index, index - 1)}
68
+ size="s"
69
+ emphasis="ghost"
70
+ />
71
+ <IconButton
72
+ icon={{ name: 'arrow-down-outline' }}
73
+ label="Move Down"
74
+ disabled={disabled ?? index === items.length - 1}
75
+ onPress={() => onMove(index, index + 1)}
76
+ size="s"
77
+ emphasis="ghost"
78
+ />
79
+ </>
80
+ ) : null}
81
+ {onRemove ? (
82
+ <IconButton
83
+ icon={{ name: 'trash-outline' }}
84
+ label="Remove"
85
+ tone="danger"
86
+ disabled={disabled}
87
+ onPress={() => onRemove(index)}
88
+ size="s"
89
+ emphasis="ghost"
90
+ />
91
+ ) : null}
92
+ </Stack>
93
+ </Stack>
94
+ </Box>
95
+ ))
96
+ )}
97
+ </Stack>
98
+ </Panel>
99
+ );
100
+ }