@native-mate/core 0.1.0 → 0.1.1
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 +102 -0
- package/dist/primitives/Spinner/Spinner.js +2 -2
- package/dist/primitives/Text/Text.js +2 -2
- package/package.json +31 -31
- package/src/__tests__/makeStyles.test.ts +56 -56
- package/src/__tests__/perf.test.ts +46 -46
- package/src/__tests__/platform.test.ts +34 -34
- package/src/__tests__/theme.test.ts +58 -58
- package/src/__tests__/tokens.test.ts +105 -105
- package/src/index.ts +27 -27
- package/src/primitives/Icon/Icon.tsx +11 -11
- package/src/primitives/Icon/Icon.types.ts +7 -7
- package/src/primitives/Separator/Separator.tsx +22 -22
- package/src/primitives/Separator/Separator.types.ts +6 -6
- package/src/primitives/Spinner/Spinner.tsx +50 -50
- package/src/primitives/Spinner/Spinner.types.ts +4 -4
- package/src/primitives/Text/Text.tsx +45 -45
- package/src/primitives/Text/Text.types.ts +15 -15
- package/src/theme/ThemeContext.ts +6 -6
- package/src/theme/ThemeProvider.tsx +27 -27
- package/src/theme/makeStyles.ts +15 -15
- package/src/theme/useTheme.ts +7 -7
- package/src/tokens/index.ts +29 -29
- package/src/tokens/presets/midnight.ts +24 -24
- package/src/tokens/presets/rose.ts +24 -24
- package/src/tokens/presets/slate.ts +24 -24
- package/src/tokens/presets/zinc.ts +37 -37
- package/src/tokens/types.ts +72 -72
- package/src/utils/platform.ts +20 -20
- package/src/utils/useBreakpoint.ts +10 -10
|
@@ -1,105 +1,105 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest'
|
|
2
|
-
import { resolveTokens, zinc, slate, rose, midnight, presets } from '../tokens'
|
|
3
|
-
|
|
4
|
-
describe('resolveTokens', () => {
|
|
5
|
-
it('resolves zinc dark mode correctly', () => {
|
|
6
|
-
const theme = resolveTokens(zinc, 'dark')
|
|
7
|
-
expect(theme.colors.background).toBe('#070709')
|
|
8
|
-
expect(theme.colors.foreground).toBe('#fafafa')
|
|
9
|
-
expect(theme.colorScheme).toBe('dark')
|
|
10
|
-
})
|
|
11
|
-
|
|
12
|
-
it('resolves zinc light mode correctly', () => {
|
|
13
|
-
const theme = resolveTokens(zinc, 'light')
|
|
14
|
-
expect(theme.colors.background).toBe('#ffffff')
|
|
15
|
-
expect(theme.colors.foreground).toBe('#09090b')
|
|
16
|
-
expect(theme.colorScheme).toBe('light')
|
|
17
|
-
})
|
|
18
|
-
|
|
19
|
-
it('returns non-color tokens unchanged', () => {
|
|
20
|
-
const theme = resolveTokens(zinc, 'dark')
|
|
21
|
-
expect(theme.spacing.lg).toBe(16)
|
|
22
|
-
expect(theme.spacing.xl).toBe(24)
|
|
23
|
-
expect(theme.radius.md).toBe(10)
|
|
24
|
-
expect(theme.typography.size.md).toBe(15)
|
|
25
|
-
expect(theme.typography.weight.bold).toBe('700')
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
it('lineHeight values are absolute pixels, not multipliers', () => {
|
|
29
|
-
const theme = resolveTokens(zinc, 'dark')
|
|
30
|
-
expect(theme.typography.lineHeight.tight).toBe(18)
|
|
31
|
-
expect(theme.typography.lineHeight.normal).toBe(22)
|
|
32
|
-
expect(theme.typography.lineHeight.relaxed).toBe(28)
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
it('applies color overrides', () => {
|
|
36
|
-
const theme = resolveTokens(zinc, 'dark', {
|
|
37
|
-
colors: { primary: '#6366f1' },
|
|
38
|
-
})
|
|
39
|
-
expect(theme.colors.primary).toBe('#6366f1')
|
|
40
|
-
expect(theme.colors.background).toBe('#070709') // unchanged
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
it('applies spacing overrides', () => {
|
|
44
|
-
const theme = resolveTokens(zinc, 'dark', {
|
|
45
|
-
spacing: { lg: 20 },
|
|
46
|
-
})
|
|
47
|
-
expect(theme.spacing.lg).toBe(20)
|
|
48
|
-
expect(theme.spacing.sm).toBe(8) // unchanged
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
it('applies radius overrides', () => {
|
|
52
|
-
const theme = resolveTokens(zinc, 'dark', {
|
|
53
|
-
radius: { md: 8 },
|
|
54
|
-
})
|
|
55
|
-
expect(theme.radius.md).toBe(8)
|
|
56
|
-
expect(theme.radius.full).toBe(9999) // unchanged
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
it('applies animation speed overrides', () => {
|
|
60
|
-
const theme = resolveTokens(zinc, 'dark', {
|
|
61
|
-
animation: { speed: { fast: 100 } },
|
|
62
|
-
})
|
|
63
|
-
expect(theme.animation.speed.fast).toBe(100)
|
|
64
|
-
expect(theme.animation.speed.normal).toBe(250) // unchanged
|
|
65
|
-
})
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
describe('presets', () => {
|
|
69
|
-
it('exports all 4 presets', () => {
|
|
70
|
-
expect(presets).toHaveProperty('zinc')
|
|
71
|
-
expect(presets).toHaveProperty('slate')
|
|
72
|
-
expect(presets).toHaveProperty('rose')
|
|
73
|
-
expect(presets).toHaveProperty('midnight')
|
|
74
|
-
})
|
|
75
|
-
|
|
76
|
-
it('slate dark background differs from zinc', () => {
|
|
77
|
-
const zincTheme = resolveTokens(zinc, 'dark')
|
|
78
|
-
const slateTheme = resolveTokens(slate, 'dark')
|
|
79
|
-
expect(slateTheme.colors.background).not.toBe(zincTheme.colors.background)
|
|
80
|
-
})
|
|
81
|
-
|
|
82
|
-
it('rose primary differs from zinc primary in dark mode', () => {
|
|
83
|
-
const zincTheme = resolveTokens(zinc, 'dark')
|
|
84
|
-
const roseTheme = resolveTokens(rose, 'dark')
|
|
85
|
-
expect(roseTheme.colors.primary).not.toBe(zincTheme.colors.primary)
|
|
86
|
-
})
|
|
87
|
-
|
|
88
|
-
it('all presets share same spacing, radius, and typography', () => {
|
|
89
|
-
const zincTheme = resolveTokens(zinc, 'dark')
|
|
90
|
-
const midnightTheme = resolveTokens(midnight, 'dark')
|
|
91
|
-
expect(midnightTheme.spacing).toEqual(zincTheme.spacing)
|
|
92
|
-
expect(midnightTheme.radius).toEqual(zincTheme.radius)
|
|
93
|
-
expect(midnightTheme.typography).toEqual(zincTheme.typography)
|
|
94
|
-
})
|
|
95
|
-
|
|
96
|
-
it('every preset color token resolves to a hex string', () => {
|
|
97
|
-
for (const preset of Object.values(presets)) {
|
|
98
|
-
const theme = resolveTokens(preset, 'dark')
|
|
99
|
-
for (const [key, value] of Object.entries(theme.colors)) {
|
|
100
|
-
expect(typeof value).toBe('string')
|
|
101
|
-
expect(value.startsWith('#')).toBe(true)
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
})
|
|
105
|
-
})
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { resolveTokens, zinc, slate, rose, midnight, presets } from '../tokens'
|
|
3
|
+
|
|
4
|
+
describe('resolveTokens', () => {
|
|
5
|
+
it('resolves zinc dark mode correctly', () => {
|
|
6
|
+
const theme = resolveTokens(zinc, 'dark')
|
|
7
|
+
expect(theme.colors.background).toBe('#070709')
|
|
8
|
+
expect(theme.colors.foreground).toBe('#fafafa')
|
|
9
|
+
expect(theme.colorScheme).toBe('dark')
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
it('resolves zinc light mode correctly', () => {
|
|
13
|
+
const theme = resolveTokens(zinc, 'light')
|
|
14
|
+
expect(theme.colors.background).toBe('#ffffff')
|
|
15
|
+
expect(theme.colors.foreground).toBe('#09090b')
|
|
16
|
+
expect(theme.colorScheme).toBe('light')
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('returns non-color tokens unchanged', () => {
|
|
20
|
+
const theme = resolveTokens(zinc, 'dark')
|
|
21
|
+
expect(theme.spacing.lg).toBe(16)
|
|
22
|
+
expect(theme.spacing.xl).toBe(24)
|
|
23
|
+
expect(theme.radius.md).toBe(10)
|
|
24
|
+
expect(theme.typography.size.md).toBe(15)
|
|
25
|
+
expect(theme.typography.weight.bold).toBe('700')
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('lineHeight values are absolute pixels, not multipliers', () => {
|
|
29
|
+
const theme = resolveTokens(zinc, 'dark')
|
|
30
|
+
expect(theme.typography.lineHeight.tight).toBe(18)
|
|
31
|
+
expect(theme.typography.lineHeight.normal).toBe(22)
|
|
32
|
+
expect(theme.typography.lineHeight.relaxed).toBe(28)
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('applies color overrides', () => {
|
|
36
|
+
const theme = resolveTokens(zinc, 'dark', {
|
|
37
|
+
colors: { primary: '#6366f1' },
|
|
38
|
+
})
|
|
39
|
+
expect(theme.colors.primary).toBe('#6366f1')
|
|
40
|
+
expect(theme.colors.background).toBe('#070709') // unchanged
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('applies spacing overrides', () => {
|
|
44
|
+
const theme = resolveTokens(zinc, 'dark', {
|
|
45
|
+
spacing: { lg: 20 },
|
|
46
|
+
})
|
|
47
|
+
expect(theme.spacing.lg).toBe(20)
|
|
48
|
+
expect(theme.spacing.sm).toBe(8) // unchanged
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('applies radius overrides', () => {
|
|
52
|
+
const theme = resolveTokens(zinc, 'dark', {
|
|
53
|
+
radius: { md: 8 },
|
|
54
|
+
})
|
|
55
|
+
expect(theme.radius.md).toBe(8)
|
|
56
|
+
expect(theme.radius.full).toBe(9999) // unchanged
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it('applies animation speed overrides', () => {
|
|
60
|
+
const theme = resolveTokens(zinc, 'dark', {
|
|
61
|
+
animation: { speed: { fast: 100 } },
|
|
62
|
+
})
|
|
63
|
+
expect(theme.animation.speed.fast).toBe(100)
|
|
64
|
+
expect(theme.animation.speed.normal).toBe(250) // unchanged
|
|
65
|
+
})
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
describe('presets', () => {
|
|
69
|
+
it('exports all 4 presets', () => {
|
|
70
|
+
expect(presets).toHaveProperty('zinc')
|
|
71
|
+
expect(presets).toHaveProperty('slate')
|
|
72
|
+
expect(presets).toHaveProperty('rose')
|
|
73
|
+
expect(presets).toHaveProperty('midnight')
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it('slate dark background differs from zinc', () => {
|
|
77
|
+
const zincTheme = resolveTokens(zinc, 'dark')
|
|
78
|
+
const slateTheme = resolveTokens(slate, 'dark')
|
|
79
|
+
expect(slateTheme.colors.background).not.toBe(zincTheme.colors.background)
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
it('rose primary differs from zinc primary in dark mode', () => {
|
|
83
|
+
const zincTheme = resolveTokens(zinc, 'dark')
|
|
84
|
+
const roseTheme = resolveTokens(rose, 'dark')
|
|
85
|
+
expect(roseTheme.colors.primary).not.toBe(zincTheme.colors.primary)
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
it('all presets share same spacing, radius, and typography', () => {
|
|
89
|
+
const zincTheme = resolveTokens(zinc, 'dark')
|
|
90
|
+
const midnightTheme = resolveTokens(midnight, 'dark')
|
|
91
|
+
expect(midnightTheme.spacing).toEqual(zincTheme.spacing)
|
|
92
|
+
expect(midnightTheme.radius).toEqual(zincTheme.radius)
|
|
93
|
+
expect(midnightTheme.typography).toEqual(zincTheme.typography)
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it('every preset color token resolves to a hex string', () => {
|
|
97
|
+
for (const preset of Object.values(presets)) {
|
|
98
|
+
const theme = resolveTokens(preset, 'dark')
|
|
99
|
+
for (const [key, value] of Object.entries(theme.colors)) {
|
|
100
|
+
expect(typeof value).toBe('string')
|
|
101
|
+
expect(value.startsWith('#')).toBe(true)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
})
|
|
105
|
+
})
|
package/src/index.ts
CHANGED
|
@@ -1,27 +1,27 @@
|
|
|
1
|
-
// Theme
|
|
2
|
-
export { ThemeProvider } from './theme/ThemeProvider'
|
|
3
|
-
export { useTheme } from './theme/useTheme'
|
|
4
|
-
export { makeStyles } from './theme/makeStyles'
|
|
5
|
-
|
|
6
|
-
// Tokens
|
|
7
|
-
export { presets, resolveTokens, zinc, slate, rose, midnight } from './tokens'
|
|
8
|
-
export type {
|
|
9
|
-
TokenSet, ResolvedTheme, ThemePreset,
|
|
10
|
-
NativeMateConfig, NativeMateTokenOverrides,
|
|
11
|
-
TokenColors, ColorToken, ResolvedColors,
|
|
12
|
-
} from './tokens/types'
|
|
13
|
-
|
|
14
|
-
// Primitives
|
|
15
|
-
export { Text } from './primitives/Text/Text'
|
|
16
|
-
export { Icon } from './primitives/Icon/Icon'
|
|
17
|
-
export { Spinner } from './primitives/Spinner/Spinner'
|
|
18
|
-
export { Separator } from './primitives/Separator/Separator'
|
|
19
|
-
export type { TextProps, TextVariant, TextSize, TextWeight } from './primitives/Text/Text.types'
|
|
20
|
-
export type { IconProps } from './primitives/Icon/Icon.types'
|
|
21
|
-
export type { SpinnerProps } from './primitives/Spinner/Spinner.types'
|
|
22
|
-
export type { SeparatorProps } from './primitives/Separator/Separator.types'
|
|
23
|
-
|
|
24
|
-
// Utils
|
|
25
|
-
export { shadow } from './utils/platform'
|
|
26
|
-
export { useBreakpoint } from './utils/useBreakpoint'
|
|
27
|
-
export type { Breakpoint } from './utils/useBreakpoint'
|
|
1
|
+
// Theme
|
|
2
|
+
export { ThemeProvider } from './theme/ThemeProvider'
|
|
3
|
+
export { useTheme } from './theme/useTheme'
|
|
4
|
+
export { makeStyles } from './theme/makeStyles'
|
|
5
|
+
|
|
6
|
+
// Tokens
|
|
7
|
+
export { presets, resolveTokens, zinc, slate, rose, midnight } from './tokens'
|
|
8
|
+
export type {
|
|
9
|
+
TokenSet, ResolvedTheme, ThemePreset,
|
|
10
|
+
NativeMateConfig, NativeMateTokenOverrides,
|
|
11
|
+
TokenColors, ColorToken, ResolvedColors,
|
|
12
|
+
} from './tokens/types'
|
|
13
|
+
|
|
14
|
+
// Primitives
|
|
15
|
+
export { Text } from './primitives/Text/Text'
|
|
16
|
+
export { Icon } from './primitives/Icon/Icon'
|
|
17
|
+
export { Spinner } from './primitives/Spinner/Spinner'
|
|
18
|
+
export { Separator } from './primitives/Separator/Separator'
|
|
19
|
+
export type { TextProps, TextVariant, TextSize, TextWeight } from './primitives/Text/Text.types'
|
|
20
|
+
export type { IconProps } from './primitives/Icon/Icon.types'
|
|
21
|
+
export type { SpinnerProps } from './primitives/Spinner/Spinner.types'
|
|
22
|
+
export type { SeparatorProps } from './primitives/Separator/Separator.types'
|
|
23
|
+
|
|
24
|
+
// Utils
|
|
25
|
+
export { shadow } from './utils/platform'
|
|
26
|
+
export { useBreakpoint } from './utils/useBreakpoint'
|
|
27
|
+
export type { Breakpoint } from './utils/useBreakpoint'
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import { useTheme } from '../../theme/useTheme'
|
|
3
|
-
import type { IconProps } from './Icon.types'
|
|
4
|
-
|
|
5
|
-
const sizePx = { xs: 14, sm: 16, md: 20, lg: 24, xl: 32 }
|
|
6
|
-
|
|
7
|
-
export const Icon: React.FC<IconProps> = ({ as: IconComponent, name, size = 'md', color, ...rest }) => {
|
|
8
|
-
const theme = useTheme()
|
|
9
|
-
if (!IconComponent) return null
|
|
10
|
-
return <IconComponent name={name} size={sizePx[size]} color={color ?? theme.colors.foreground} {...rest} />
|
|
11
|
-
}
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { useTheme } from '../../theme/useTheme'
|
|
3
|
+
import type { IconProps } from './Icon.types'
|
|
4
|
+
|
|
5
|
+
const sizePx = { xs: 14, sm: 16, md: 20, lg: 24, xl: 32 }
|
|
6
|
+
|
|
7
|
+
export const Icon: React.FC<IconProps> = ({ as: IconComponent, name, size = 'md', color, ...rest }) => {
|
|
8
|
+
const theme = useTheme()
|
|
9
|
+
if (!IconComponent) return null
|
|
10
|
+
return <IconComponent name={name} size={sizePx[size]} color={color ?? theme.colors.foreground} {...rest} />
|
|
11
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
export interface IconProps {
|
|
2
|
-
as?: React.ComponentType<any>
|
|
3
|
-
name?: string
|
|
4
|
-
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl'
|
|
5
|
-
color?: string
|
|
6
|
-
[key: string]: any
|
|
7
|
-
}
|
|
1
|
+
export interface IconProps {
|
|
2
|
+
as?: React.ComponentType<any>
|
|
3
|
+
name?: string
|
|
4
|
+
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl'
|
|
5
|
+
color?: string
|
|
6
|
+
[key: string]: any
|
|
7
|
+
}
|
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import { View } from 'react-native'
|
|
3
|
-
import { useTheme } from '../../theme/useTheme'
|
|
4
|
-
import type { SeparatorProps } from './Separator.types'
|
|
5
|
-
|
|
6
|
-
export const Separator: React.FC<SeparatorProps> = ({ orientation = 'horizontal', style }) => {
|
|
7
|
-
const theme = useTheme()
|
|
8
|
-
return (
|
|
9
|
-
<View
|
|
10
|
-
accessible={false}
|
|
11
|
-
style={[
|
|
12
|
-
{
|
|
13
|
-
backgroundColor: theme.colors.border,
|
|
14
|
-
...(orientation === 'horizontal'
|
|
15
|
-
? { height: 1, width: '100%' }
|
|
16
|
-
: { width: 1, height: '100%' }),
|
|
17
|
-
},
|
|
18
|
-
style,
|
|
19
|
-
]}
|
|
20
|
-
/>
|
|
21
|
-
)
|
|
22
|
-
}
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { View } from 'react-native'
|
|
3
|
+
import { useTheme } from '../../theme/useTheme'
|
|
4
|
+
import type { SeparatorProps } from './Separator.types'
|
|
5
|
+
|
|
6
|
+
export const Separator: React.FC<SeparatorProps> = ({ orientation = 'horizontal', style }) => {
|
|
7
|
+
const theme = useTheme()
|
|
8
|
+
return (
|
|
9
|
+
<View
|
|
10
|
+
accessible={false}
|
|
11
|
+
style={[
|
|
12
|
+
{
|
|
13
|
+
backgroundColor: theme.colors.border,
|
|
14
|
+
...(orientation === 'horizontal'
|
|
15
|
+
? { height: 1, width: '100%' }
|
|
16
|
+
: { width: 1, height: '100%' }),
|
|
17
|
+
},
|
|
18
|
+
style,
|
|
19
|
+
]}
|
|
20
|
+
/>
|
|
21
|
+
)
|
|
22
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { ViewStyle } from 'react-native'
|
|
2
|
-
|
|
3
|
-
export interface SeparatorProps {
|
|
4
|
-
orientation?: 'horizontal' | 'vertical'
|
|
5
|
-
style?: ViewStyle
|
|
6
|
-
}
|
|
1
|
+
import type { ViewStyle } from 'react-native'
|
|
2
|
+
|
|
3
|
+
export interface SeparatorProps {
|
|
4
|
+
orientation?: 'horizontal' | 'vertical'
|
|
5
|
+
style?: ViewStyle
|
|
6
|
+
}
|
|
@@ -1,50 +1,50 @@
|
|
|
1
|
-
import React, { useEffect } from 'react'
|
|
2
|
-
import { View } from 'react-native'
|
|
3
|
-
import Animated, {
|
|
4
|
-
useSharedValue,
|
|
5
|
-
useAnimatedStyle,
|
|
6
|
-
withRepeat,
|
|
7
|
-
withTiming,
|
|
8
|
-
Easing,
|
|
9
|
-
} from 'react-native-reanimated'
|
|
10
|
-
import { useTheme } from '../../theme/useTheme'
|
|
11
|
-
import type { SpinnerProps } from './Spinner.types'
|
|
12
|
-
|
|
13
|
-
const sizes = { sm: 16, md: 24, lg: 32 }
|
|
14
|
-
|
|
15
|
-
export const Spinner: React.FC<SpinnerProps> = ({ size = 'md', color }) => {
|
|
16
|
-
const theme = useTheme()
|
|
17
|
-
const rotation = useSharedValue(0)
|
|
18
|
-
const px = sizes[size]
|
|
19
|
-
const spinnerColor = color ?? theme.colors.primary
|
|
20
|
-
|
|
21
|
-
useEffect(() => {
|
|
22
|
-
rotation.value = withRepeat(
|
|
23
|
-
withTiming(360, { duration: 900, easing: Easing.linear }),
|
|
24
|
-
-1,
|
|
25
|
-
false,
|
|
26
|
-
)
|
|
27
|
-
}, [])
|
|
28
|
-
|
|
29
|
-
const animatedStyle = useAnimatedStyle(() => ({
|
|
30
|
-
transform: [{ rotate: `${rotation.value}deg` }],
|
|
31
|
-
}))
|
|
32
|
-
|
|
33
|
-
return (
|
|
34
|
-
<View style={{ width: px, height: px }}>
|
|
35
|
-
<Animated.View
|
|
36
|
-
style={[
|
|
37
|
-
animatedStyle,
|
|
38
|
-
{
|
|
39
|
-
width: px,
|
|
40
|
-
height: px,
|
|
41
|
-
borderRadius: px / 2,
|
|
42
|
-
borderWidth: 2,
|
|
43
|
-
borderColor: spinnerColor,
|
|
44
|
-
borderTopColor: 'transparent',
|
|
45
|
-
},
|
|
46
|
-
]}
|
|
47
|
-
/>
|
|
48
|
-
</View>
|
|
49
|
-
)
|
|
50
|
-
}
|
|
1
|
+
import React, { useEffect } from 'react'
|
|
2
|
+
import { View } from 'react-native'
|
|
3
|
+
import Animated, {
|
|
4
|
+
useSharedValue,
|
|
5
|
+
useAnimatedStyle,
|
|
6
|
+
withRepeat,
|
|
7
|
+
withTiming,
|
|
8
|
+
Easing,
|
|
9
|
+
} from 'react-native-reanimated'
|
|
10
|
+
import { useTheme } from '../../theme/useTheme'
|
|
11
|
+
import type { SpinnerProps } from './Spinner.types'
|
|
12
|
+
|
|
13
|
+
const sizes = { sm: 16, md: 24, lg: 32 }
|
|
14
|
+
|
|
15
|
+
export const Spinner: React.FC<SpinnerProps> = ({ size = 'md', color }) => {
|
|
16
|
+
const theme = useTheme()
|
|
17
|
+
const rotation = useSharedValue(0)
|
|
18
|
+
const px = sizes[size]
|
|
19
|
+
const spinnerColor = color ?? theme.colors.primary
|
|
20
|
+
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
rotation.value = withRepeat(
|
|
23
|
+
withTiming(360, { duration: 900, easing: Easing.linear }),
|
|
24
|
+
-1,
|
|
25
|
+
false,
|
|
26
|
+
)
|
|
27
|
+
}, [])
|
|
28
|
+
|
|
29
|
+
const animatedStyle = useAnimatedStyle(() => ({
|
|
30
|
+
transform: [{ rotate: `${rotation.value}deg` }],
|
|
31
|
+
}))
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<View style={{ width: px, height: px }}>
|
|
35
|
+
<Animated.View
|
|
36
|
+
style={[
|
|
37
|
+
animatedStyle,
|
|
38
|
+
{
|
|
39
|
+
width: px,
|
|
40
|
+
height: px,
|
|
41
|
+
borderRadius: px / 2,
|
|
42
|
+
borderWidth: 2,
|
|
43
|
+
borderColor: spinnerColor,
|
|
44
|
+
borderTopColor: 'transparent',
|
|
45
|
+
},
|
|
46
|
+
]}
|
|
47
|
+
/>
|
|
48
|
+
</View>
|
|
49
|
+
)
|
|
50
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export interface SpinnerProps {
|
|
2
|
-
size?: 'sm' | 'md' | 'lg'
|
|
3
|
-
color?: string
|
|
4
|
-
}
|
|
1
|
+
export interface SpinnerProps {
|
|
2
|
+
size?: 'sm' | 'md' | 'lg'
|
|
3
|
+
color?: string
|
|
4
|
+
}
|
|
@@ -1,45 +1,45 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import { Text as RNText } from 'react-native'
|
|
3
|
-
import type { TextStyle } from 'react-native'
|
|
4
|
-
import { useTheme } from '../../theme/useTheme'
|
|
5
|
-
import type { TextProps, TextVariant, TextSize, TextWeight } from './Text.types'
|
|
6
|
-
|
|
7
|
-
const variantMap: Record<TextVariant, { sizeKey: string; weightKey: string }> = {
|
|
8
|
-
body: { sizeKey: 'md', weightKey: 'regular' },
|
|
9
|
-
label: { sizeKey: 'sm', weightKey: 'medium' },
|
|
10
|
-
caption: { sizeKey: 'xs', weightKey: 'regular' },
|
|
11
|
-
heading: { sizeKey: 'xl', weightKey: 'bold' },
|
|
12
|
-
title: { sizeKey: '2xl', weightKey: 'bold' },
|
|
13
|
-
display: { sizeKey: '3xl', weightKey: 'bold' },
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export const Text: React.FC<TextProps> = ({
|
|
17
|
-
variant = 'body',
|
|
18
|
-
size,
|
|
19
|
-
weight,
|
|
20
|
-
color,
|
|
21
|
-
muted = false,
|
|
22
|
-
style,
|
|
23
|
-
children,
|
|
24
|
-
...rest
|
|
25
|
-
}) => {
|
|
26
|
-
const theme = useTheme()
|
|
27
|
-
const { sizeKey, weightKey } = variantMap[variant]
|
|
28
|
-
|
|
29
|
-
return (
|
|
30
|
-
<RNText
|
|
31
|
-
style={[
|
|
32
|
-
{
|
|
33
|
-
color: color ?? (muted ? theme.colors.muted : theme.colors.foreground),
|
|
34
|
-
fontSize: theme.typography.size[size ?? sizeKey as TextSize],
|
|
35
|
-
fontWeight: theme.typography.weight[weight ?? weightKey as TextWeight] as TextStyle['fontWeight'],
|
|
36
|
-
lineHeight: theme.typography.lineHeight.normal,
|
|
37
|
-
},
|
|
38
|
-
style,
|
|
39
|
-
]}
|
|
40
|
-
{...rest}
|
|
41
|
-
>
|
|
42
|
-
{children}
|
|
43
|
-
</RNText>
|
|
44
|
-
)
|
|
45
|
-
}
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { Text as RNText } from 'react-native'
|
|
3
|
+
import type { TextStyle } from 'react-native'
|
|
4
|
+
import { useTheme } from '../../theme/useTheme'
|
|
5
|
+
import type { TextProps, TextVariant, TextSize, TextWeight } from './Text.types'
|
|
6
|
+
|
|
7
|
+
const variantMap: Record<TextVariant, { sizeKey: string; weightKey: string }> = {
|
|
8
|
+
body: { sizeKey: 'md', weightKey: 'regular' },
|
|
9
|
+
label: { sizeKey: 'sm', weightKey: 'medium' },
|
|
10
|
+
caption: { sizeKey: 'xs', weightKey: 'regular' },
|
|
11
|
+
heading: { sizeKey: 'xl', weightKey: 'bold' },
|
|
12
|
+
title: { sizeKey: '2xl', weightKey: 'bold' },
|
|
13
|
+
display: { sizeKey: '3xl', weightKey: 'bold' },
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const Text: React.FC<TextProps> = ({
|
|
17
|
+
variant = 'body',
|
|
18
|
+
size,
|
|
19
|
+
weight,
|
|
20
|
+
color,
|
|
21
|
+
muted = false,
|
|
22
|
+
style,
|
|
23
|
+
children,
|
|
24
|
+
...rest
|
|
25
|
+
}) => {
|
|
26
|
+
const theme = useTheme()
|
|
27
|
+
const { sizeKey, weightKey } = variantMap[variant]
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<RNText
|
|
31
|
+
style={[
|
|
32
|
+
{
|
|
33
|
+
color: color ?? (muted ? theme.colors.muted : theme.colors.foreground),
|
|
34
|
+
fontSize: theme.typography.size[size ?? sizeKey as TextSize],
|
|
35
|
+
fontWeight: theme.typography.weight[weight ?? weightKey as TextWeight] as TextStyle['fontWeight'],
|
|
36
|
+
lineHeight: theme.typography.lineHeight.normal,
|
|
37
|
+
},
|
|
38
|
+
style,
|
|
39
|
+
]}
|
|
40
|
+
{...rest}
|
|
41
|
+
>
|
|
42
|
+
{children}
|
|
43
|
+
</RNText>
|
|
44
|
+
)
|
|
45
|
+
}
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import type { TextProps as RNTextProps } from 'react-native'
|
|
2
|
-
|
|
3
|
-
export type TextVariant = 'body' | 'label' | 'caption' | 'heading' | 'title' | 'display'
|
|
4
|
-
export type TextSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl'
|
|
5
|
-
export type TextWeight = 'regular' | 'medium' | 'semibold' | 'bold'
|
|
6
|
-
|
|
7
|
-
export interface TextProps extends Omit<RNTextProps, 'style'> {
|
|
8
|
-
variant?: TextVariant
|
|
9
|
-
size?: TextSize
|
|
10
|
-
weight?: TextWeight
|
|
11
|
-
color?: string
|
|
12
|
-
muted?: boolean
|
|
13
|
-
children: React.ReactNode
|
|
14
|
-
style?: RNTextProps['style']
|
|
15
|
-
}
|
|
1
|
+
import type { TextProps as RNTextProps } from 'react-native'
|
|
2
|
+
|
|
3
|
+
export type TextVariant = 'body' | 'label' | 'caption' | 'heading' | 'title' | 'display'
|
|
4
|
+
export type TextSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl'
|
|
5
|
+
export type TextWeight = 'regular' | 'medium' | 'semibold' | 'bold'
|
|
6
|
+
|
|
7
|
+
export interface TextProps extends Omit<RNTextProps, 'style'> {
|
|
8
|
+
variant?: TextVariant
|
|
9
|
+
size?: TextSize
|
|
10
|
+
weight?: TextWeight
|
|
11
|
+
color?: string
|
|
12
|
+
muted?: boolean
|
|
13
|
+
children: React.ReactNode
|
|
14
|
+
style?: RNTextProps['style']
|
|
15
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { createContext } from 'react'
|
|
2
|
-
import type { ResolvedTheme } from '../tokens/types'
|
|
3
|
-
import { resolveTokens, zinc } from '../tokens'
|
|
4
|
-
|
|
5
|
-
export const defaultTheme: ResolvedTheme = resolveTokens(zinc, 'light')
|
|
6
|
-
export const ThemeContext = createContext<ResolvedTheme>(defaultTheme)
|
|
1
|
+
import { createContext } from 'react'
|
|
2
|
+
import type { ResolvedTheme } from '../tokens/types'
|
|
3
|
+
import { resolveTokens, zinc } from '../tokens'
|
|
4
|
+
|
|
5
|
+
export const defaultTheme: ResolvedTheme = resolveTokens(zinc, 'light')
|
|
6
|
+
export const ThemeContext = createContext<ResolvedTheme>(defaultTheme)
|