feat(release): prepare 1.1.0 candidate
This commit is contained in:
parent
5a34f66981
commit
5205dcdfa9
736 changed files with 115667 additions and 12203 deletions
164
apps/mobile-rn/src/theme/mobile-theme.ts
Normal file
164
apps/mobile-rn/src/theme/mobile-theme.ts
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
import type { ColorSchemeName } from 'react-native'
|
||||
|
||||
export type MobileThemeMode = 'system' | 'light' | 'dark'
|
||||
export type EffectiveMobileTheme = 'light' | 'dark'
|
||||
|
||||
export const MOBILE_THEME_MODES: readonly MobileThemeMode[] = [
|
||||
'system',
|
||||
'light',
|
||||
'dark',
|
||||
] as const
|
||||
|
||||
export interface MobileThemePalette {
|
||||
bg: {
|
||||
app: string
|
||||
card: string
|
||||
cardHover: string
|
||||
elevated: string
|
||||
sidebar: string
|
||||
inset: string
|
||||
chassis: string
|
||||
}
|
||||
text: {
|
||||
primary: string
|
||||
white: string
|
||||
onAccent: string
|
||||
secondary: string
|
||||
label: string
|
||||
disabled: string
|
||||
inactive: string
|
||||
muted: string
|
||||
}
|
||||
accent: {
|
||||
main: string
|
||||
pressed: string
|
||||
dim: string
|
||||
glow: string
|
||||
green: string
|
||||
greenGlow: string
|
||||
}
|
||||
border: {
|
||||
subtle: string
|
||||
default: string
|
||||
strong: string
|
||||
}
|
||||
tag: {
|
||||
purple: string
|
||||
orange: string
|
||||
red: string
|
||||
green: string
|
||||
blue: string
|
||||
}
|
||||
led: {
|
||||
off: string
|
||||
}
|
||||
shadow: string
|
||||
}
|
||||
|
||||
const DARK_PALETTE: MobileThemePalette = {
|
||||
bg: {
|
||||
app: '#19191b',
|
||||
card: '#242427',
|
||||
cardHover: '#2a2a2d',
|
||||
elevated: '#2e2e32',
|
||||
sidebar: '#1e1f21',
|
||||
inset: '#0f0f11',
|
||||
chassis: '#242528',
|
||||
},
|
||||
text: {
|
||||
primary: '#f4f4f5',
|
||||
white: '#ffffff',
|
||||
onAccent: '#ffffff',
|
||||
secondary: '#b4b4bb',
|
||||
label: '#a1a1aa',
|
||||
disabled: '#5f6066',
|
||||
inactive: '#8d8e94',
|
||||
muted: '#9b9ba3',
|
||||
},
|
||||
accent: {
|
||||
// 브랜드 통일: 앱(Midnight Glass) blue 토큰과 동일 값
|
||||
main: '#3b82f6',
|
||||
pressed: '#2563eb',
|
||||
dim: 'rgba(59, 130, 246, 0.16)',
|
||||
glow: 'rgba(59, 130, 246, 0.5)',
|
||||
green: '#4ade80',
|
||||
greenGlow: 'rgba(74, 222, 128, 0.55)',
|
||||
},
|
||||
border: {
|
||||
subtle: 'rgba(255, 255, 255, 0.08)',
|
||||
default: '#3a3a40',
|
||||
strong: 'rgba(255, 255, 255, 0.22)',
|
||||
},
|
||||
tag: {
|
||||
purple: '#c084fc',
|
||||
orange: '#fbbf24',
|
||||
red: '#f87171',
|
||||
green: '#4ade80',
|
||||
blue: '#60a5fa',
|
||||
},
|
||||
led: { off: '#111111' },
|
||||
shadow: '#000000',
|
||||
}
|
||||
|
||||
const LIGHT_PALETTE: MobileThemePalette = {
|
||||
bg: {
|
||||
app: '#f5f5f4',
|
||||
card: '#ffffff',
|
||||
cardHover: '#f1f1ef',
|
||||
elevated: '#ececea',
|
||||
sidebar: '#efefed',
|
||||
inset: '#e7e7e4',
|
||||
chassis: '#dededb',
|
||||
},
|
||||
text: {
|
||||
primary: '#1c1c1e',
|
||||
white: '#ffffff',
|
||||
onAccent: '#ffffff',
|
||||
secondary: '#45454b',
|
||||
label: '#55555d',
|
||||
disabled: '#99999f',
|
||||
inactive: '#6f6f76',
|
||||
muted: '#626269',
|
||||
},
|
||||
accent: {
|
||||
main: '#2563eb',
|
||||
pressed: '#1d4ed8',
|
||||
dim: 'rgba(37, 99, 235, 0.10)',
|
||||
glow: 'rgba(37, 99, 235, 0.24)',
|
||||
green: '#15803d',
|
||||
greenGlow: 'rgba(21, 128, 61, 0.22)',
|
||||
},
|
||||
border: {
|
||||
subtle: 'rgba(28, 28, 30, 0.10)',
|
||||
default: '#cececb',
|
||||
strong: 'rgba(28, 28, 30, 0.30)',
|
||||
},
|
||||
tag: {
|
||||
purple: '#7e22ce',
|
||||
orange: '#a25200',
|
||||
red: '#b42318',
|
||||
green: '#167044',
|
||||
blue: '#175cd3',
|
||||
},
|
||||
led: { off: '#c6c6c2' },
|
||||
shadow: '#4b4b50',
|
||||
}
|
||||
|
||||
export function isMobileThemeMode(value: unknown): value is MobileThemeMode {
|
||||
return typeof value === 'string'
|
||||
&& (MOBILE_THEME_MODES as readonly string[]).includes(value)
|
||||
}
|
||||
|
||||
export function resolveEffectiveTheme(
|
||||
mode: MobileThemeMode,
|
||||
systemScheme: ColorSchemeName,
|
||||
): EffectiveMobileTheme {
|
||||
if (mode === 'light' || mode === 'dark') return mode
|
||||
return systemScheme === 'light' ? 'light' : 'dark'
|
||||
}
|
||||
|
||||
export function getMobileThemePalette(
|
||||
effectiveTheme: EffectiveMobileTheme,
|
||||
): MobileThemePalette {
|
||||
return effectiveTheme === 'light' ? LIGHT_PALETTE : DARK_PALETTE
|
||||
}
|
||||
170
apps/mobile-rn/src/theme/themed-components.tsx
Normal file
170
apps/mobile-rn/src/theme/themed-components.tsx
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
import {
|
||||
Pressable,
|
||||
Text,
|
||||
View,
|
||||
type PressableProps,
|
||||
type StyleProp,
|
||||
type TextProps,
|
||||
type TextStyle,
|
||||
type ViewProps,
|
||||
type ViewStyle,
|
||||
} from 'react-native'
|
||||
import { d3roNativeFonts, d3roNativeRadius, d3roNativeTypo, type D3roNativeTypoKey } from '@d3ro/ui-native'
|
||||
import { useMobilePreferences } from '../lib/preferences-context'
|
||||
|
||||
type ThemeTextColor =
|
||||
| 'primary'
|
||||
| 'secondary'
|
||||
| 'muted'
|
||||
| 'accent'
|
||||
| 'success'
|
||||
| 'danger'
|
||||
| 'onAccent'
|
||||
|
||||
interface ThemeTextProps extends TextProps {
|
||||
variant?: D3roNativeTypoKey
|
||||
color?: ThemeTextColor
|
||||
style?: StyleProp<TextStyle>
|
||||
}
|
||||
|
||||
export function ThemeText({
|
||||
variant = 'body',
|
||||
color = 'primary',
|
||||
style,
|
||||
children,
|
||||
...textProps
|
||||
}: ThemeTextProps): React.ReactElement {
|
||||
const { palette } = useMobilePreferences()
|
||||
const resolvedColor: Record<ThemeTextColor, string> = {
|
||||
primary: palette.text.primary,
|
||||
secondary: palette.text.secondary,
|
||||
muted: palette.text.muted,
|
||||
accent: palette.accent.main,
|
||||
success: palette.tag.green,
|
||||
danger: palette.tag.red,
|
||||
onAccent: palette.text.onAccent,
|
||||
}
|
||||
|
||||
return (
|
||||
<Text
|
||||
{...textProps}
|
||||
style={[
|
||||
d3roNativeTypo[variant],
|
||||
{
|
||||
color: resolvedColor[color],
|
||||
// v3: 시스템 산세리프 — 한글은 iOS Apple SD Gothic Neo /
|
||||
// Android Noto Sans KR로 자동 폴백 (Menlo/monospace 폐기).
|
||||
fontFamily: d3roNativeFonts.sans,
|
||||
},
|
||||
style,
|
||||
]}
|
||||
>
|
||||
{children}
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
|
||||
interface ThemeCardProps extends ViewProps {
|
||||
inset?: boolean
|
||||
style?: StyleProp<ViewStyle>
|
||||
}
|
||||
|
||||
export function ThemeCard({
|
||||
inset = false,
|
||||
style,
|
||||
children,
|
||||
...viewProps
|
||||
}: ThemeCardProps): React.ReactElement {
|
||||
const { palette } = useMobilePreferences()
|
||||
return (
|
||||
<View
|
||||
{...viewProps}
|
||||
style={[
|
||||
{
|
||||
backgroundColor: inset ? palette.bg.inset : palette.bg.card,
|
||||
borderColor: palette.border.subtle,
|
||||
borderWidth: inset ? 0 : 1,
|
||||
// 카드 반경은 SSOT 토큰 사용
|
||||
borderRadius: inset ? d3roNativeRadius.inner : d3roNativeRadius.card,
|
||||
padding: inset ? 10 : 16,
|
||||
shadowColor: palette.shadow,
|
||||
shadowOffset: { width: 0, height: 4 },
|
||||
shadowOpacity: inset ? 0 : 0.12,
|
||||
shadowRadius: inset ? 0 : 8,
|
||||
elevation: inset ? 0 : 2,
|
||||
},
|
||||
style,
|
||||
]}
|
||||
>
|
||||
{children}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
interface ThemeButtonProps extends Omit<PressableProps, 'children' | 'style'> {
|
||||
label: string
|
||||
variant?: 'primary' | 'secondary' | 'danger' | 'quiet'
|
||||
style?: StyleProp<ViewStyle>
|
||||
textStyle?: StyleProp<TextStyle>
|
||||
}
|
||||
|
||||
export function ThemeButton({
|
||||
label,
|
||||
variant = 'primary',
|
||||
disabled = false,
|
||||
style,
|
||||
textStyle,
|
||||
...pressableProps
|
||||
}: ThemeButtonProps): React.ReactElement {
|
||||
const { palette } = useMobilePreferences()
|
||||
const primary = variant === 'primary'
|
||||
const danger = variant === 'danger'
|
||||
const quiet = variant === 'quiet'
|
||||
const backgroundColor = primary
|
||||
? palette.accent.main
|
||||
: danger
|
||||
? palette.tag.red
|
||||
: 'transparent'
|
||||
const borderColor = quiet
|
||||
? 'transparent'
|
||||
: primary
|
||||
? palette.accent.main
|
||||
: danger
|
||||
? palette.tag.red
|
||||
: palette.border.strong
|
||||
|
||||
return (
|
||||
<Pressable
|
||||
{...pressableProps}
|
||||
accessibilityRole={pressableProps.accessibilityRole ?? 'button'}
|
||||
disabled={disabled}
|
||||
style={({ pressed }) => [
|
||||
{
|
||||
minHeight: 48,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
paddingHorizontal: 18,
|
||||
paddingVertical: 12,
|
||||
borderRadius: d3roNativeRadius.button,
|
||||
borderWidth: quiet ? 0 : 1,
|
||||
borderColor,
|
||||
backgroundColor: pressed && primary
|
||||
? palette.accent.pressed
|
||||
: pressed && !primary
|
||||
? palette.bg.cardHover
|
||||
: backgroundColor,
|
||||
opacity: disabled ? 0.48 : 1,
|
||||
},
|
||||
style,
|
||||
]}
|
||||
>
|
||||
<ThemeText
|
||||
variant="small"
|
||||
color={primary || danger ? 'onAccent' : quiet ? 'muted' : 'primary'}
|
||||
style={[{ textAlign: 'center' }, textStyle]}
|
||||
>
|
||||
{label}
|
||||
</ThemeText>
|
||||
</Pressable>
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue