@obisoft/ui 0.8.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/README.md +45 -0
- package/package.json +52 -0
- package/src/components/AppHeader.tsx +67 -0
- package/src/components/BottomSheet.tsx +110 -0
- package/src/components/Button.tsx +117 -0
- package/src/components/Card.tsx +46 -0
- package/src/components/CenteredState.tsx +35 -0
- package/src/components/Chip.tsx +47 -0
- package/src/components/ChoiceField.tsx +44 -0
- package/src/components/DateRangeFilter.tsx +226 -0
- package/src/components/EmptyState.tsx +32 -0
- package/src/components/ErrorState.tsx +62 -0
- package/src/components/Fab.tsx +36 -0
- package/src/components/FormRow.tsx +39 -0
- package/src/components/FormWorkspaceShell.tsx +126 -0
- package/src/components/GlassTabBar.tsx +39 -0
- package/src/components/Icon.tsx +259 -0
- package/src/components/IconButton.tsx +73 -0
- package/src/components/ListFilterBar.tsx +134 -0
- package/src/components/ListRow.tsx +198 -0
- package/src/components/ListWorkspace.tsx +76 -0
- package/src/components/LoadingState.tsx +37 -0
- package/src/components/MetricCard.tsx +74 -0
- package/src/components/MetricsGrid.tsx +25 -0
- package/src/components/MiniBarChart.tsx +42 -0
- package/src/components/NavigationDrawer.tsx +243 -0
- package/src/components/QueryState.tsx +82 -0
- package/src/components/QueryWorkspace.tsx +74 -0
- package/src/components/ScanInputBar.tsx +69 -0
- package/src/components/ScannerShell.tsx +76 -0
- package/src/components/Screen.tsx +44 -0
- package/src/components/SearchArea.tsx +34 -0
- package/src/components/SegmentedControl.tsx +64 -0
- package/src/components/Skeleton.tsx +42 -0
- package/src/components/StatusBadge.tsx +23 -0
- package/src/components/SuccessState.tsx +41 -0
- package/src/components/TextField.tsx +47 -0
- package/src/components/ToggleRow.tsx +35 -0
- package/src/components/record-detail/RecordDetail.tsx +60 -0
- package/src/components/record-detail/RecordDetailActions.tsx +88 -0
- package/src/components/record-detail/RecordDetailField.tsx +43 -0
- package/src/components/record-detail/RecordDetailHero.tsx +46 -0
- package/src/components/record-detail/RecordDetailLine.tsx +36 -0
- package/src/components/record-detail/RecordDetailSection.tsx +36 -0
- package/src/components/record-detail/index.ts +7 -0
- package/src/components/record-detail/status.ts +16 -0
- package/src/gallery/catalog.ts +31 -0
- package/src/glass/AmbientBackground.tsx +29 -0
- package/src/glass/GlassSurface.tsx +107 -0
- package/src/index.ts +66 -0
- package/src/layout/Box.tsx +62 -0
- package/src/layout/Divider.tsx +8 -0
- package/src/layout/Row.tsx +7 -0
- package/src/layout/Stack.tsx +7 -0
- package/src/provider/ObiUIProvider.tsx +45 -0
- package/src/styles/useObiStyles.ts +288 -0
- package/src/theme/effects.ts +219 -0
- package/src/theme/index.ts +3 -0
- package/src/theme/theme.ts +53 -0
- package/src/theme/tokens.ts +133 -0
- package/src/typography/ObiText.tsx +52 -0
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
import { StyleSheet } from 'react-native';
|
|
3
|
+
import { useObiTheme } from '../provider/ObiUIProvider';
|
|
4
|
+
import { shadow } from '../theme/effects';
|
|
5
|
+
import type { ObiTheme } from '../theme/theme';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Semantic utility layer for application screens.
|
|
9
|
+
*
|
|
10
|
+
* This is the React Native equivalent of the layout/utility layer provided by
|
|
11
|
+
* web frameworks such as Bootstrap. Components should be preferred when a
|
|
12
|
+
* semantic component exists, while these styles cover composition, form
|
|
13
|
+
* rhythm and screen-level layout without duplicating StyleSheet objects in
|
|
14
|
+
* every application.
|
|
15
|
+
*/
|
|
16
|
+
export function createObiStyles(theme: ObiTheme) {
|
|
17
|
+
const { colors, glass, radius, spacing, type } = theme;
|
|
18
|
+
|
|
19
|
+
return StyleSheet.create({
|
|
20
|
+
flex1: { flex: 1 },
|
|
21
|
+
pressed: { opacity: 0.75 },
|
|
22
|
+
|
|
23
|
+
formScroll: {
|
|
24
|
+
padding: spacing.lg,
|
|
25
|
+
gap: spacing.lg,
|
|
26
|
+
maxWidth: 840,
|
|
27
|
+
width: '100%',
|
|
28
|
+
alignSelf: 'center',
|
|
29
|
+
},
|
|
30
|
+
formScrollCompact: {
|
|
31
|
+
padding: spacing.lg,
|
|
32
|
+
gap: spacing.md,
|
|
33
|
+
maxWidth: 840,
|
|
34
|
+
width: '100%',
|
|
35
|
+
alignSelf: 'center',
|
|
36
|
+
},
|
|
37
|
+
wideScroll: {
|
|
38
|
+
padding: spacing.lg,
|
|
39
|
+
gap: spacing.md,
|
|
40
|
+
maxWidth: 1100,
|
|
41
|
+
width: '100%',
|
|
42
|
+
alignSelf: 'center',
|
|
43
|
+
},
|
|
44
|
+
authScroll: {
|
|
45
|
+
flexGrow: 1,
|
|
46
|
+
padding: spacing.xl,
|
|
47
|
+
justifyContent: 'center',
|
|
48
|
+
width: '100%',
|
|
49
|
+
maxWidth: 540,
|
|
50
|
+
alignSelf: 'center',
|
|
51
|
+
gap: spacing.xl,
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
cardFlush: { padding: 0, overflow: 'hidden' },
|
|
55
|
+
alertError: {
|
|
56
|
+
borderColor: colors.dangerBorder,
|
|
57
|
+
backgroundColor: colors.dangerSoft,
|
|
58
|
+
},
|
|
59
|
+
metricsRow: { flexDirection: 'row', gap: spacing.md },
|
|
60
|
+
gapSm: { gap: spacing.sm },
|
|
61
|
+
gapMd: { gap: spacing.md },
|
|
62
|
+
formSection: { gap: spacing.md },
|
|
63
|
+
formHero: { gap: spacing.md },
|
|
64
|
+
chipWrap: { flexDirection: 'row', flexWrap: 'wrap', gap: spacing.sm },
|
|
65
|
+
formRow: {
|
|
66
|
+
flexDirection: 'row',
|
|
67
|
+
alignItems: 'center',
|
|
68
|
+
justifyContent: 'space-between',
|
|
69
|
+
gap: spacing.md,
|
|
70
|
+
},
|
|
71
|
+
formFields: { flexDirection: 'row', gap: spacing.md },
|
|
72
|
+
formLineSelected: { borderColor: colors.primary, borderWidth: 1 },
|
|
73
|
+
headingRow: { flexDirection: 'row', alignItems: 'center', gap: spacing.md },
|
|
74
|
+
paginationRow: {
|
|
75
|
+
flexDirection: 'row',
|
|
76
|
+
alignItems: 'center',
|
|
77
|
+
justifyContent: 'center',
|
|
78
|
+
gap: spacing.md,
|
|
79
|
+
},
|
|
80
|
+
toggleRow: {
|
|
81
|
+
flexDirection: 'row',
|
|
82
|
+
alignItems: 'center',
|
|
83
|
+
justifyContent: 'space-between',
|
|
84
|
+
gap: spacing.lg,
|
|
85
|
+
},
|
|
86
|
+
toggleCopy: { flex: 1, gap: spacing.xs },
|
|
87
|
+
|
|
88
|
+
paymentRow: {
|
|
89
|
+
flexDirection: 'row',
|
|
90
|
+
alignItems: 'center',
|
|
91
|
+
gap: spacing.md,
|
|
92
|
+
minHeight: 48,
|
|
93
|
+
paddingHorizontal: spacing.md,
|
|
94
|
+
paddingVertical: spacing.sm,
|
|
95
|
+
borderRadius: radius.md,
|
|
96
|
+
backgroundColor: glass.surfaceStrong,
|
|
97
|
+
borderWidth: StyleSheet.hairlineWidth,
|
|
98
|
+
borderColor: glass.border,
|
|
99
|
+
...shadow.glassSm,
|
|
100
|
+
},
|
|
101
|
+
paymentLabel: { flex: 1 },
|
|
102
|
+
amountField: {
|
|
103
|
+
width: '100%',
|
|
104
|
+
minHeight: 40,
|
|
105
|
+
margin: 0,
|
|
106
|
+
paddingHorizontal: spacing.sm,
|
|
107
|
+
textAlign: 'right',
|
|
108
|
+
fontWeight: '700',
|
|
109
|
+
color: colors.text,
|
|
110
|
+
backgroundColor: glass.input,
|
|
111
|
+
borderWidth: StyleSheet.hairlineWidth,
|
|
112
|
+
borderColor: glass.borderSoft,
|
|
113
|
+
borderRadius: radius.sm,
|
|
114
|
+
},
|
|
115
|
+
input: {
|
|
116
|
+
minHeight: 48,
|
|
117
|
+
borderWidth: StyleSheet.hairlineWidth,
|
|
118
|
+
borderColor: glass.border,
|
|
119
|
+
borderRadius: radius.md,
|
|
120
|
+
backgroundColor: glass.input,
|
|
121
|
+
paddingHorizontal: spacing.md,
|
|
122
|
+
color: colors.text,
|
|
123
|
+
fontSize: type.body,
|
|
124
|
+
...shadow.glassSm,
|
|
125
|
+
},
|
|
126
|
+
sectionLabel: {
|
|
127
|
+
fontSize: 11,
|
|
128
|
+
fontWeight: '700',
|
|
129
|
+
color: colors.textMuted,
|
|
130
|
+
letterSpacing: 0.8,
|
|
131
|
+
textTransform: 'uppercase',
|
|
132
|
+
},
|
|
133
|
+
|
|
134
|
+
listItemPressed: { backgroundColor: colors.surfacePressed },
|
|
135
|
+
emptyState: {
|
|
136
|
+
flex: 1,
|
|
137
|
+
alignItems: 'center',
|
|
138
|
+
justifyContent: 'center',
|
|
139
|
+
padding: spacing.xxl,
|
|
140
|
+
gap: spacing.md,
|
|
141
|
+
},
|
|
142
|
+
filterRow: {
|
|
143
|
+
flexDirection: 'row',
|
|
144
|
+
flexWrap: 'wrap',
|
|
145
|
+
gap: spacing.sm,
|
|
146
|
+
},
|
|
147
|
+
headerActions: { flexDirection: 'row', gap: spacing.md, alignItems: 'center' },
|
|
148
|
+
headerLink: { color: colors.primary, fontWeight: '700', fontSize: 13 },
|
|
149
|
+
headerLinkDisabled: { opacity: 0.45 },
|
|
150
|
+
|
|
151
|
+
filterWrap: { gap: spacing.sm },
|
|
152
|
+
filterRowInner: {
|
|
153
|
+
flexDirection: 'row',
|
|
154
|
+
gap: spacing.sm,
|
|
155
|
+
alignItems: 'center',
|
|
156
|
+
},
|
|
157
|
+
filterSearch: { flex: 1 },
|
|
158
|
+
filterBtn: {
|
|
159
|
+
width: 48,
|
|
160
|
+
height: 48,
|
|
161
|
+
borderRadius: radius.md,
|
|
162
|
+
borderWidth: StyleSheet.hairlineWidth,
|
|
163
|
+
borderColor: glass.border,
|
|
164
|
+
backgroundColor: glass.control,
|
|
165
|
+
alignItems: 'center',
|
|
166
|
+
justifyContent: 'center',
|
|
167
|
+
...shadow.glassSm,
|
|
168
|
+
},
|
|
169
|
+
filterBtnActive: {
|
|
170
|
+
backgroundColor: glass.primaryTintStrong,
|
|
171
|
+
borderColor: colors.primary,
|
|
172
|
+
},
|
|
173
|
+
filterIcon: { gap: 3, alignItems: 'flex-start' },
|
|
174
|
+
filterBarMark: {
|
|
175
|
+
height: 2,
|
|
176
|
+
borderRadius: 1,
|
|
177
|
+
backgroundColor: colors.textMuted,
|
|
178
|
+
},
|
|
179
|
+
filterBarMarkActive: { backgroundColor: colors.primary },
|
|
180
|
+
dateRangeField: { justifyContent: 'center', paddingVertical: 0 },
|
|
181
|
+
dateRangeFieldText: {
|
|
182
|
+
color: colors.text,
|
|
183
|
+
fontSize: type.body,
|
|
184
|
+
fontWeight: '600',
|
|
185
|
+
},
|
|
186
|
+
dateRangeSheetSection: { marginTop: spacing.md, marginBottom: spacing.xs },
|
|
187
|
+
dateRangeSheetCaption: {
|
|
188
|
+
fontSize: type.caption,
|
|
189
|
+
color: colors.textMuted,
|
|
190
|
+
fontWeight: '700',
|
|
191
|
+
marginBottom: 2,
|
|
192
|
+
textTransform: 'uppercase',
|
|
193
|
+
letterSpacing: 0.6,
|
|
194
|
+
},
|
|
195
|
+
filterSheetContent: {
|
|
196
|
+
gap: spacing.xs,
|
|
197
|
+
paddingTop: spacing.sm,
|
|
198
|
+
paddingBottom: spacing.lg,
|
|
199
|
+
flexGrow: 1,
|
|
200
|
+
},
|
|
201
|
+
sheetOption: {
|
|
202
|
+
flexDirection: 'row',
|
|
203
|
+
alignItems: 'center',
|
|
204
|
+
justifyContent: 'space-between',
|
|
205
|
+
paddingVertical: spacing.lg,
|
|
206
|
+
minHeight: 56,
|
|
207
|
+
borderBottomWidth: StyleSheet.hairlineWidth,
|
|
208
|
+
borderBottomColor: glass.separator,
|
|
209
|
+
},
|
|
210
|
+
sheetOptionLabel: { fontSize: type.body, color: colors.text, fontWeight: '600' },
|
|
211
|
+
sheetOptionLabelSelected: { color: colors.primary, fontWeight: '800' },
|
|
212
|
+
|
|
213
|
+
loginBrand: { gap: spacing.sm },
|
|
214
|
+
loginLogo: {
|
|
215
|
+
width: 68,
|
|
216
|
+
height: 68,
|
|
217
|
+
borderRadius: 18,
|
|
218
|
+
backgroundColor: colors.primary,
|
|
219
|
+
alignItems: 'center',
|
|
220
|
+
justifyContent: 'center',
|
|
221
|
+
...shadow.floating,
|
|
222
|
+
},
|
|
223
|
+
detailCenter: {
|
|
224
|
+
alignItems: 'center',
|
|
225
|
+
justifyContent: 'center',
|
|
226
|
+
gap: spacing.lg,
|
|
227
|
+
padding: spacing.xl,
|
|
228
|
+
},
|
|
229
|
+
scanInputWrap: { flex: 1, gap: spacing.xs },
|
|
230
|
+
scanInputRow: {
|
|
231
|
+
flexDirection: 'row',
|
|
232
|
+
alignItems: 'center',
|
|
233
|
+
gap: spacing.sm,
|
|
234
|
+
flex: 1,
|
|
235
|
+
},
|
|
236
|
+
printerDevice: {
|
|
237
|
+
flexDirection: 'row',
|
|
238
|
+
alignItems: 'center',
|
|
239
|
+
justifyContent: 'space-between',
|
|
240
|
+
gap: spacing.md,
|
|
241
|
+
paddingVertical: spacing.sm,
|
|
242
|
+
},
|
|
243
|
+
tenantCard: {
|
|
244
|
+
flexDirection: 'row',
|
|
245
|
+
alignItems: 'center',
|
|
246
|
+
gap: spacing.md,
|
|
247
|
+
},
|
|
248
|
+
|
|
249
|
+
scannerRoot: { flex: 1, backgroundColor: '#000' },
|
|
250
|
+
scannerPermission: {
|
|
251
|
+
flex: 1,
|
|
252
|
+
alignItems: 'center',
|
|
253
|
+
justifyContent: 'center',
|
|
254
|
+
gap: spacing.lg,
|
|
255
|
+
padding: spacing.xl,
|
|
256
|
+
backgroundColor: colors.background,
|
|
257
|
+
},
|
|
258
|
+
scannerText: { textAlign: 'center', maxWidth: 420 },
|
|
259
|
+
scannerScrim: {
|
|
260
|
+
paddingTop: spacing.xl,
|
|
261
|
+
paddingHorizontal: spacing.lg,
|
|
262
|
+
paddingBottom: spacing.xl,
|
|
263
|
+
backgroundColor: 'rgba(0,0,0,0.38)',
|
|
264
|
+
gap: spacing.sm,
|
|
265
|
+
},
|
|
266
|
+
scannerWhite: { color: colors.onPrimary, textAlign: 'center' },
|
|
267
|
+
scannerSheet: {
|
|
268
|
+
position: 'absolute',
|
|
269
|
+
left: spacing.md,
|
|
270
|
+
right: spacing.md,
|
|
271
|
+
bottom: spacing.md,
|
|
272
|
+
backgroundColor: glass.chromeStrong,
|
|
273
|
+
borderRadius: radius.xl,
|
|
274
|
+
padding: spacing.lg,
|
|
275
|
+
gap: spacing.md,
|
|
276
|
+
borderWidth: StyleSheet.hairlineWidth,
|
|
277
|
+
borderColor: glass.border,
|
|
278
|
+
...shadow.lg,
|
|
279
|
+
},
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
export type ObiStyles = ReturnType<typeof createObiStyles>;
|
|
284
|
+
|
|
285
|
+
export function useObiStyles(): ObiStyles {
|
|
286
|
+
const theme = useObiTheme();
|
|
287
|
+
return useMemo(() => createObiStyles(theme), [theme]);
|
|
288
|
+
}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import type { AppColors } from "./tokens";
|
|
2
|
+
import { Platform, type ViewStyle } from "react-native";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Liquid Glass material tokens.
|
|
6
|
+
*
|
|
7
|
+
* These values intentionally use lower alpha than a traditional
|
|
8
|
+
* glassmorphism theme. Native blur/refraction should provide most of the
|
|
9
|
+
* perceived surface; tint, border and elevation only define its edge.
|
|
10
|
+
*/
|
|
11
|
+
function isDarkPalette(colors: AppColors) {
|
|
12
|
+
const hex = colors.background.replace("#", "");
|
|
13
|
+
if (hex.length !== 6) return false;
|
|
14
|
+
|
|
15
|
+
const r = parseInt(hex.slice(0, 2), 16) / 255;
|
|
16
|
+
const g = parseInt(hex.slice(2, 4), 16) / 255;
|
|
17
|
+
const b = parseInt(hex.slice(4, 6), 16) / 255;
|
|
18
|
+
|
|
19
|
+
const linearize = (value: number) =>
|
|
20
|
+
value <= 0.03928 ? value / 12.92 : Math.pow((value + 0.055) / 1.055, 2.4);
|
|
21
|
+
|
|
22
|
+
const luminance =
|
|
23
|
+
0.2126 * linearize(r) +
|
|
24
|
+
0.7152 * linearize(g) +
|
|
25
|
+
0.0722 * linearize(b);
|
|
26
|
+
|
|
27
|
+
return luminance < 0.22;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function createGlass(colors: AppColors) {
|
|
31
|
+
if (isDarkPalette(colors)) {
|
|
32
|
+
return {
|
|
33
|
+
surface: "rgba(25,30,42,0.30)",
|
|
34
|
+
surfaceStrong: "rgba(25,30,42,0.40)",
|
|
35
|
+
surfaceDense: "rgba(25,30,42,0.58)",
|
|
36
|
+
surfaceSoft: "rgba(39,45,61,0.20)",
|
|
37
|
+
|
|
38
|
+
chrome: "rgba(18,22,32,0.18)",
|
|
39
|
+
chromeStrong: "rgba(18,22,32,0.30)",
|
|
40
|
+
control: "rgba(49,56,74,0.22)",
|
|
41
|
+
controlPressed: "rgba(71,79,102,0.34)",
|
|
42
|
+
input: "rgba(28,34,47,0.34)",
|
|
43
|
+
|
|
44
|
+
border: "rgba(255,255,255,0.15)",
|
|
45
|
+
borderSoft: "rgba(255,255,255,0.09)",
|
|
46
|
+
innerStroke: "rgba(255,255,255,0.10)",
|
|
47
|
+
highlight: "rgba(255,255,255,0.16)",
|
|
48
|
+
highlightSoft: "rgba(255,255,255,0.075)",
|
|
49
|
+
separator: "rgba(255,255,255,0.075)",
|
|
50
|
+
|
|
51
|
+
backdrop: "rgba(3,7,18,0.24)",
|
|
52
|
+
backdropStrong: "rgba(3,7,18,0.40)",
|
|
53
|
+
|
|
54
|
+
/** Paneles laterales (sidebar / checkout) — casi opacos, sin BlurView. */
|
|
55
|
+
panel: "rgba(18,22,32,0.96)",
|
|
56
|
+
|
|
57
|
+
primaryTint: "rgba(123,124,240,0.14)",
|
|
58
|
+
primaryTintStrong: "rgba(123,124,240,0.24)",
|
|
59
|
+
dangerTint: "rgba(249,112,102,0.12)",
|
|
60
|
+
successTint: "rgba(60,203,127,0.11)",
|
|
61
|
+
darkSurface: "rgba(6,9,15,0.66)",
|
|
62
|
+
darkBorder: "rgba(255,255,255,0.14)",
|
|
63
|
+
} as const;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
surface: "rgba(255,255,255,0.28)",
|
|
68
|
+
surfaceStrong: "rgba(255,255,255,0.38)",
|
|
69
|
+
surfaceDense: "rgba(255,255,255,0.52)",
|
|
70
|
+
surfaceSoft: "rgba(255,255,255,0.16)",
|
|
71
|
+
|
|
72
|
+
chrome: "rgba(255,255,255,0.22)",
|
|
73
|
+
chromeStrong: "rgba(255,255,255,0.36)",
|
|
74
|
+
control: "rgba(255,255,255,0.28)",
|
|
75
|
+
controlPressed: "rgba(255,255,255,0.42)",
|
|
76
|
+
input: "rgba(255,255,255,0.40)",
|
|
77
|
+
|
|
78
|
+
border: "rgba(255,255,255,0.70)",
|
|
79
|
+
borderSoft: "rgba(255,255,255,0.42)",
|
|
80
|
+
innerStroke: "rgba(255,255,255,0.55)",
|
|
81
|
+
highlight: "rgba(255,255,255,0.85)",
|
|
82
|
+
highlightSoft: "rgba(255,255,255,0.45)",
|
|
83
|
+
separator: "rgba(15,23,42,0.07)",
|
|
84
|
+
|
|
85
|
+
backdrop: "rgba(15,23,42,0.28)",
|
|
86
|
+
backdropStrong: "rgba(15,23,42,0.42)",
|
|
87
|
+
|
|
88
|
+
/** Paneles laterales (sidebar / checkout) — casi opacos, sin BlurView. */
|
|
89
|
+
panel: "rgba(248,250,255,0.96)",
|
|
90
|
+
|
|
91
|
+
primaryTint: "rgba(91,92,230,0.10)",
|
|
92
|
+
primaryTintStrong: "rgba(91,92,230,0.18)",
|
|
93
|
+
dangerTint: "rgba(217,45,32,0.09)",
|
|
94
|
+
successTint: "rgba(22,132,91,0.09)",
|
|
95
|
+
darkSurface: "rgba(17,24,39,0.64)",
|
|
96
|
+
darkBorder: "rgba(255,255,255,0.14)",
|
|
97
|
+
} as const;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export const liquidGlassPresets = {
|
|
101
|
+
chrome: {
|
|
102
|
+
glassType: "clear" as const,
|
|
103
|
+
glassOpacity: 0.10,
|
|
104
|
+
blurAmount: 30,
|
|
105
|
+
},
|
|
106
|
+
dock: {
|
|
107
|
+
glassType: "clear" as const,
|
|
108
|
+
glassOpacity: 0.12,
|
|
109
|
+
blurAmount: 34,
|
|
110
|
+
},
|
|
111
|
+
control: {
|
|
112
|
+
glassType: "clear" as const,
|
|
113
|
+
glassOpacity: 0.08,
|
|
114
|
+
blurAmount: 22,
|
|
115
|
+
},
|
|
116
|
+
surface: {
|
|
117
|
+
glassType: "regular" as const,
|
|
118
|
+
glassOpacity: 0.14,
|
|
119
|
+
blurAmount: 30,
|
|
120
|
+
},
|
|
121
|
+
sheet: {
|
|
122
|
+
glassType: "regular" as const,
|
|
123
|
+
glassOpacity: 0.20,
|
|
124
|
+
blurAmount: 38,
|
|
125
|
+
},
|
|
126
|
+
modal: {
|
|
127
|
+
glassType: "regular" as const,
|
|
128
|
+
glassOpacity: 0.22,
|
|
129
|
+
blurAmount: 40,
|
|
130
|
+
},
|
|
131
|
+
scanner: {
|
|
132
|
+
glassType: "clear" as const,
|
|
133
|
+
glassOpacity: 0.14,
|
|
134
|
+
blurAmount: 34,
|
|
135
|
+
},
|
|
136
|
+
} as const;
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Android `elevation` pinta una silueta opaca detrás de views translúcidos
|
|
140
|
+
* y rompe el glass. Usamos boxShadow (RN moderno) en ambas plataformas;
|
|
141
|
+
* en iOS también shadow* por compatibilidad visual.
|
|
142
|
+
*/
|
|
143
|
+
function glassDepth(
|
|
144
|
+
ios: Pick<ViewStyle, "shadowColor" | "shadowOffset" | "shadowOpacity" | "shadowRadius">,
|
|
145
|
+
box: string,
|
|
146
|
+
): ViewStyle {
|
|
147
|
+
return Platform.select<ViewStyle>({
|
|
148
|
+
ios: { ...ios },
|
|
149
|
+
android: {
|
|
150
|
+
elevation: 0,
|
|
151
|
+
// @ts-expect-error boxShadow is supported in RN 0.76+
|
|
152
|
+
boxShadow: box,
|
|
153
|
+
},
|
|
154
|
+
default: { elevation: 0 },
|
|
155
|
+
})!;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export const shadow = {
|
|
159
|
+
xs: {
|
|
160
|
+
shadowColor: "#0F172A",
|
|
161
|
+
shadowOffset: { width: 0, height: 1 },
|
|
162
|
+
shadowOpacity: 0.025,
|
|
163
|
+
shadowRadius: 2,
|
|
164
|
+
elevation: 1,
|
|
165
|
+
},
|
|
166
|
+
sm: {
|
|
167
|
+
shadowColor: "#0F172A",
|
|
168
|
+
shadowOffset: { width: 0, height: 3 },
|
|
169
|
+
shadowOpacity: 0.05,
|
|
170
|
+
shadowRadius: 8,
|
|
171
|
+
elevation: 2,
|
|
172
|
+
},
|
|
173
|
+
md: {
|
|
174
|
+
shadowColor: "#0F172A",
|
|
175
|
+
shadowOffset: { width: 0, height: 7 },
|
|
176
|
+
shadowOpacity: 0.075,
|
|
177
|
+
shadowRadius: 18,
|
|
178
|
+
elevation: 4,
|
|
179
|
+
},
|
|
180
|
+
lg: {
|
|
181
|
+
shadowColor: "#0F172A",
|
|
182
|
+
shadowOffset: { width: 0, height: 16 },
|
|
183
|
+
shadowOpacity: 0.115,
|
|
184
|
+
shadowRadius: 34,
|
|
185
|
+
elevation: 8,
|
|
186
|
+
},
|
|
187
|
+
floating: {
|
|
188
|
+
shadowColor: "#25245F",
|
|
189
|
+
shadowOffset: { width: 0, height: 10 },
|
|
190
|
+
shadowOpacity: 0.16,
|
|
191
|
+
shadowRadius: 24,
|
|
192
|
+
elevation: 9,
|
|
193
|
+
},
|
|
194
|
+
top: {
|
|
195
|
+
shadowColor: "#0F172A",
|
|
196
|
+
shadowOffset: { width: 0, height: -6 },
|
|
197
|
+
shadowOpacity: 0.06,
|
|
198
|
+
shadowRadius: 18,
|
|
199
|
+
elevation: 7,
|
|
200
|
+
},
|
|
201
|
+
glass: glassDepth(
|
|
202
|
+
{
|
|
203
|
+
shadowColor: "#0F172A",
|
|
204
|
+
shadowOffset: { width: 0, height: 8 },
|
|
205
|
+
shadowOpacity: 0.12,
|
|
206
|
+
shadowRadius: 18,
|
|
207
|
+
},
|
|
208
|
+
"0px 6px 18px rgba(15, 23, 42, 0.12)",
|
|
209
|
+
),
|
|
210
|
+
glassSm: glassDepth(
|
|
211
|
+
{
|
|
212
|
+
shadowColor: "#0F172A",
|
|
213
|
+
shadowOffset: { width: 0, height: 4 },
|
|
214
|
+
shadowOpacity: 0.08,
|
|
215
|
+
shadowRadius: 12,
|
|
216
|
+
},
|
|
217
|
+
"0px 3px 12px rgba(15, 23, 42, 0.09)",
|
|
218
|
+
),
|
|
219
|
+
} as const;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { createGlass } from './effects';
|
|
2
|
+
import {
|
|
3
|
+
darkColors,
|
|
4
|
+
lightColors,
|
|
5
|
+
radius,
|
|
6
|
+
spacing,
|
|
7
|
+
type,
|
|
8
|
+
type AppColors,
|
|
9
|
+
} from './tokens';
|
|
10
|
+
|
|
11
|
+
export type ObiThemeMode = 'light' | 'dark';
|
|
12
|
+
export type ObiSpacingKey = keyof typeof spacing;
|
|
13
|
+
export type ObiRadiusKey = keyof typeof radius;
|
|
14
|
+
|
|
15
|
+
export type ObiTheme = {
|
|
16
|
+
mode: ObiThemeMode;
|
|
17
|
+
isDark: boolean;
|
|
18
|
+
colors: AppColors;
|
|
19
|
+
spacing: typeof spacing;
|
|
20
|
+
radius: typeof radius;
|
|
21
|
+
type: typeof type;
|
|
22
|
+
glass: ReturnType<typeof createGlass>;
|
|
23
|
+
navigation: {
|
|
24
|
+
bottomBarHeight: number;
|
|
25
|
+
headerMinHeight: number;
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export type ObiThemeOverrides = {
|
|
30
|
+
colors?: Partial<AppColors>;
|
|
31
|
+
navigation?: Partial<ObiTheme['navigation']>;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export function createObiTheme(mode: ObiThemeMode, overrides?: ObiThemeOverrides): ObiTheme {
|
|
35
|
+
const baseColors = mode === 'dark' ? darkColors : lightColors;
|
|
36
|
+
const colors: AppColors = { ...baseColors, ...(overrides?.colors ?? {}) };
|
|
37
|
+
const navigation = {
|
|
38
|
+
bottomBarHeight: 56,
|
|
39
|
+
headerMinHeight: 56,
|
|
40
|
+
...(overrides?.navigation ?? {}),
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
mode,
|
|
45
|
+
isDark: mode === 'dark',
|
|
46
|
+
colors,
|
|
47
|
+
spacing,
|
|
48
|
+
radius,
|
|
49
|
+
type,
|
|
50
|
+
glass: createGlass(colors),
|
|
51
|
+
navigation,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
export type AppColors = {
|
|
2
|
+
primary: string;
|
|
3
|
+
primaryDark: string;
|
|
4
|
+
primarySoft: string;
|
|
5
|
+
primaryFaint: string;
|
|
6
|
+
secondary: string;
|
|
7
|
+
secondarySoft: string;
|
|
8
|
+
|
|
9
|
+
background: string;
|
|
10
|
+
backgroundElevated: string;
|
|
11
|
+
surface: string;
|
|
12
|
+
surfaceMuted: string;
|
|
13
|
+
surfacePressed: string;
|
|
14
|
+
|
|
15
|
+
text: string;
|
|
16
|
+
textStrong: string;
|
|
17
|
+
textMuted: string;
|
|
18
|
+
textSubtle: string;
|
|
19
|
+
|
|
20
|
+
border: string;
|
|
21
|
+
borderStrong: string;
|
|
22
|
+
separator: string;
|
|
23
|
+
|
|
24
|
+
danger: string;
|
|
25
|
+
success: string;
|
|
26
|
+
warning: string;
|
|
27
|
+
info: string;
|
|
28
|
+
|
|
29
|
+
onPrimary: string;
|
|
30
|
+
onPrimaryMuted: string;
|
|
31
|
+
onPrimarySoft: string;
|
|
32
|
+
|
|
33
|
+
warningSoft: string;
|
|
34
|
+
warningBorder: string;
|
|
35
|
+
dangerSoft: string;
|
|
36
|
+
dangerBorder: string;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export const lightColors: AppColors = {
|
|
40
|
+
primary: "#5B5CE6",
|
|
41
|
+
primaryDark: "#4446C8",
|
|
42
|
+
primarySoft: "#EEEDFF",
|
|
43
|
+
primaryFaint: "#F6F5FF",
|
|
44
|
+
secondary: "#0EA596",
|
|
45
|
+
secondarySoft: "#EAFBF7",
|
|
46
|
+
|
|
47
|
+
background: "#EEF3FA",
|
|
48
|
+
backgroundElevated: "#F8F9FC",
|
|
49
|
+
surface: "#FFFFFF",
|
|
50
|
+
surfaceMuted: "#F6F7FA",
|
|
51
|
+
surfacePressed: "#EEF0F5",
|
|
52
|
+
|
|
53
|
+
text: "#111827",
|
|
54
|
+
textStrong: "#0B1220",
|
|
55
|
+
textMuted: "#667085",
|
|
56
|
+
textSubtle: "#98A2B3",
|
|
57
|
+
|
|
58
|
+
border: "#E3E7EE",
|
|
59
|
+
borderStrong: "#D5DAE3",
|
|
60
|
+
separator: "rgba(15, 23, 42, 0.075)",
|
|
61
|
+
|
|
62
|
+
danger: "#D92D20",
|
|
63
|
+
success: "#16845B",
|
|
64
|
+
warning: "#B54708",
|
|
65
|
+
info: "#2563EB",
|
|
66
|
+
|
|
67
|
+
onPrimary: "#FFFFFF",
|
|
68
|
+
onPrimaryMuted: "rgba(255,255,255,0.78)",
|
|
69
|
+
onPrimarySoft: "rgba(255,255,255,0.16)",
|
|
70
|
+
|
|
71
|
+
warningSoft: "#FFFAEB",
|
|
72
|
+
warningBorder: "#FEC84B",
|
|
73
|
+
dangerSoft: "#FEF3F2",
|
|
74
|
+
dangerBorder: "#FDA29B",
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export const darkColors: AppColors = {
|
|
78
|
+
primary: "#7B7CF0",
|
|
79
|
+
primaryDark: "#6263DC",
|
|
80
|
+
primarySoft: "#29294D",
|
|
81
|
+
primaryFaint: "#1B1B2E",
|
|
82
|
+
secondary: "#2DD4BF",
|
|
83
|
+
secondarySoft: "#123733",
|
|
84
|
+
|
|
85
|
+
background: "#0F1219",
|
|
86
|
+
backgroundElevated: "#131823",
|
|
87
|
+
surface: "#1A1F2B",
|
|
88
|
+
surfaceMuted: "#242A38",
|
|
89
|
+
surfacePressed: "#2B3241",
|
|
90
|
+
|
|
91
|
+
text: "#F3F5F9",
|
|
92
|
+
textStrong: "#FFFFFF",
|
|
93
|
+
textMuted: "#9AA3B5",
|
|
94
|
+
textSubtle: "#727D91",
|
|
95
|
+
|
|
96
|
+
border: "#2E3545",
|
|
97
|
+
borderStrong: "#3C4558",
|
|
98
|
+
separator: "rgba(255,255,255,0.075)",
|
|
99
|
+
|
|
100
|
+
danger: "#F97066",
|
|
101
|
+
success: "#3CCB7F",
|
|
102
|
+
warning: "#FDB022",
|
|
103
|
+
info: "#60A5FA",
|
|
104
|
+
|
|
105
|
+
onPrimary: "#FFFFFF",
|
|
106
|
+
onPrimaryMuted: "rgba(255,255,255,0.78)",
|
|
107
|
+
onPrimarySoft: "rgba(255,255,255,0.16)",
|
|
108
|
+
|
|
109
|
+
warningSoft: "#2A2110",
|
|
110
|
+
warningBorder: "#854D0E",
|
|
111
|
+
dangerSoft: "#2A1215",
|
|
112
|
+
dangerBorder: "#912018",
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
export const spacing = {
|
|
116
|
+
xs: 4,
|
|
117
|
+
sm: 8,
|
|
118
|
+
md: 12,
|
|
119
|
+
lg: 16,
|
|
120
|
+
xl: 24,
|
|
121
|
+
xxl: 32,
|
|
122
|
+
} as const;
|
|
123
|
+
|
|
124
|
+
// Rounded, but restrained so the POS keeps a professional, non-generic look.
|
|
125
|
+
export const radius = { sm: 8, md: 12, lg: 16, xl: 22, pill: 999 } as const;
|
|
126
|
+
|
|
127
|
+
export const type = {
|
|
128
|
+
caption: 12,
|
|
129
|
+
body: 15,
|
|
130
|
+
subtitle: 18,
|
|
131
|
+
title: 24,
|
|
132
|
+
display: 32,
|
|
133
|
+
} as const;
|