@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,46 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { View } from 'react-native';
|
|
3
|
+
import { useObiTheme } from '../../provider/ObiUIProvider';
|
|
4
|
+
import { ObiText } from '../../typography/ObiText';
|
|
5
|
+
import { StatusBadge, type StatusTone } from '../StatusBadge';
|
|
6
|
+
|
|
7
|
+
export function RecordDetailHero({
|
|
8
|
+
kicker,
|
|
9
|
+
title,
|
|
10
|
+
subtitle,
|
|
11
|
+
amount,
|
|
12
|
+
badge,
|
|
13
|
+
}: {
|
|
14
|
+
kicker?: string;
|
|
15
|
+
title: string;
|
|
16
|
+
subtitle?: string;
|
|
17
|
+
amount?: string;
|
|
18
|
+
badge?: { label: string; tone?: StatusTone };
|
|
19
|
+
}) {
|
|
20
|
+
const theme = useObiTheme();
|
|
21
|
+
return (
|
|
22
|
+
<View
|
|
23
|
+
style={{
|
|
24
|
+
alignItems: 'center',
|
|
25
|
+
paddingHorizontal: theme.spacing.xl,
|
|
26
|
+
paddingTop: theme.spacing.xl,
|
|
27
|
+
paddingBottom: theme.spacing.md,
|
|
28
|
+
gap: theme.spacing.sm,
|
|
29
|
+
}}
|
|
30
|
+
>
|
|
31
|
+
{kicker ? (
|
|
32
|
+
<ObiText variant="caption" weight="700" muted style={{ letterSpacing: 1.4, textTransform: 'uppercase' }}>
|
|
33
|
+
{kicker}
|
|
34
|
+
</ObiText>
|
|
35
|
+
) : null}
|
|
36
|
+
<ObiText variant="title" weight="800" style={{ textAlign: 'center', fontSize: 22, lineHeight: 28 }}>{title}</ObiText>
|
|
37
|
+
{subtitle ? <ObiText muted style={{ textAlign: 'center', fontSize: 14 }}>{subtitle}</ObiText> : null}
|
|
38
|
+
{badge ? <StatusBadge label={badge.label} tone={badge.tone} /> : null}
|
|
39
|
+
{amount ? (
|
|
40
|
+
<ObiText variant="display" weight="800" style={{ marginTop: theme.spacing.xs, letterSpacing: -0.6, textAlign: 'center' }}>
|
|
41
|
+
{amount}
|
|
42
|
+
</ObiText>
|
|
43
|
+
) : null}
|
|
44
|
+
</View>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { View } from 'react-native';
|
|
3
|
+
import { useObiTheme } from '../../provider/ObiUIProvider';
|
|
4
|
+
import { ObiText } from '../../typography/ObiText';
|
|
5
|
+
|
|
6
|
+
export function RecordDetailLine({
|
|
7
|
+
title,
|
|
8
|
+
caption,
|
|
9
|
+
amount,
|
|
10
|
+
last,
|
|
11
|
+
}: {
|
|
12
|
+
title: string;
|
|
13
|
+
caption?: string;
|
|
14
|
+
amount?: string;
|
|
15
|
+
last?: boolean;
|
|
16
|
+
}) {
|
|
17
|
+
const theme = useObiTheme();
|
|
18
|
+
return (
|
|
19
|
+
<View
|
|
20
|
+
style={{
|
|
21
|
+
flexDirection: 'row',
|
|
22
|
+
alignItems: 'center',
|
|
23
|
+
gap: theme.spacing.md,
|
|
24
|
+
paddingVertical: theme.spacing.md,
|
|
25
|
+
borderBottomWidth: last ? 0 : 1,
|
|
26
|
+
borderBottomColor: theme.colors.border,
|
|
27
|
+
}}
|
|
28
|
+
>
|
|
29
|
+
<View style={{ flex: 1, gap: 2 }}>
|
|
30
|
+
<ObiText weight="700" numberOfLines={2}>{title}</ObiText>
|
|
31
|
+
{caption ? <ObiText variant="caption" muted>{caption}</ObiText> : null}
|
|
32
|
+
</View>
|
|
33
|
+
{amount ? <ObiText weight="800" style={{ textAlign: 'right' }}>{amount}</ObiText> : null}
|
|
34
|
+
</View>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { View } from 'react-native';
|
|
3
|
+
import { useObiTheme } from '../../provider/ObiUIProvider';
|
|
4
|
+
import { shadow } from '../../theme/effects';
|
|
5
|
+
import { ObiText } from '../../typography/ObiText';
|
|
6
|
+
|
|
7
|
+
export function RecordDetailSection({ title, children }: { title?: string; children: React.ReactNode }) {
|
|
8
|
+
const theme = useObiTheme();
|
|
9
|
+
return (
|
|
10
|
+
<View style={{ marginHorizontal: theme.spacing.lg, marginBottom: theme.spacing.md, gap: theme.spacing.sm }}>
|
|
11
|
+
{title ? (
|
|
12
|
+
<ObiText
|
|
13
|
+
variant="caption"
|
|
14
|
+
weight="700"
|
|
15
|
+
muted
|
|
16
|
+
style={{ letterSpacing: 0.9, textTransform: 'uppercase', paddingHorizontal: theme.spacing.xs }}
|
|
17
|
+
>
|
|
18
|
+
{title}
|
|
19
|
+
</ObiText>
|
|
20
|
+
) : null}
|
|
21
|
+
<View
|
|
22
|
+
style={{
|
|
23
|
+
backgroundColor: theme.glass.surfaceStrong,
|
|
24
|
+
borderRadius: theme.radius.lg,
|
|
25
|
+
borderWidth: 1,
|
|
26
|
+
borderColor: theme.glass.border,
|
|
27
|
+
paddingHorizontal: theme.spacing.lg,
|
|
28
|
+
overflow: 'hidden',
|
|
29
|
+
...shadow.xs,
|
|
30
|
+
}}
|
|
31
|
+
>
|
|
32
|
+
{children}
|
|
33
|
+
</View>
|
|
34
|
+
</View>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { RecordDetail, RecordDetailNote } from './RecordDetail';
|
|
2
|
+
export { RecordDetailActions, type RecordAction } from './RecordDetailActions';
|
|
3
|
+
export { RecordDetailHero } from './RecordDetailHero';
|
|
4
|
+
export { RecordDetailSection } from './RecordDetailSection';
|
|
5
|
+
export { RecordDetailField } from './RecordDetailField';
|
|
6
|
+
export { RecordDetailLine } from './RecordDetailLine';
|
|
7
|
+
export { toneFromStatus, prettyStatus } from './status';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { StatusTone } from '../StatusBadge';
|
|
2
|
+
|
|
3
|
+
export function toneFromStatus(status: string): StatusTone {
|
|
4
|
+
const value = (status ?? '').toLowerCase();
|
|
5
|
+
if (/pagad|aprob|activ|finaliz|acept|confirm|emitid/.test(value)) return 'success';
|
|
6
|
+
if (/pend|proces|borrad|cola/.test(value)) return 'warning';
|
|
7
|
+
if (/anul|inactiv|rechaz|vencid|error/.test(value)) return 'danger';
|
|
8
|
+
if (/convert/.test(value)) return 'primary';
|
|
9
|
+
return 'neutral';
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function prettyStatus(status: string) {
|
|
13
|
+
const trimmed = (status ?? '').trim();
|
|
14
|
+
if (!trimmed) return trimmed;
|
|
15
|
+
return trimmed.charAt(0).toUpperCase() + trimmed.slice(1);
|
|
16
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export type ObiCatalogEntry = {
|
|
2
|
+
name: string;
|
|
3
|
+
group: 'foundation' | 'forms' | 'lists' | 'metrics' | 'feedback' | 'scanner' | 'navigation';
|
|
4
|
+
description: string;
|
|
5
|
+
exportName: string;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
/** Public surface catalog for gallery / docs. Keep in sync with package exports. */
|
|
9
|
+
export const OBI_UI_CATALOG: ObiCatalogEntry[] = [
|
|
10
|
+
{ name: 'ObiUIProvider', group: 'foundation', description: 'Theme provider (light/dark/system)', exportName: 'ObiUIProvider' },
|
|
11
|
+
{ name: 'useObiStyles', group: 'foundation', description: 'Semantic utility styles reactive to theme', exportName: 'useObiStyles' },
|
|
12
|
+
{ name: 'Box / Stack / Row', group: 'foundation', description: 'Layout primitives', exportName: 'Box' },
|
|
13
|
+
{ name: 'Button', group: 'forms', description: 'Primary actions', exportName: 'Button' },
|
|
14
|
+
{ name: 'TextField', group: 'forms', description: 'Text input', exportName: 'TextField' },
|
|
15
|
+
{ name: 'FormWorkspaceShell', group: 'forms', description: 'CRUD form chrome with dock actions', exportName: 'FormWorkspaceShell' },
|
|
16
|
+
{ name: 'FormRow', group: 'forms', description: 'Multi-field form row', exportName: 'FormRow' },
|
|
17
|
+
{ name: 'ChoiceField', group: 'forms', description: 'Selectable options', exportName: 'ChoiceField' },
|
|
18
|
+
{ name: 'DateRangeFilter', group: 'forms', description: 'Date range + presets sheet', exportName: 'DateRangeFilter' },
|
|
19
|
+
{ name: 'ListWorkspace', group: 'lists', description: 'List screen chrome with query states + optional footer/FAB', exportName: 'ListWorkspace' },
|
|
20
|
+
{ name: 'QueryWorkspace', group: 'lists', description: 'Scroll/metrics query chrome with filter + QueryState + footer', exportName: 'QueryWorkspace' },
|
|
21
|
+
{ name: 'ListFilterBar', group: 'lists', description: 'Search + filter icon sheet', exportName: 'ListFilterBar' },
|
|
22
|
+
{ name: 'ListRow', group: 'lists', description: 'Swipeable list row', exportName: 'ListRow' },
|
|
23
|
+
{ name: 'MetricCard / MetricsGrid', group: 'metrics', description: 'KPI tiles in 2×2 grid', exportName: 'MetricsGrid' },
|
|
24
|
+
{ name: 'QueryState', group: 'feedback', description: 'Loading / error / empty orchestration', exportName: 'QueryState' },
|
|
25
|
+
{ name: 'LoadingState', group: 'feedback', description: 'Centered spinner', exportName: 'LoadingState' },
|
|
26
|
+
{ name: 'ErrorState', group: 'feedback', description: 'Error with retry', exportName: 'ErrorState' },
|
|
27
|
+
{ name: 'EmptyState', group: 'feedback', description: 'Empty placeholder with CTA', exportName: 'EmptyState' },
|
|
28
|
+
{ name: 'ScannerShell', group: 'scanner', description: 'Scanner chrome (camera slot in app)', exportName: 'ScannerShell' },
|
|
29
|
+
{ name: 'ScanInputBar', group: 'scanner', description: 'Manual barcode entry bar', exportName: 'ScanInputBar' },
|
|
30
|
+
{ name: 'NavigationDrawer', group: 'navigation', description: 'Presentational drawer menu', exportName: 'NavigationDrawer' },
|
|
31
|
+
];
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { StyleSheet, View } from 'react-native';
|
|
3
|
+
import LinearGradient from 'react-native-linear-gradient';
|
|
4
|
+
import { useObiTheme } from '../provider/ObiUIProvider';
|
|
5
|
+
|
|
6
|
+
export function AmbientBackground({ children }: { children?: React.ReactNode }) {
|
|
7
|
+
const theme = useObiTheme();
|
|
8
|
+
return (
|
|
9
|
+
<View style={[styles.root, { backgroundColor: theme.isDark ? '#0D1118' : '#EEF3FA' }]}>
|
|
10
|
+
<LinearGradient
|
|
11
|
+
pointerEvents="none"
|
|
12
|
+
colors={theme.isDark ? ['#0D1118', '#121A26', '#111522'] : ['#EDF3FB', '#F4F3FF', '#EEF8F6']}
|
|
13
|
+
start={{ x: 0.05, y: 0 }}
|
|
14
|
+
end={{ x: 0.95, y: 1 }}
|
|
15
|
+
style={StyleSheet.absoluteFill}
|
|
16
|
+
/>
|
|
17
|
+
<View pointerEvents="none" style={[styles.glow, styles.glowPrimary, { backgroundColor: theme.isDark ? 'rgba(123,124,240,0.16)' : 'rgba(91,92,230,0.14)' }]} />
|
|
18
|
+
<View pointerEvents="none" style={[styles.glow, styles.glowMint, { backgroundColor: theme.isDark ? 'rgba(45,212,191,0.12)' : 'rgba(14,165,150,0.11)' }]} />
|
|
19
|
+
{children}
|
|
20
|
+
</View>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const styles = StyleSheet.create({
|
|
25
|
+
root: { flex: 1 },
|
|
26
|
+
glow: { position: 'absolute', borderRadius: 999 },
|
|
27
|
+
glowPrimary: { width: 340, height: 340, top: -120, right: -120 },
|
|
28
|
+
glowMint: { width: 300, height: 300, bottom: 80, left: -150 },
|
|
29
|
+
});
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Platform, StyleSheet, View, type StyleProp, type ViewStyle } from 'react-native';
|
|
3
|
+
import { BlurView, LiquidGlassView } from '@sbaiahmed1/react-native-blur';
|
|
4
|
+
import { useObiTheme } from '../provider/ObiUIProvider';
|
|
5
|
+
import { liquidGlassPresets } from '../theme/effects';
|
|
6
|
+
|
|
7
|
+
type Variant = keyof typeof liquidGlassPresets;
|
|
8
|
+
|
|
9
|
+
type Props = {
|
|
10
|
+
variant?: Variant;
|
|
11
|
+
interactive?: boolean;
|
|
12
|
+
style?: StyleProp<ViewStyle>;
|
|
13
|
+
children?: React.ReactNode;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
function AndroidFrost({ variant, style, children }: Required<Pick<Props, 'variant'>> & Pick<Props, 'style' | 'children'>) {
|
|
17
|
+
const theme = useObiTheme();
|
|
18
|
+
const preset = liquidGlassPresets[variant];
|
|
19
|
+
const overlay = theme.isDark
|
|
20
|
+
? 'rgba(22,27,38,0.30)'
|
|
21
|
+
: variant === 'dock' || variant === 'chrome'
|
|
22
|
+
? 'rgba(255,255,255,0.14)'
|
|
23
|
+
: variant === 'sheet' || variant === 'modal'
|
|
24
|
+
? 'rgba(255,255,255,0.26)'
|
|
25
|
+
: 'rgba(255,255,255,0.18)';
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<BlurView
|
|
29
|
+
blurType={theme.isDark ? 'dark' : 'xlight'}
|
|
30
|
+
blurAmount={Math.min(preset.blurAmount, 24)}
|
|
31
|
+
blurRounds={3}
|
|
32
|
+
overlayColor={overlay}
|
|
33
|
+
reducedTransparencyFallbackColor={theme.isDark ? 'rgba(25,30,42,0.78)' : 'rgba(255,255,255,0.72)'}
|
|
34
|
+
style={[styles.clip, { borderColor: theme.glass.borderSoft }, style]}
|
|
35
|
+
>
|
|
36
|
+
{children}
|
|
37
|
+
</BlurView>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function GlassSurface({ variant = 'surface', interactive = false, style, children }: Props) {
|
|
42
|
+
const theme = useObiTheme();
|
|
43
|
+
const preset = liquidGlassPresets[variant];
|
|
44
|
+
|
|
45
|
+
if (Platform.OS === 'android') {
|
|
46
|
+
return <AndroidFrost variant={variant} style={style}>{children}</AndroidFrost>;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<LiquidGlassView
|
|
51
|
+
glassType={preset.glassType}
|
|
52
|
+
glassTintColor={theme.isDark ? '#171C27' : '#FFFFFF'}
|
|
53
|
+
glassOpacity={preset.glassOpacity}
|
|
54
|
+
isInteractive={interactive}
|
|
55
|
+
reducedTransparencyFallbackColor={theme.isDark ? 'rgba(25,30,42,0.72)' : 'rgba(255,255,255,0.72)'}
|
|
56
|
+
style={[styles.clip, { borderColor: theme.glass.borderSoft }, style]}
|
|
57
|
+
>
|
|
58
|
+
{children}
|
|
59
|
+
</LiquidGlassView>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function FrostedSurface({ variant = 'chrome', style, children }: Props) {
|
|
64
|
+
const theme = useObiTheme();
|
|
65
|
+
if (Platform.OS === 'android') {
|
|
66
|
+
return <AndroidFrost variant={variant} style={[{ borderColor: 'transparent' }, style]}>{children}</AndroidFrost>;
|
|
67
|
+
}
|
|
68
|
+
const preset = liquidGlassPresets[variant];
|
|
69
|
+
return (
|
|
70
|
+
<BlurView
|
|
71
|
+
blurType={theme.isDark ? 'systemMaterialDark' : 'systemMaterialLight'}
|
|
72
|
+
blurAmount={preset.blurAmount}
|
|
73
|
+
overlayColor={theme.isDark ? 'rgba(20,25,36,0.12)' : 'rgba(255,255,255,0.16)'}
|
|
74
|
+
reducedTransparencyFallbackColor={theme.isDark ? 'rgba(25,30,42,0.72)' : 'rgba(255,255,255,0.72)'}
|
|
75
|
+
style={[styles.clip, { borderColor: 'transparent' }, style]}
|
|
76
|
+
>
|
|
77
|
+
{children}
|
|
78
|
+
</BlurView>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function SoftGlassPanel({ style, children, dense = false }: Props & { dense?: boolean }) {
|
|
83
|
+
const theme = useObiTheme();
|
|
84
|
+
return (
|
|
85
|
+
<View
|
|
86
|
+
style={[
|
|
87
|
+
styles.clip,
|
|
88
|
+
{
|
|
89
|
+
borderColor: theme.glass.borderSoft,
|
|
90
|
+
backgroundColor: theme.isDark
|
|
91
|
+
? dense ? 'rgba(22,27,38,0.62)' : 'rgba(22,27,38,0.45)'
|
|
92
|
+
: dense ? 'rgba(255,255,255,0.38)' : 'rgba(255,255,255,0.24)',
|
|
93
|
+
},
|
|
94
|
+
style,
|
|
95
|
+
]}
|
|
96
|
+
>
|
|
97
|
+
{children}
|
|
98
|
+
</View>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const styles = StyleSheet.create({
|
|
103
|
+
clip: {
|
|
104
|
+
overflow: 'hidden',
|
|
105
|
+
borderWidth: StyleSheet.hairlineWidth,
|
|
106
|
+
},
|
|
107
|
+
});
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
export { ObiUIProvider, useObiUI, useObiTheme, type ObiThemePreference } from './provider/ObiUIProvider';
|
|
2
|
+
export { createObiTheme, type ObiTheme, type ObiThemeMode, type ObiSpacingKey, type ObiRadiusKey, type ObiThemeOverrides } from './theme/theme';
|
|
3
|
+
export { lightColors, darkColors, radius, spacing, type, type AppColors } from './theme/tokens';
|
|
4
|
+
export { createGlass, liquidGlassPresets, shadow } from './theme/effects';
|
|
5
|
+
|
|
6
|
+
export { Box, type BoxProps } from './layout/Box';
|
|
7
|
+
export { Stack } from './layout/Stack';
|
|
8
|
+
export { Row } from './layout/Row';
|
|
9
|
+
export { Divider } from './layout/Divider';
|
|
10
|
+
|
|
11
|
+
export { ObiText, AppText, type ObiTextProps } from './typography/ObiText';
|
|
12
|
+
export { Button, type ButtonProps } from './components/Button';
|
|
13
|
+
export { Card } from './components/Card';
|
|
14
|
+
export { TextField } from './components/TextField';
|
|
15
|
+
export { Screen } from './components/Screen';
|
|
16
|
+
|
|
17
|
+
export { GlassSurface, FrostedSurface, SoftGlassPanel } from './glass/GlassSurface';
|
|
18
|
+
export { AmbientBackground } from './glass/AmbientBackground';
|
|
19
|
+
|
|
20
|
+
export { Chip } from './components/Chip';
|
|
21
|
+
export { SegmentedControl, type SegmentedOption } from './components/SegmentedControl';
|
|
22
|
+
export { Skeleton } from './components/Skeleton';
|
|
23
|
+
export { ToggleRow } from './components/ToggleRow';
|
|
24
|
+
export { StatusBadge, type StatusTone } from './components/StatusBadge';
|
|
25
|
+
export { Fab } from './components/Fab';
|
|
26
|
+
export { BottomSheet } from './components/BottomSheet';
|
|
27
|
+
export { ChoiceField, type ChoiceOption } from './components/ChoiceField';
|
|
28
|
+
|
|
29
|
+
export { Icon, type IconName, type IconProps } from './components/Icon';
|
|
30
|
+
export { IconButton } from './components/IconButton';
|
|
31
|
+
export { SearchArea } from './components/SearchArea';
|
|
32
|
+
export { ListFilterBar, type ListFilterBarProps, type FilterOption } from './components/ListFilterBar';
|
|
33
|
+
export { ListRow, type ListRowProps, type SwipeAction, type SwipeActionTone } from './components/ListRow';
|
|
34
|
+
export { FormWorkspaceShell, FormBlock, type FormWorkspaceShellProps, type FormBlockProps } from './components/FormWorkspaceShell';
|
|
35
|
+
export { AppHeader, type AppHeaderProps } from './components/AppHeader';
|
|
36
|
+
export { GlassTabBarBackground, glassTabBarBaseStyle } from './components/GlassTabBar';
|
|
37
|
+
export { MetricCard, type MetricCardProps, type MetricFormat } from './components/MetricCard';
|
|
38
|
+
export { MetricsGrid } from './components/MetricsGrid';
|
|
39
|
+
export { MiniBarChart, type MiniBarChartItem } from './components/MiniBarChart';
|
|
40
|
+
export { EmptyState } from './components/EmptyState';
|
|
41
|
+
export {
|
|
42
|
+
RecordDetail,
|
|
43
|
+
RecordDetailNote,
|
|
44
|
+
RecordDetailActions,
|
|
45
|
+
RecordDetailHero,
|
|
46
|
+
RecordDetailSection,
|
|
47
|
+
RecordDetailField,
|
|
48
|
+
RecordDetailLine,
|
|
49
|
+
toneFromStatus,
|
|
50
|
+
prettyStatus,
|
|
51
|
+
type RecordAction,
|
|
52
|
+
} from './components/record-detail';
|
|
53
|
+
export { NavigationDrawer, type NavigationDrawerProps, type NavigationDrawerEntry, type NavigationDrawerLeaf, type NavigationDrawerGroup, type NavigationDrawerDivider } from './components/NavigationDrawer';
|
|
54
|
+
export { SuccessState } from './components/SuccessState';
|
|
55
|
+
export { LoadingState, type LoadingStateProps } from './components/LoadingState';
|
|
56
|
+
export { ErrorState, type ErrorStateProps } from './components/ErrorState';
|
|
57
|
+
export { QueryState, type QueryStateProps } from './components/QueryState';
|
|
58
|
+
export { ListWorkspace, type ListWorkspaceProps } from './components/ListWorkspace';
|
|
59
|
+
export { QueryWorkspace, type QueryWorkspaceProps } from './components/QueryWorkspace';
|
|
60
|
+
export { FormRow, type FormRowProps } from './components/FormRow';
|
|
61
|
+
export { createObiStyles, useObiStyles, type ObiStyles } from './styles/useObiStyles';
|
|
62
|
+
export { DateRangeFilter, defaultDateRange, type DateRangeValue, type DatePreset, type DateFilterOption, type DateRangeFilterProps } from './components/DateRangeFilter';
|
|
63
|
+
export { CenteredState, type CenteredStateProps } from './components/CenteredState';
|
|
64
|
+
export { ScanInputBar, type ScanInputBarProps } from './components/ScanInputBar';
|
|
65
|
+
export { ScannerShell, type ScannerShellProps } from './components/ScannerShell';
|
|
66
|
+
export { OBI_UI_CATALOG, type ObiCatalogEntry } from './gallery/catalog';
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { View, type StyleProp, type ViewProps, type ViewStyle } from 'react-native';
|
|
3
|
+
import { useObiTheme } from '../provider/ObiUIProvider';
|
|
4
|
+
import type { ObiRadiusKey, ObiSpacingKey } from '../theme/theme';
|
|
5
|
+
|
|
6
|
+
type Space = ObiSpacingKey | number;
|
|
7
|
+
|
|
8
|
+
function resolveSpace(value: Space | undefined, values: Record<string, number>) {
|
|
9
|
+
if (value == null) return undefined;
|
|
10
|
+
return typeof value === 'number' ? value : values[value];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type BoxProps = ViewProps & {
|
|
14
|
+
padding?: Space;
|
|
15
|
+
paddingX?: Space;
|
|
16
|
+
paddingY?: Space;
|
|
17
|
+
margin?: Space;
|
|
18
|
+
gap?: Space;
|
|
19
|
+
radius?: ObiRadiusKey | number;
|
|
20
|
+
flex?: number;
|
|
21
|
+
backgroundColor?: string;
|
|
22
|
+
style?: StyleProp<ViewStyle>;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export function Box({
|
|
26
|
+
padding,
|
|
27
|
+
paddingX,
|
|
28
|
+
paddingY,
|
|
29
|
+
margin,
|
|
30
|
+
gap,
|
|
31
|
+
radius,
|
|
32
|
+
flex,
|
|
33
|
+
backgroundColor,
|
|
34
|
+
style,
|
|
35
|
+
...props
|
|
36
|
+
}: BoxProps) {
|
|
37
|
+
const theme = useObiTheme();
|
|
38
|
+
const resolvedRadius = typeof radius === 'number'
|
|
39
|
+
? radius
|
|
40
|
+
: radius
|
|
41
|
+
? theme.radius[radius]
|
|
42
|
+
: undefined;
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<View
|
|
46
|
+
{...props}
|
|
47
|
+
style={[
|
|
48
|
+
{
|
|
49
|
+
padding: resolveSpace(padding, theme.spacing),
|
|
50
|
+
paddingHorizontal: resolveSpace(paddingX, theme.spacing),
|
|
51
|
+
paddingVertical: resolveSpace(paddingY, theme.spacing),
|
|
52
|
+
margin: resolveSpace(margin, theme.spacing),
|
|
53
|
+
gap: resolveSpace(gap, theme.spacing),
|
|
54
|
+
borderRadius: resolvedRadius,
|
|
55
|
+
flex,
|
|
56
|
+
backgroundColor,
|
|
57
|
+
},
|
|
58
|
+
style,
|
|
59
|
+
]}
|
|
60
|
+
/>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { View, type StyleProp, type ViewStyle } from 'react-native';
|
|
3
|
+
import { useObiTheme } from '../provider/ObiUIProvider';
|
|
4
|
+
|
|
5
|
+
export function Divider({ style }: { style?: StyleProp<ViewStyle> }) {
|
|
6
|
+
const { colors } = useObiTheme();
|
|
7
|
+
return <View style={[{ height: 1, backgroundColor: colors.separator }, style]} />;
|
|
8
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { ViewStyle } from 'react-native';
|
|
3
|
+
import { Box, type BoxProps } from './Box';
|
|
4
|
+
|
|
5
|
+
export function Row({ style, ...props }: BoxProps) {
|
|
6
|
+
return <Box {...props} style={[{ flexDirection: 'row', alignItems: 'center' } satisfies ViewStyle, style]} />;
|
|
7
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { ViewStyle } from 'react-native';
|
|
3
|
+
import { Box, type BoxProps } from './Box';
|
|
4
|
+
|
|
5
|
+
export function Stack({ style, ...props }: BoxProps) {
|
|
6
|
+
return <Box {...props} style={[{ flexDirection: 'column' } satisfies ViewStyle, style]} />;
|
|
7
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import React, { createContext, useContext, useMemo } from 'react';
|
|
2
|
+
import { useColorScheme } from 'react-native';
|
|
3
|
+
import { createObiTheme, type ObiTheme, type ObiThemeMode, type ObiThemeOverrides } from '../theme/theme';
|
|
4
|
+
|
|
5
|
+
export type ObiThemePreference = ObiThemeMode | 'system';
|
|
6
|
+
|
|
7
|
+
type ObiUIContextValue = {
|
|
8
|
+
theme: ObiTheme;
|
|
9
|
+
mode: ObiThemeMode;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const ObiUIContext = createContext<ObiUIContextValue | null>(null);
|
|
13
|
+
|
|
14
|
+
export function ObiUIProvider({
|
|
15
|
+
children,
|
|
16
|
+
mode = 'system',
|
|
17
|
+
overrides,
|
|
18
|
+
}: {
|
|
19
|
+
children: React.ReactNode;
|
|
20
|
+
mode?: ObiThemePreference;
|
|
21
|
+
overrides?: ObiThemeOverrides;
|
|
22
|
+
}) {
|
|
23
|
+
const systemMode = useColorScheme();
|
|
24
|
+
const resolvedMode: ObiThemeMode =
|
|
25
|
+
mode === 'system' ? (systemMode === 'dark' ? 'dark' : 'light') : mode;
|
|
26
|
+
|
|
27
|
+
const value = useMemo<ObiUIContextValue>(() => ({
|
|
28
|
+
mode: resolvedMode,
|
|
29
|
+
theme: createObiTheme(resolvedMode, overrides),
|
|
30
|
+
}), [resolvedMode, overrides]);
|
|
31
|
+
|
|
32
|
+
return <ObiUIContext.Provider value={value}>{children}</ObiUIContext.Provider>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function useObiUI() {
|
|
36
|
+
const value = useContext(ObiUIContext);
|
|
37
|
+
if (!value) {
|
|
38
|
+
throw new Error('useObiUI debe utilizarse dentro de <ObiUIProvider>.');
|
|
39
|
+
}
|
|
40
|
+
return value;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function useObiTheme() {
|
|
44
|
+
return useObiUI().theme;
|
|
45
|
+
}
|