@ankhorage/zora 1.0.9 → 1.1.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.
- package/CHANGELOG.md +12 -0
- package/README.md +148 -0
- package/dist/components/image/Image.d.ts +4 -0
- package/dist/components/image/Image.d.ts.map +1 -0
- package/dist/components/image/Image.js +8 -0
- package/dist/components/image/Image.js.map +1 -0
- package/dist/components/image/index.d.ts +3 -0
- package/dist/components/image/index.d.ts.map +1 -0
- package/dist/components/image/index.js +2 -0
- package/dist/components/image/index.js.map +1 -0
- package/dist/components/image/types.d.ts +6 -0
- package/dist/components/image/types.d.ts.map +1 -0
- package/dist/components/image/types.js +2 -0
- package/dist/components/image/types.js.map +1 -0
- package/dist/components/navigation-item/NavigationItem.d.ts +4 -0
- package/dist/components/navigation-item/NavigationItem.d.ts.map +1 -0
- package/dist/components/navigation-item/NavigationItem.js +18 -0
- package/dist/components/navigation-item/NavigationItem.js.map +1 -0
- package/dist/components/navigation-item/index.d.ts +3 -0
- package/dist/components/navigation-item/index.d.ts.map +1 -0
- package/dist/components/navigation-item/index.js +2 -0
- package/dist/components/navigation-item/index.js.map +1 -0
- package/dist/components/navigation-item/types.d.ts +23 -0
- package/dist/components/navigation-item/types.d.ts.map +1 -0
- package/dist/components/navigation-item/types.js +2 -0
- package/dist/components/navigation-item/types.js.map +1 -0
- package/dist/components/navigation-list/NavigationList.d.ts +4 -0
- package/dist/components/navigation-list/NavigationList.d.ts.map +1 -0
- package/dist/components/navigation-list/NavigationList.js +26 -0
- package/dist/components/navigation-list/NavigationList.js.map +1 -0
- package/dist/components/navigation-list/index.d.ts +3 -0
- package/dist/components/navigation-list/index.d.ts.map +1 -0
- package/dist/components/navigation-list/index.js +2 -0
- package/dist/components/navigation-list/index.js.map +1 -0
- package/dist/components/navigation-list/types.d.ts +15 -0
- package/dist/components/navigation-list/types.d.ts.map +1 -0
- package/dist/components/navigation-list/types.js +2 -0
- package/dist/components/navigation-list/types.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/internal/resolveZoraNavigationItems.d.ts +64 -0
- package/dist/internal/resolveZoraNavigationItems.d.ts.map +1 -0
- package/dist/internal/resolveZoraNavigationItems.js +55 -0
- package/dist/internal/resolveZoraNavigationItems.js.map +1 -0
- package/dist/patterns/image-preview/ImagePreview.d.ts +4 -0
- package/dist/patterns/image-preview/ImagePreview.d.ts.map +1 -0
- package/dist/patterns/image-preview/ImagePreview.js +41 -0
- package/dist/patterns/image-preview/ImagePreview.js.map +1 -0
- package/dist/patterns/image-preview/index.d.ts +3 -0
- package/dist/patterns/image-preview/index.d.ts.map +1 -0
- package/dist/patterns/image-preview/index.js +2 -0
- package/dist/patterns/image-preview/index.js.map +1 -0
- package/dist/patterns/image-preview/types.d.ts +36 -0
- package/dist/patterns/image-preview/types.d.ts.map +1 -0
- package/dist/patterns/image-preview/types.js +2 -0
- package/dist/patterns/image-preview/types.js.map +1 -0
- package/dist/patterns/image-upload-field/ImageUploadField.d.ts +4 -0
- package/dist/patterns/image-upload-field/ImageUploadField.d.ts.map +1 -0
- package/dist/patterns/image-upload-field/ImageUploadField.js +211 -0
- package/dist/patterns/image-upload-field/ImageUploadField.js.map +1 -0
- package/dist/patterns/image-upload-field/index.d.ts +3 -0
- package/dist/patterns/image-upload-field/index.d.ts.map +1 -0
- package/dist/patterns/image-upload-field/index.js +2 -0
- package/dist/patterns/image-upload-field/index.js.map +1 -0
- package/dist/patterns/image-upload-field/types.d.ts +35 -0
- package/dist/patterns/image-upload-field/types.d.ts.map +1 -0
- package/dist/patterns/image-upload-field/types.js +2 -0
- package/dist/patterns/image-upload-field/types.js.map +1 -0
- package/dist/patterns/image-upload-field/uploadFlow.d.ts +18 -0
- package/dist/patterns/image-upload-field/uploadFlow.d.ts.map +1 -0
- package/dist/patterns/image-upload-field/uploadFlow.js +106 -0
- package/dist/patterns/image-upload-field/uploadFlow.js.map +1 -0
- package/dist/patterns/zora-drawer-content/ZoraDrawerContent.d.ts +4 -0
- package/dist/patterns/zora-drawer-content/ZoraDrawerContent.d.ts.map +1 -0
- package/dist/patterns/zora-drawer-content/ZoraDrawerContent.js +26 -0
- package/dist/patterns/zora-drawer-content/ZoraDrawerContent.js.map +1 -0
- package/dist/patterns/zora-drawer-content/index.d.ts +3 -0
- package/dist/patterns/zora-drawer-content/index.d.ts.map +1 -0
- package/dist/patterns/zora-drawer-content/index.js +2 -0
- package/dist/patterns/zora-drawer-content/index.js.map +1 -0
- package/dist/patterns/zora-drawer-content/types.d.ts +15 -0
- package/dist/patterns/zora-drawer-content/types.d.ts.map +1 -0
- package/dist/patterns/zora-drawer-content/types.js +2 -0
- package/dist/patterns/zora-drawer-content/types.js.map +1 -0
- package/dist/patterns/zora-tab-bar/ZoraTabBar.d.ts +4 -0
- package/dist/patterns/zora-tab-bar/ZoraTabBar.d.ts.map +1 -0
- package/dist/patterns/zora-tab-bar/ZoraTabBar.js +33 -0
- package/dist/patterns/zora-tab-bar/ZoraTabBar.js.map +1 -0
- package/dist/patterns/zora-tab-bar/index.d.ts +3 -0
- package/dist/patterns/zora-tab-bar/index.d.ts.map +1 -0
- package/dist/patterns/zora-tab-bar/index.js +2 -0
- package/dist/patterns/zora-tab-bar/index.js.map +1 -0
- package/dist/patterns/zora-tab-bar/types.d.ts +19 -0
- package/dist/patterns/zora-tab-bar/types.d.ts.map +1 -0
- package/dist/patterns/zora-tab-bar/types.js +2 -0
- package/dist/patterns/zora-tab-bar/types.js.map +1 -0
- package/package.json +3 -3
- package/src/audit.test.ts +29 -0
- package/src/components/image/Image.tsx +11 -0
- package/src/components/image/index.ts +2 -0
- package/src/components/image/types.ts +7 -0
- package/src/components/navigation-item/NavigationItem.tsx +36 -0
- package/src/components/navigation-item/index.ts +6 -0
- package/src/components/navigation-item/types.ts +26 -0
- package/src/components/navigation-list/NavigationList.tsx +62 -0
- package/src/components/navigation-list/index.ts +2 -0
- package/src/components/navigation-list/types.ts +17 -0
- package/src/index.ts +26 -0
- package/src/internal/resolveZoraNavigationItems.test.ts +163 -0
- package/src/internal/resolveZoraNavigationItems.ts +156 -0
- package/src/navigationExports.test.ts +15 -0
- package/src/patterns/image-preview/ImagePreview.tsx +76 -0
- package/src/patterns/image-preview/index.ts +2 -0
- package/src/patterns/image-preview/types.ts +41 -0
- package/src/patterns/image-upload-field/ImageUploadField.tsx +293 -0
- package/src/patterns/image-upload-field/index.ts +2 -0
- package/src/patterns/image-upload-field/types.ts +41 -0
- package/src/patterns/image-upload-field/uploadFlow.test.ts +117 -0
- package/src/patterns/image-upload-field/uploadFlow.ts +145 -0
- package/src/patterns/zora-drawer-content/ZoraDrawerContent.tsx +53 -0
- package/src/patterns/zora-drawer-content/index.ts +2 -0
- package/src/patterns/zora-drawer-content/types.ts +20 -0
- package/src/patterns/zora-tab-bar/ZoraTabBar.tsx +55 -0
- package/src/patterns/zora-tab-bar/index.ts +2 -0
- package/src/patterns/zora-tab-bar/types.ts +18 -0
- package/src/showcaseCoverage.test.ts +7 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
createDrawerItemPressHandler,
|
|
6
|
+
createTabBarItemPressHandler,
|
|
7
|
+
resolveNavigationItems,
|
|
8
|
+
resolveRouteLabel,
|
|
9
|
+
type ZoraNavigationDescriptor,
|
|
10
|
+
type ZoraNavigationState,
|
|
11
|
+
type ZoraResolvedNavigationItem,
|
|
12
|
+
type ZoraRouteLabelSource,
|
|
13
|
+
} from './resolveZoraNavigationItems';
|
|
14
|
+
|
|
15
|
+
describe('resolveRouteLabel', () => {
|
|
16
|
+
test('prefers routeMap label over descriptor label', () => {
|
|
17
|
+
const descriptor: ZoraNavigationDescriptor = {
|
|
18
|
+
options: {
|
|
19
|
+
title: 'Home Title',
|
|
20
|
+
tabBarLabel: 'Home Tab',
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const result = resolveRouteLabel({
|
|
25
|
+
kind: 'tab',
|
|
26
|
+
route: { key: 'k1', name: 'home' },
|
|
27
|
+
metadata: { label: 'Home Map' },
|
|
28
|
+
descriptor,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const expectedSource: ZoraRouteLabelSource = 'routeMap';
|
|
32
|
+
expect(result).toEqual({ label: 'Home Map', source: expectedSource });
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('uses descriptor label/title as fallback for label only', () => {
|
|
36
|
+
const descriptor: ZoraNavigationDescriptor = {
|
|
37
|
+
options: {
|
|
38
|
+
title: 'Settings Title',
|
|
39
|
+
tabBarLabel: undefined,
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const result = resolveRouteLabel({
|
|
44
|
+
kind: 'tab',
|
|
45
|
+
route: { key: 'k2', name: 'settings' },
|
|
46
|
+
metadata: undefined,
|
|
47
|
+
descriptor,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
expect(result).toEqual({ label: 'Settings Title', source: 'descriptor' });
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('falls back to route name if nothing else provided', () => {
|
|
54
|
+
const result = resolveRouteLabel({
|
|
55
|
+
kind: 'drawer',
|
|
56
|
+
route: { key: 'k3', name: 'profile' },
|
|
57
|
+
metadata: undefined,
|
|
58
|
+
descriptor: undefined,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
expect(result).toEqual({ label: 'profile', source: 'name' });
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
describe('resolveNavigationItems', () => {
|
|
66
|
+
test('maps active index + routeMap disabled state without using descriptors for icons/badges', () => {
|
|
67
|
+
const state: ZoraNavigationState = {
|
|
68
|
+
index: 1,
|
|
69
|
+
routes: [
|
|
70
|
+
{ key: 'r1', name: 'home' },
|
|
71
|
+
{ key: 'r2', name: 'settings' },
|
|
72
|
+
],
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const items = resolveNavigationItems({
|
|
76
|
+
state,
|
|
77
|
+
kind: 'tab',
|
|
78
|
+
descriptors: {
|
|
79
|
+
r1: { options: { tabBarLabel: 'Home Opt', title: 'Home Title' } },
|
|
80
|
+
r2: { options: { tabBarLabel: 'Settings Opt' } },
|
|
81
|
+
},
|
|
82
|
+
routeMap: {
|
|
83
|
+
settings: { disabled: true, badge: React.createElement('span', null, '3') },
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
expect(items).toHaveLength(2);
|
|
88
|
+
expect(items[0]).toMatchObject({ active: false, disabled: false });
|
|
89
|
+
expect(items[1]).toMatchObject({ active: true, disabled: true });
|
|
90
|
+
expect(items[1]?.metadata?.badge).toBeDefined();
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
describe('press handler helpers', () => {
|
|
95
|
+
test('tab press handler is undefined for disabled or active items', () => {
|
|
96
|
+
const handlerDisabled = createTabBarItemPressHandler({
|
|
97
|
+
item: {
|
|
98
|
+
route: { key: 'k1', name: 'home' },
|
|
99
|
+
metadata: { disabled: true },
|
|
100
|
+
label: 'Home',
|
|
101
|
+
active: false,
|
|
102
|
+
disabled: true,
|
|
103
|
+
},
|
|
104
|
+
navigation: { navigate: () => undefined },
|
|
105
|
+
});
|
|
106
|
+
expect(handlerDisabled).toBeUndefined();
|
|
107
|
+
|
|
108
|
+
const handlerActive = createTabBarItemPressHandler({
|
|
109
|
+
item: {
|
|
110
|
+
route: { key: 'k1', name: 'home' },
|
|
111
|
+
metadata: undefined,
|
|
112
|
+
label: 'Home',
|
|
113
|
+
active: true,
|
|
114
|
+
disabled: false,
|
|
115
|
+
},
|
|
116
|
+
navigation: { navigate: () => undefined },
|
|
117
|
+
});
|
|
118
|
+
expect(handlerActive).toBeUndefined();
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test('tab press handler respects preventDefault', () => {
|
|
122
|
+
let navigatedTo: string | null = null;
|
|
123
|
+
const handler = createTabBarItemPressHandler({
|
|
124
|
+
item: {
|
|
125
|
+
route: { key: 'k1', name: 'home' },
|
|
126
|
+
metadata: undefined,
|
|
127
|
+
label: 'Home',
|
|
128
|
+
active: false,
|
|
129
|
+
disabled: false,
|
|
130
|
+
},
|
|
131
|
+
navigation: {
|
|
132
|
+
emit: () => ({ defaultPrevented: true }),
|
|
133
|
+
navigate: (name) => {
|
|
134
|
+
navigatedTo = name;
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
expect(handler).toBeDefined();
|
|
140
|
+
handler?.();
|
|
141
|
+
expect(navigatedTo).toBeNull();
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test('drawer press handler navigates and closes drawer', () => {
|
|
145
|
+
const calls: string[] = [];
|
|
146
|
+
const handler = createDrawerItemPressHandler({
|
|
147
|
+
item: {
|
|
148
|
+
route: { key: 'k1', name: 'home' },
|
|
149
|
+
metadata: undefined,
|
|
150
|
+
label: 'Home',
|
|
151
|
+
active: false,
|
|
152
|
+
disabled: false,
|
|
153
|
+
} satisfies ZoraResolvedNavigationItem,
|
|
154
|
+
navigation: {
|
|
155
|
+
navigate: (name) => calls.push(`navigate:${name}`),
|
|
156
|
+
closeDrawer: () => calls.push('close'),
|
|
157
|
+
},
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
handler?.();
|
|
161
|
+
expect(calls).toEqual(['navigate:home', 'close']);
|
|
162
|
+
});
|
|
163
|
+
});
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import type React from 'react';
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
ZoraNavigationRouteMetadata,
|
|
5
|
+
ZoraNavigationRouteState,
|
|
6
|
+
} from '../components/navigation-item';
|
|
7
|
+
import type { ZoraNavigationRouteMap } from '../components/navigation-list';
|
|
8
|
+
|
|
9
|
+
export interface ZoraNavigationDescriptorOptions {
|
|
10
|
+
title?: string;
|
|
11
|
+
tabBarLabel?: string | React.ReactNode;
|
|
12
|
+
drawerLabel?: string | React.ReactNode;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface ZoraNavigationDescriptor {
|
|
16
|
+
options?: ZoraNavigationDescriptorOptions;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type ZoraNavigationDescriptors = Readonly<
|
|
20
|
+
Record<string, ZoraNavigationDescriptor | undefined>
|
|
21
|
+
>;
|
|
22
|
+
|
|
23
|
+
export interface ZoraNavigationState {
|
|
24
|
+
index: number;
|
|
25
|
+
routes: readonly ZoraNavigationRouteState[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface ZoraTabPressEvent {
|
|
29
|
+
type: 'tabPress';
|
|
30
|
+
target: string;
|
|
31
|
+
canPreventDefault: true;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface ZoraTabPressEventResult {
|
|
35
|
+
defaultPrevented: boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface ZoraTabBarNavigation {
|
|
39
|
+
emit?: (event: ZoraTabPressEvent) => ZoraTabPressEventResult;
|
|
40
|
+
navigate: (name: string) => void;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface ZoraDrawerNavigation {
|
|
44
|
+
navigate: (name: string) => void;
|
|
45
|
+
closeDrawer?: () => void;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface ZoraResolvedNavigationItem {
|
|
49
|
+
route: ZoraNavigationRouteState;
|
|
50
|
+
metadata?: ZoraNavigationRouteMetadata;
|
|
51
|
+
label: React.ReactNode;
|
|
52
|
+
active: boolean;
|
|
53
|
+
disabled: boolean;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export type ZoraRouteLabelSource = 'routeMap' | 'descriptor' | 'name';
|
|
57
|
+
|
|
58
|
+
export function resolveRouteLabel({
|
|
59
|
+
route,
|
|
60
|
+
metadata,
|
|
61
|
+
descriptor,
|
|
62
|
+
kind,
|
|
63
|
+
}: {
|
|
64
|
+
route: ZoraNavigationRouteState;
|
|
65
|
+
metadata: ZoraNavigationRouteMetadata | undefined;
|
|
66
|
+
descriptor: ZoraNavigationDescriptor | undefined;
|
|
67
|
+
kind: 'tab' | 'drawer';
|
|
68
|
+
}): { label: React.ReactNode; source: ZoraRouteLabelSource } {
|
|
69
|
+
if (metadata?.label !== undefined) {
|
|
70
|
+
return { label: metadata.label, source: 'routeMap' };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const options = descriptor?.options;
|
|
74
|
+
const fallback =
|
|
75
|
+
kind === 'tab'
|
|
76
|
+
? (options?.tabBarLabel ?? options?.title)
|
|
77
|
+
: (options?.drawerLabel ?? options?.title);
|
|
78
|
+
|
|
79
|
+
if (fallback !== undefined) {
|
|
80
|
+
return { label: fallback, source: 'descriptor' };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return { label: route.name, source: 'name' };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function resolveNavigationItems({
|
|
87
|
+
state,
|
|
88
|
+
descriptors,
|
|
89
|
+
routeMap,
|
|
90
|
+
kind,
|
|
91
|
+
}: {
|
|
92
|
+
state: ZoraNavigationState;
|
|
93
|
+
descriptors?: ZoraNavigationDescriptors | undefined;
|
|
94
|
+
routeMap?: ZoraNavigationRouteMap | undefined;
|
|
95
|
+
kind: 'tab' | 'drawer';
|
|
96
|
+
}): readonly ZoraResolvedNavigationItem[] {
|
|
97
|
+
const activeRoute = state.routes[state.index];
|
|
98
|
+
|
|
99
|
+
return state.routes.map((route) => {
|
|
100
|
+
const metadata = routeMap?.[route.name];
|
|
101
|
+
const descriptor = descriptors?.[route.key];
|
|
102
|
+
const { label } = resolveRouteLabel({ route, metadata, descriptor, kind });
|
|
103
|
+
const disabled = Boolean(metadata?.disabled);
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
route,
|
|
107
|
+
metadata,
|
|
108
|
+
label,
|
|
109
|
+
active: activeRoute ? activeRoute.key === route.key : false,
|
|
110
|
+
disabled,
|
|
111
|
+
};
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function createTabBarItemPressHandler({
|
|
116
|
+
item,
|
|
117
|
+
navigation,
|
|
118
|
+
}: {
|
|
119
|
+
item: ZoraResolvedNavigationItem;
|
|
120
|
+
navigation: ZoraTabBarNavigation;
|
|
121
|
+
}): (() => void) | undefined {
|
|
122
|
+
if (item.disabled || item.active) {
|
|
123
|
+
return undefined;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return () => {
|
|
127
|
+
const result = navigation.emit?.({
|
|
128
|
+
type: 'tabPress',
|
|
129
|
+
target: item.route.key,
|
|
130
|
+
canPreventDefault: true,
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
if (result?.defaultPrevented) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
navigation.navigate(item.route.name);
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function createDrawerItemPressHandler({
|
|
142
|
+
item,
|
|
143
|
+
navigation,
|
|
144
|
+
}: {
|
|
145
|
+
item: ZoraResolvedNavigationItem;
|
|
146
|
+
navigation: ZoraDrawerNavigation;
|
|
147
|
+
}): (() => void) | undefined {
|
|
148
|
+
if (item.disabled) {
|
|
149
|
+
return undefined;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return () => {
|
|
153
|
+
navigation.navigate(item.route.name);
|
|
154
|
+
navigation.closeDrawer?.();
|
|
155
|
+
};
|
|
156
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
|
|
4
|
+
import { describe, expect, test } from 'bun:test';
|
|
5
|
+
|
|
6
|
+
describe('navigation exports', () => {
|
|
7
|
+
test('exports navigation chrome from src/index.ts', () => {
|
|
8
|
+
const source = readFileSync(join(process.cwd(), 'src', 'index.ts'), 'utf8');
|
|
9
|
+
|
|
10
|
+
expect(source).toContain('export { NavigationItem }');
|
|
11
|
+
expect(source).toContain('export { NavigationList }');
|
|
12
|
+
expect(source).toContain('export { ZoraTabBar }');
|
|
13
|
+
expect(source).toContain('export { ZoraDrawerContent }');
|
|
14
|
+
});
|
|
15
|
+
});
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
import { Image } from '../../components/image';
|
|
4
|
+
import { Text } from '../../components/text';
|
|
5
|
+
import { Box, Stack } from '../../foundation';
|
|
6
|
+
import { useZoraTheme } from '../../theme/useZoraTheme';
|
|
7
|
+
import { withZoraThemeScope } from '../../theme/withZoraThemeScope';
|
|
8
|
+
import type { ImagePreviewProps, ZoraImageAsset } from './types';
|
|
9
|
+
|
|
10
|
+
function resolveRenderableUrl(asset: ZoraImageAsset | null | undefined): string | null {
|
|
11
|
+
if (!asset) return null;
|
|
12
|
+
|
|
13
|
+
if (asset.kind === 'url') {
|
|
14
|
+
return asset.url;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return asset.publicUrl ?? null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function resolveSafeAspectRatio(value: number | undefined): number {
|
|
21
|
+
if (!value || !Number.isFinite(value) || value <= 0) {
|
|
22
|
+
return 1;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return value;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function ImagePreviewInner({
|
|
29
|
+
themeId: _themeId,
|
|
30
|
+
mode: _mode,
|
|
31
|
+
testID,
|
|
32
|
+
asset,
|
|
33
|
+
aspectRatio,
|
|
34
|
+
fit = 'cover',
|
|
35
|
+
emptyTitle = 'No image',
|
|
36
|
+
emptyDescription = 'Select an image to preview it here.',
|
|
37
|
+
}: ImagePreviewProps) {
|
|
38
|
+
const { theme } = useZoraTheme();
|
|
39
|
+
const renderableUrl = resolveRenderableUrl(asset);
|
|
40
|
+
const resolvedAspectRatio = resolveSafeAspectRatio(aspectRatio);
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<Box
|
|
44
|
+
bg={theme.semantics.neutral.surface}
|
|
45
|
+
borderColor={theme.semantics.neutral.divider}
|
|
46
|
+
borderWidth={1}
|
|
47
|
+
radius="l"
|
|
48
|
+
testID={testID}
|
|
49
|
+
style={{ overflow: 'hidden' }}
|
|
50
|
+
>
|
|
51
|
+
{renderableUrl ? (
|
|
52
|
+
<Box style={{ aspectRatio: resolvedAspectRatio, width: '100%' }}>
|
|
53
|
+
<Image
|
|
54
|
+
alt={asset?.alt}
|
|
55
|
+
fit={fit}
|
|
56
|
+
source={renderableUrl}
|
|
57
|
+
style={{ height: '100%', width: '100%' }}
|
|
58
|
+
/>
|
|
59
|
+
</Box>
|
|
60
|
+
) : (
|
|
61
|
+
<Box p="l">
|
|
62
|
+
<Stack gap="xs">
|
|
63
|
+
<Text variant="label" weight="semiBold">
|
|
64
|
+
{emptyTitle}
|
|
65
|
+
</Text>
|
|
66
|
+
<Text tone="muted" variant="bodySmall">
|
|
67
|
+
{emptyDescription}
|
|
68
|
+
</Text>
|
|
69
|
+
</Stack>
|
|
70
|
+
</Box>
|
|
71
|
+
)}
|
|
72
|
+
</Box>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export const ImagePreview = withZoraThemeScope(ImagePreviewInner);
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type React from 'react';
|
|
2
|
+
|
|
3
|
+
import type { ImageFit } from '../../components/image';
|
|
4
|
+
import type { ZoraBaseProps } from '../../theme/ZoraBaseProps';
|
|
5
|
+
|
|
6
|
+
export interface ZoraImageMetadata {
|
|
7
|
+
fileName?: string;
|
|
8
|
+
sizeBytes?: number;
|
|
9
|
+
createdAt?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type ZoraImageAsset =
|
|
13
|
+
| {
|
|
14
|
+
kind: 'url';
|
|
15
|
+
url: string;
|
|
16
|
+
alt?: string;
|
|
17
|
+
width?: number;
|
|
18
|
+
height?: number;
|
|
19
|
+
contentType?: string;
|
|
20
|
+
metadata?: ZoraImageMetadata;
|
|
21
|
+
}
|
|
22
|
+
| {
|
|
23
|
+
kind: 'storage';
|
|
24
|
+
storageId?: string;
|
|
25
|
+
bucket: string;
|
|
26
|
+
path: string;
|
|
27
|
+
publicUrl?: string;
|
|
28
|
+
alt?: string;
|
|
29
|
+
width?: number;
|
|
30
|
+
height?: number;
|
|
31
|
+
contentType?: string;
|
|
32
|
+
metadata?: ZoraImageMetadata;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export interface ImagePreviewProps extends ZoraBaseProps {
|
|
36
|
+
asset?: ZoraImageAsset | null;
|
|
37
|
+
aspectRatio?: number;
|
|
38
|
+
fit?: ImageFit;
|
|
39
|
+
emptyTitle?: React.ReactNode;
|
|
40
|
+
emptyDescription?: React.ReactNode;
|
|
41
|
+
}
|