feat(release): prepare 1.1.0 candidate

This commit is contained in:
Yun Chan 2026-08-29 18:33:45 +09:00
parent 5a34f66981
commit 5205dcdfa9
736 changed files with 115667 additions and 12203 deletions

View 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
}

View 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>
)
}