u
No files matched your search
@@ -0,0 +1,41 @@
|
||||
# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files
|
||||
|
||||
# dependencies
|
||||
node_modules/
|
||||
|
||||
# Expo
|
||||
.expo/
|
||||
dist/
|
||||
web-build/
|
||||
expo-env.d.ts
|
||||
|
||||
# Native
|
||||
.kotlin/
|
||||
*.orig.*
|
||||
*.jks
|
||||
*.p8
|
||||
*.p12
|
||||
*.key
|
||||
*.mobileprovision
|
||||
|
||||
# Metro
|
||||
.metro-health-check*
|
||||
|
||||
# debug
|
||||
npm-debug.*
|
||||
yarn-debug.*
|
||||
yarn-error.*
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# local env files
|
||||
.env*.local
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
|
||||
# generated native folders
|
||||
/ios
|
||||
/android
|
||||
@@ -0,0 +1,51 @@
|
||||
{
|
||||
// Disable the default formatter, use eslint instead
|
||||
"prettier.enable": false,
|
||||
"editor.formatOnSave": false,
|
||||
|
||||
// Auto fix
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": "explicit",
|
||||
"source.organizeImports": "never"
|
||||
},
|
||||
|
||||
// Silent the stylistic rules in your IDE, but still auto fix them
|
||||
"eslint.rules.customizations": [
|
||||
{ "rule": "style/*", "severity": "off", "fixable": true },
|
||||
{ "rule": "format/*", "severity": "off", "fixable": true },
|
||||
{ "rule": "*-indent", "severity": "off", "fixable": true },
|
||||
{ "rule": "*-spacing", "severity": "off", "fixable": true },
|
||||
{ "rule": "*-spaces", "severity": "off", "fixable": true },
|
||||
{ "rule": "*-order", "severity": "off", "fixable": true },
|
||||
{ "rule": "*-dangle", "severity": "off", "fixable": true },
|
||||
{ "rule": "*-newline", "severity": "off", "fixable": true },
|
||||
{ "rule": "*quotes", "severity": "off", "fixable": true },
|
||||
{ "rule": "*semi", "severity": "off", "fixable": true }
|
||||
],
|
||||
|
||||
// Enable eslint for all supported languages
|
||||
"eslint.validate": [
|
||||
"javascript",
|
||||
"javascriptreact",
|
||||
"typescript",
|
||||
"typescriptreact",
|
||||
"vue",
|
||||
"html",
|
||||
"markdown",
|
||||
"json",
|
||||
"json5",
|
||||
"jsonc",
|
||||
"yaml",
|
||||
"toml",
|
||||
"xml",
|
||||
"gql",
|
||||
"graphql",
|
||||
"astro",
|
||||
"svelte",
|
||||
"css",
|
||||
"less",
|
||||
"scss",
|
||||
"pcss",
|
||||
"postcss"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"expo": {
|
||||
"name": "base-app",
|
||||
"slug": "base-app",
|
||||
"version": "1.0.0",
|
||||
"orientation": "portrait",
|
||||
"icon": "./assets/icon.png",
|
||||
"userInterfaceStyle": "light",
|
||||
"newArchEnabled": true,
|
||||
"scheme": "oneao",
|
||||
"splash": {
|
||||
"image": "./assets/splash-icon.png",
|
||||
"resizeMode": "contain",
|
||||
"backgroundColor": "#ffffff"
|
||||
},
|
||||
"ios": {
|
||||
"supportsTablet": true
|
||||
},
|
||||
"android": {
|
||||
"adaptiveIcon": {
|
||||
"foregroundImage": "./assets/adaptive-icon.png",
|
||||
"backgroundColor": "#ffffff"
|
||||
},
|
||||
"edgeToEdgeEnabled": true,
|
||||
"predictiveBackGestureEnabled": false
|
||||
},
|
||||
"web": {
|
||||
"favicon": "./assets/favicon.png"
|
||||
},
|
||||
"plugins": [
|
||||
"expo-router",
|
||||
"expo-font"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 17 KiB |
@@ -0,0 +1,9 @@
|
||||
module.exports = function (api) {
|
||||
api.cache(true)
|
||||
return {
|
||||
presets: [
|
||||
['babel-preset-expo', { jsxImportSource: 'nativewind' }],
|
||||
'nativewind/babel',
|
||||
],
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import antfu from '@antfu/eslint-config'
|
||||
|
||||
export default antfu({
|
||||
stylistic: {
|
||||
indent: 2, // 缩进为 2 个空格(可选 4 或 'tab')
|
||||
quotes: 'single', // 使用单引号(可选 'double')
|
||||
},
|
||||
rules: {
|
||||
'unused-imports/no-unused-imports': 'warn',
|
||||
'unused-imports/no-unused-vars': 'warn',
|
||||
'@typescript-eslint/no-use-before-define': 'off', // 关闭 TS 的“定义前使用”警告
|
||||
'no-console': 'off', // 关闭 console 报错
|
||||
'node/prefer-global/process': 'off',
|
||||
'@typescript-eslint/no-require-imports': 'off',
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,7 @@
|
||||
// Learn more https://docs.expo.io/guides/customizing-metro
|
||||
const { getDefaultConfig } = require('expo/metro-config')
|
||||
const { withNativeWind } = require('nativewind/metro')
|
||||
/** @type {import('expo/metro-config').MetroConfig} */
|
||||
const config = getDefaultConfig(__dirname)
|
||||
|
||||
module.exports = withNativeWind(config, { input: './src/styles/global.css' })
|
||||
@@ -0,0 +1 @@
|
||||
/// <reference types="nativewind/types" />
|
||||
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"name": "base-app",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"main": "expo-router/entry",
|
||||
"scripts": {
|
||||
"start": "expo start",
|
||||
"android": "expo start --android",
|
||||
"ios": "expo start --ios",
|
||||
"web": "expo start --web"
|
||||
},
|
||||
"dependencies": {
|
||||
"@gluestack-ui/core": "^3.0.12",
|
||||
"@gluestack-ui/utils": "^3.0.11",
|
||||
"@gluestack/ui-next-adapter": "^3.0.3",
|
||||
"@legendapp/motion": "^2.4.0",
|
||||
"@react-native-async-storage/async-storage": "2.2.0",
|
||||
"expo": "~54.0.20",
|
||||
"expo-constants": "~18.0.10",
|
||||
"expo-font": "~14.0.9",
|
||||
"expo-linking": "~8.0.8",
|
||||
"expo-router": "~6.0.14",
|
||||
"expo-splash-screen": "~31.0.10",
|
||||
"expo-status-bar": "~3.0.8",
|
||||
"lucide-react-native": "^0.552.0",
|
||||
"nativewind": "^4.2.1",
|
||||
"react": "19.1.0",
|
||||
"react-native": "0.81.5",
|
||||
"react-native-gesture-handler": "~2.28.0",
|
||||
"react-native-reanimated": "~4.1.3",
|
||||
"react-native-safe-area-context": "~5.6.1",
|
||||
"react-native-screens": "~4.16.0",
|
||||
"react-native-svg": "15.12.1",
|
||||
"zustand": "^5.0.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@antfu/eslint-config": "^6.2.0",
|
||||
"@types/react": "~19.1.0",
|
||||
"eslint": "^9.38.0",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"typescript": "~5.9.2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
nodeLinker: hoisted
|
||||
@@ -0,0 +1,45 @@
|
||||
import type { TabbarItem } from '@/components/custom/app-tabbar'
|
||||
import AppTabbar from '@/components/custom/app-tabbar'
|
||||
import { useToast } from '@/utils/toast'
|
||||
|
||||
export default function TabsLayout() {
|
||||
const toast = useToast()
|
||||
|
||||
const tabbarOptions: TabbarItem[] = [
|
||||
{
|
||||
name: 'user',
|
||||
href: '/user',
|
||||
icon: 'CircleCheckBig',
|
||||
},
|
||||
|
||||
{
|
||||
name: 'money',
|
||||
href: '/money',
|
||||
icon: 'CircleDollarSign',
|
||||
},
|
||||
{
|
||||
name: 'app',
|
||||
icon: 'LayoutGrid',
|
||||
activeIcon: 'Smile',
|
||||
iconSize: 26,
|
||||
onPress: () => {
|
||||
toast.show({
|
||||
title: '该功能尚未开发,请尽情期待!',
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'chat',
|
||||
href: '/chat',
|
||||
icon: 'MessageCircle',
|
||||
},
|
||||
{
|
||||
name: 'love',
|
||||
href: '/love',
|
||||
icon: 'Heart',
|
||||
},
|
||||
]
|
||||
return (
|
||||
<AppTabbar data={tabbarOptions} />
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { Text, View } from 'react-native'
|
||||
|
||||
export default function Chat() {
|
||||
return (
|
||||
<View>
|
||||
<Text>Chat</Text>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { Text, View } from 'react-native'
|
||||
|
||||
export default function Love() {
|
||||
return (
|
||||
<View>
|
||||
<Text>Love</Text>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { Text, View } from 'react-native'
|
||||
|
||||
export default function Money() {
|
||||
return (
|
||||
<View>
|
||||
<Text>Money</Text>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { Text, View } from 'react-native'
|
||||
|
||||
export default function User() {
|
||||
return (
|
||||
<View>
|
||||
<Text>User</Text>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { Redirect } from 'expo-router'
|
||||
|
||||
export function middleware({ pathname }: { pathname: string }) {
|
||||
console.log(pathname)
|
||||
|
||||
// 示例:如果需要重定向
|
||||
// if (pathname === '/old-path') {
|
||||
// return Redirect('/new-path');
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { useEffect } from 'react'
|
||||
import { Linking } from 'react-native'
|
||||
|
||||
export default function NativeIntent() {
|
||||
useEffect(() => {
|
||||
Linking.getInitialURL().then((url) => {
|
||||
console.log('处理未匹配的深度链接', url)
|
||||
})
|
||||
}, [])
|
||||
|
||||
return null
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { router, Stack } from 'expo-router'
|
||||
import { ArrowLeft } from 'lucide-react-native'
|
||||
import { EmptyState } from '@/components/common/empty-status'
|
||||
|
||||
export default function NotFound() {
|
||||
return (
|
||||
<>
|
||||
<Stack.Screen options={{ headerShown: false }} />
|
||||
<EmptyState
|
||||
image={require('@/static/images/404.png')}
|
||||
title="(´・ω・`) 页面走丢了~"
|
||||
description="回首页看看吧!"
|
||||
buttonText="返回应用首页"
|
||||
buttonIcon={ArrowLeft}
|
||||
onButtonPress={() => router.replace('/')}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import type { ErrorBoundaryProps } from 'expo-router'
|
||||
import { ToastProvider } from '@gluestack-ui/core/toast/creator'
|
||||
import { useFonts } from 'expo-font'
|
||||
import { Stack } from 'expo-router'
|
||||
import * as SplashScreen from 'expo-splash-screen'
|
||||
import { StatusBar } from 'expo-status-bar'
|
||||
import { RefreshCw } from 'lucide-react-native'
|
||||
import { useEffect } from 'react'
|
||||
import { EmptyState } from '@/components/common/empty-status'
|
||||
import { GluestackUIProvider } from '@/components/ui/gluestack-ui-provider'
|
||||
import { ToastProviderWrapper } from '@/utils/toast'
|
||||
import '@/styles/global.css'
|
||||
import 'react-native-reanimated'
|
||||
import 'react-native-gesture-handler'
|
||||
|
||||
SplashScreen.preventAutoHideAsync()
|
||||
|
||||
export function ErrorBoundary({ error, retry }: ErrorBoundaryProps) {
|
||||
return (
|
||||
<GluestackUIProvider>
|
||||
<EmptyState
|
||||
image={require('@/static/images/server-error.png')}
|
||||
title="(´・ω・`) 出错啦~"
|
||||
description={`发生了一点小问题:\n${error?.message || '请稍后再试'}`}
|
||||
buttonText="重新加载"
|
||||
buttonIcon={RefreshCw}
|
||||
onButtonPress={retry}
|
||||
/>
|
||||
</GluestackUIProvider>
|
||||
)
|
||||
}
|
||||
|
||||
export default function RootLayout() {
|
||||
const [loaded] = useFonts({
|
||||
SpaceMono: require('@/static/fonts/SpaceMono-Regular.ttf'),
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (loaded) {
|
||||
SplashScreen.hideAsync()
|
||||
}
|
||||
}, [loaded])
|
||||
|
||||
return (
|
||||
<GluestackUIProvider>
|
||||
<ToastProvider>
|
||||
<ToastProviderWrapper>
|
||||
<Stack
|
||||
screenOptions={{
|
||||
headerTitleAlign: 'center',
|
||||
animation: 'slide_from_right',
|
||||
}}
|
||||
>
|
||||
<Stack.Screen
|
||||
name="(tabs)"
|
||||
options={{ headerShown: false }}
|
||||
/>
|
||||
<Stack.Screen name="+not-found" />
|
||||
</Stack>
|
||||
<StatusBar style="auto" />
|
||||
</ToastProviderWrapper>
|
||||
</ToastProvider>
|
||||
</GluestackUIProvider>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import { Image, ScrollView, Text } from 'react-native'
|
||||
import { Button, ButtonIcon, ButtonText } from '@/components/ui/button'
|
||||
|
||||
interface EmptyStateProps {
|
||||
image: any
|
||||
title: string
|
||||
description?: string
|
||||
buttonText: string
|
||||
buttonIcon?: React.ElementType
|
||||
onButtonPress: () => void
|
||||
}
|
||||
|
||||
export function EmptyState({
|
||||
image,
|
||||
title,
|
||||
description,
|
||||
buttonText,
|
||||
buttonIcon,
|
||||
onButtonPress,
|
||||
}: EmptyStateProps) {
|
||||
return (
|
||||
<ScrollView
|
||||
contentContainerStyle={{
|
||||
flexGrow: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
backgroundColor: 'white',
|
||||
padding: 16,
|
||||
}}
|
||||
showsVerticalScrollIndicator={true}
|
||||
>
|
||||
<Image source={image} className="w-56 h-56 mb-4" resizeMode="contain" />
|
||||
|
||||
<Text className="text-2xl font-semibold text-gray-900 mb-2">{title}</Text>
|
||||
|
||||
{description && (
|
||||
<Text className="text-base text-gray-600 text-center mb-6 leading-6">
|
||||
{description}
|
||||
</Text>
|
||||
)}
|
||||
|
||||
<Button
|
||||
onPress={onButtonPress}
|
||||
>
|
||||
{buttonIcon && <ButtonIcon as={buttonIcon} />}
|
||||
<ButtonText>{buttonText}</ButtonText>
|
||||
</Button>
|
||||
</ScrollView>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
import { usePathname } from 'expo-router'
|
||||
import { TabList, Tabs, TabSlot, TabTrigger } from 'expo-router/ui'
|
||||
import { icons } from 'lucide-react-native'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Pressable, View } from 'react-native'
|
||||
import Animated, {
|
||||
useAnimatedStyle,
|
||||
useSharedValue,
|
||||
withSequence,
|
||||
withTiming,
|
||||
} from 'react-native-reanimated'
|
||||
|
||||
export interface TabbarItem {
|
||||
icon: keyof typeof icons
|
||||
iconSize?: number
|
||||
activeIcon?: keyof typeof icons
|
||||
name: string
|
||||
href?: string
|
||||
onPress?: () => void
|
||||
}
|
||||
|
||||
interface AppTabbarProps {
|
||||
data: TabbarItem[]
|
||||
onTabPress?: (item: TabbarItem) => void
|
||||
}
|
||||
|
||||
function TabButton({
|
||||
icon,
|
||||
activeIcon,
|
||||
iconSize = 24,
|
||||
isFocused = false,
|
||||
onPress,
|
||||
}: {
|
||||
icon: keyof typeof icons
|
||||
activeIcon?: keyof typeof icons
|
||||
iconSize?: number
|
||||
isFocused?: boolean
|
||||
onPress?: () => void
|
||||
}) {
|
||||
const activeColor = '#333'
|
||||
const inactiveColor = '#aaa'
|
||||
|
||||
const scale = useSharedValue(1)
|
||||
const animatedStyle = useAnimatedStyle(() => ({
|
||||
transform: [{ scale: scale.value }],
|
||||
}))
|
||||
|
||||
const IconComponent = icons[isFocused && activeIcon ? activeIcon : icon]
|
||||
|
||||
const handlePress = () => {
|
||||
scale.value = withSequence(
|
||||
withTiming(0.9, { duration: 200 }),
|
||||
withTiming(1, { duration: 200 }),
|
||||
)
|
||||
onPress?.()
|
||||
}
|
||||
|
||||
return (
|
||||
<Pressable
|
||||
onPress={handlePress}
|
||||
className="flex-1 items-center justify-center h-full"
|
||||
>
|
||||
<Animated.View style={animatedStyle}>
|
||||
<IconComponent
|
||||
width={iconSize}
|
||||
height={iconSize}
|
||||
stroke={isFocused ? activeColor : inactiveColor}
|
||||
/>
|
||||
</Animated.View>
|
||||
</Pressable>
|
||||
)
|
||||
}
|
||||
|
||||
export default function AppTabbar({ data, onTabPress }: AppTabbarProps) {
|
||||
const pathname = usePathname()
|
||||
|
||||
// 初始选中 Tab
|
||||
const defaultTab = data.find(item => item.href === pathname)?.name
|
||||
?? data.find(item => item.href)?.name
|
||||
?? data[0]?.name
|
||||
const [activeTab, setActiveTab] = useState<string>(defaultTab)
|
||||
|
||||
// 当 pathname 改变(路由跳转)时,同步高亮
|
||||
useEffect(() => {
|
||||
const matchTab = data.find(item => item.href === pathname)
|
||||
if (matchTab) {
|
||||
setActiveTab(matchTab.name)
|
||||
}
|
||||
// 如果没有匹配的路由,保持当前的 activeTab 不变
|
||||
// 这样无 href 的项就能保持选中状态
|
||||
}, [pathname, data])
|
||||
|
||||
const handleTabPress = (item: TabbarItem) => {
|
||||
setActiveTab(item.name)
|
||||
item.onPress?.()
|
||||
onTabPress?.(item)
|
||||
}
|
||||
|
||||
return (
|
||||
<Tabs>
|
||||
<TabSlot />
|
||||
|
||||
<TabList asChild>
|
||||
<View className="flex-row bg-white h-12 items-center">
|
||||
{data.map((item) => {
|
||||
const isFocused = activeTab === item.name
|
||||
|
||||
if (item.href) {
|
||||
return (
|
||||
<TabTrigger key={item.name} name={item.name} href={item.href} asChild>
|
||||
<TabButton
|
||||
icon={item.icon}
|
||||
activeIcon={item.activeIcon}
|
||||
iconSize={item.iconSize}
|
||||
isFocused={isFocused}
|
||||
onPress={() => handleTabPress(item)}
|
||||
/>
|
||||
</TabTrigger>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<TabButton
|
||||
key={item.name}
|
||||
icon={item.icon}
|
||||
activeIcon={item.activeIcon}
|
||||
iconSize={item.iconSize}
|
||||
onPress={() => handleTabPress(item)}
|
||||
isFocused={isFocused}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</View>
|
||||
</TabList>
|
||||
</Tabs>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,434 @@
|
||||
'use client';
|
||||
import React from 'react';
|
||||
import { createButton } from '@gluestack-ui/core/button/creator';
|
||||
import {
|
||||
tva,
|
||||
withStyleContext,
|
||||
useStyleContext,
|
||||
type VariantProps,
|
||||
} from '@gluestack-ui/utils/nativewind-utils';
|
||||
import { cssInterop } from 'nativewind';
|
||||
import { ActivityIndicator, Pressable, Text, View } from 'react-native';
|
||||
import { PrimitiveIcon, UIIcon } from '@gluestack-ui/core/icon/creator';
|
||||
|
||||
const SCOPE = 'BUTTON';
|
||||
|
||||
const Root = withStyleContext(Pressable, SCOPE);
|
||||
|
||||
const UIButton = createButton({
|
||||
Root: Root,
|
||||
Text,
|
||||
Group: View,
|
||||
Spinner: ActivityIndicator,
|
||||
Icon: UIIcon,
|
||||
});
|
||||
|
||||
cssInterop(PrimitiveIcon, {
|
||||
className: {
|
||||
target: 'style',
|
||||
nativeStyleToProp: {
|
||||
height: true,
|
||||
width: true,
|
||||
fill: true,
|
||||
color: 'classNameColor',
|
||||
stroke: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const buttonStyle = tva({
|
||||
base: 'group/button rounded bg-primary-500 flex-row items-center justify-center data-[focus-visible=true]:web:outline-none data-[focus-visible=true]:web:ring-2 data-[disabled=true]:opacity-40 gap-2',
|
||||
variants: {
|
||||
action: {
|
||||
primary:
|
||||
'bg-primary-500 data-[hover=true]:bg-primary-600 data-[active=true]:bg-primary-700 border-primary-300 data-[hover=true]:border-primary-400 data-[active=true]:border-primary-500 data-[focus-visible=true]:web:ring-indicator-info',
|
||||
secondary:
|
||||
'bg-secondary-500 border-secondary-300 data-[hover=true]:bg-secondary-600 data-[hover=true]:border-secondary-400 data-[active=true]:bg-secondary-700 data-[active=true]:border-secondary-700 data-[focus-visible=true]:web:ring-indicator-info',
|
||||
positive:
|
||||
'bg-success-500 border-success-300 data-[hover=true]:bg-success-600 data-[hover=true]:border-success-400 data-[active=true]:bg-success-700 data-[active=true]:border-success-500 data-[focus-visible=true]:web:ring-indicator-info',
|
||||
negative:
|
||||
'bg-error-500 border-error-300 data-[hover=true]:bg-error-600 data-[hover=true]:border-error-400 data-[active=true]:bg-error-700 data-[active=true]:border-error-500 data-[focus-visible=true]:web:ring-indicator-info',
|
||||
default:
|
||||
'bg-transparent data-[hover=true]:bg-background-50 data-[active=true]:bg-transparent',
|
||||
},
|
||||
variant: {
|
||||
link: 'px-0',
|
||||
outline:
|
||||
'bg-transparent border data-[hover=true]:bg-background-50 data-[active=true]:bg-transparent',
|
||||
solid: '',
|
||||
},
|
||||
|
||||
size: {
|
||||
xs: 'px-3.5 h-8',
|
||||
sm: 'px-4 h-9',
|
||||
md: 'px-5 h-10',
|
||||
lg: 'px-6 h-11',
|
||||
xl: 'px-7 h-12',
|
||||
},
|
||||
},
|
||||
compoundVariants: [
|
||||
{
|
||||
action: 'primary',
|
||||
variant: 'link',
|
||||
class:
|
||||
'px-0 bg-transparent data-[hover=true]:bg-transparent data-[active=true]:bg-transparent',
|
||||
},
|
||||
{
|
||||
action: 'secondary',
|
||||
variant: 'link',
|
||||
class:
|
||||
'px-0 bg-transparent data-[hover=true]:bg-transparent data-[active=true]:bg-transparent',
|
||||
},
|
||||
{
|
||||
action: 'positive',
|
||||
variant: 'link',
|
||||
class:
|
||||
'px-0 bg-transparent data-[hover=true]:bg-transparent data-[active=true]:bg-transparent',
|
||||
},
|
||||
{
|
||||
action: 'negative',
|
||||
variant: 'link',
|
||||
class:
|
||||
'px-0 bg-transparent data-[hover=true]:bg-transparent data-[active=true]:bg-transparent',
|
||||
},
|
||||
{
|
||||
action: 'primary',
|
||||
variant: 'outline',
|
||||
class:
|
||||
'bg-transparent data-[hover=true]:bg-background-50 data-[active=true]:bg-transparent',
|
||||
},
|
||||
{
|
||||
action: 'secondary',
|
||||
variant: 'outline',
|
||||
class:
|
||||
'bg-transparent data-[hover=true]:bg-background-50 data-[active=true]:bg-transparent',
|
||||
},
|
||||
{
|
||||
action: 'positive',
|
||||
variant: 'outline',
|
||||
class:
|
||||
'bg-transparent data-[hover=true]:bg-background-50 data-[active=true]:bg-transparent',
|
||||
},
|
||||
{
|
||||
action: 'negative',
|
||||
variant: 'outline',
|
||||
class:
|
||||
'bg-transparent data-[hover=true]:bg-background-50 data-[active=true]:bg-transparent',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const buttonTextStyle = tva({
|
||||
base: 'text-typography-0 font-semibold web:select-none',
|
||||
parentVariants: {
|
||||
action: {
|
||||
primary:
|
||||
'text-primary-600 data-[hover=true]:text-primary-600 data-[active=true]:text-primary-700',
|
||||
secondary:
|
||||
'text-typography-500 data-[hover=true]:text-typography-600 data-[active=true]:text-typography-700',
|
||||
positive:
|
||||
'text-success-600 data-[hover=true]:text-success-600 data-[active=true]:text-success-700',
|
||||
negative:
|
||||
'text-error-600 data-[hover=true]:text-error-600 data-[active=true]:text-error-700',
|
||||
},
|
||||
variant: {
|
||||
link: 'data-[hover=true]:underline data-[active=true]:underline',
|
||||
outline: '',
|
||||
solid:
|
||||
'text-typography-0 data-[hover=true]:text-typography-0 data-[active=true]:text-typography-0',
|
||||
},
|
||||
size: {
|
||||
xs: 'text-xs',
|
||||
sm: 'text-sm',
|
||||
md: 'text-base',
|
||||
lg: 'text-lg',
|
||||
xl: 'text-xl',
|
||||
},
|
||||
},
|
||||
parentCompoundVariants: [
|
||||
{
|
||||
variant: 'solid',
|
||||
action: 'primary',
|
||||
class:
|
||||
'text-typography-0 data-[hover=true]:text-typography-0 data-[active=true]:text-typography-0',
|
||||
},
|
||||
{
|
||||
variant: 'solid',
|
||||
action: 'secondary',
|
||||
class:
|
||||
'text-typography-800 data-[hover=true]:text-typography-800 data-[active=true]:text-typography-800',
|
||||
},
|
||||
{
|
||||
variant: 'solid',
|
||||
action: 'positive',
|
||||
class:
|
||||
'text-typography-0 data-[hover=true]:text-typography-0 data-[active=true]:text-typography-0',
|
||||
},
|
||||
{
|
||||
variant: 'solid',
|
||||
action: 'negative',
|
||||
class:
|
||||
'text-typography-0 data-[hover=true]:text-typography-0 data-[active=true]:text-typography-0',
|
||||
},
|
||||
{
|
||||
variant: 'outline',
|
||||
action: 'primary',
|
||||
class:
|
||||
'text-primary-500 data-[hover=true]:text-primary-500 data-[active=true]:text-primary-500',
|
||||
},
|
||||
{
|
||||
variant: 'outline',
|
||||
action: 'secondary',
|
||||
class:
|
||||
'text-typography-500 data-[hover=true]:text-primary-600 data-[active=true]:text-typography-700',
|
||||
},
|
||||
{
|
||||
variant: 'outline',
|
||||
action: 'positive',
|
||||
class:
|
||||
'text-primary-500 data-[hover=true]:text-primary-500 data-[active=true]:text-primary-500',
|
||||
},
|
||||
{
|
||||
variant: 'outline',
|
||||
action: 'negative',
|
||||
class:
|
||||
'text-primary-500 data-[hover=true]:text-primary-500 data-[active=true]:text-primary-500',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const buttonIconStyle = tva({
|
||||
base: 'fill-none',
|
||||
parentVariants: {
|
||||
variant: {
|
||||
link: 'data-[hover=true]:underline data-[active=true]:underline',
|
||||
outline: '',
|
||||
solid:
|
||||
'text-typography-0 data-[hover=true]:text-typography-0 data-[active=true]:text-typography-0',
|
||||
},
|
||||
size: {
|
||||
xs: 'h-3.5 w-3.5',
|
||||
sm: 'h-4 w-4',
|
||||
md: 'h-[18px] w-[18px]',
|
||||
lg: 'h-[18px] w-[18px]',
|
||||
xl: 'h-5 w-5',
|
||||
},
|
||||
action: {
|
||||
primary:
|
||||
'text-primary-600 data-[hover=true]:text-primary-600 data-[active=true]:text-primary-700',
|
||||
secondary:
|
||||
'text-typography-500 data-[hover=true]:text-typography-600 data-[active=true]:text-typography-700',
|
||||
positive:
|
||||
'text-success-600 data-[hover=true]:text-success-600 data-[active=true]:text-success-700',
|
||||
|
||||
negative:
|
||||
'text-error-600 data-[hover=true]:text-error-600 data-[active=true]:text-error-700',
|
||||
},
|
||||
},
|
||||
parentCompoundVariants: [
|
||||
{
|
||||
variant: 'solid',
|
||||
action: 'primary',
|
||||
class:
|
||||
'text-typography-0 data-[hover=true]:text-typography-0 data-[active=true]:text-typography-0',
|
||||
},
|
||||
{
|
||||
variant: 'solid',
|
||||
action: 'secondary',
|
||||
class:
|
||||
'text-typography-800 data-[hover=true]:text-typography-800 data-[active=true]:text-typography-800',
|
||||
},
|
||||
{
|
||||
variant: 'solid',
|
||||
action: 'positive',
|
||||
class:
|
||||
'text-typography-0 data-[hover=true]:text-typography-0 data-[active=true]:text-typography-0',
|
||||
},
|
||||
{
|
||||
variant: 'solid',
|
||||
action: 'negative',
|
||||
class:
|
||||
'text-typography-0 data-[hover=true]:text-typography-0 data-[active=true]:text-typography-0',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const buttonGroupStyle = tva({
|
||||
base: '',
|
||||
variants: {
|
||||
space: {
|
||||
'xs': 'gap-1',
|
||||
'sm': 'gap-2',
|
||||
'md': 'gap-3',
|
||||
'lg': 'gap-4',
|
||||
'xl': 'gap-5',
|
||||
'2xl': 'gap-6',
|
||||
'3xl': 'gap-7',
|
||||
'4xl': 'gap-8',
|
||||
},
|
||||
isAttached: {
|
||||
true: 'gap-0',
|
||||
},
|
||||
flexDirection: {
|
||||
'row': 'flex-row',
|
||||
'column': 'flex-col',
|
||||
'row-reverse': 'flex-row-reverse',
|
||||
'column-reverse': 'flex-col-reverse',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
type IButtonProps = Omit<
|
||||
React.ComponentPropsWithoutRef<typeof UIButton>,
|
||||
'context'
|
||||
> &
|
||||
VariantProps<typeof buttonStyle> & { className?: string };
|
||||
|
||||
const Button = React.forwardRef<
|
||||
React.ElementRef<typeof UIButton>,
|
||||
IButtonProps
|
||||
>(
|
||||
(
|
||||
{ className, variant = 'solid', size = 'md', action = 'primary', ...props },
|
||||
ref
|
||||
) => {
|
||||
return (
|
||||
<UIButton
|
||||
ref={ref}
|
||||
{...props}
|
||||
className={buttonStyle({ variant, size, action, class: className })}
|
||||
context={{ variant, size, action }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
type IButtonTextProps = React.ComponentPropsWithoutRef<typeof UIButton.Text> &
|
||||
VariantProps<typeof buttonTextStyle> & { className?: string };
|
||||
|
||||
const ButtonText = React.forwardRef<
|
||||
React.ElementRef<typeof UIButton.Text>,
|
||||
IButtonTextProps
|
||||
>(({ className, variant, size, action, ...props }, ref) => {
|
||||
const {
|
||||
variant: parentVariant,
|
||||
size: parentSize,
|
||||
action: parentAction,
|
||||
} = useStyleContext(SCOPE);
|
||||
|
||||
return (
|
||||
<UIButton.Text
|
||||
ref={ref}
|
||||
{...props}
|
||||
className={buttonTextStyle({
|
||||
parentVariants: {
|
||||
variant: parentVariant,
|
||||
size: parentSize,
|
||||
action: parentAction,
|
||||
},
|
||||
variant,
|
||||
size,
|
||||
action,
|
||||
class: className,
|
||||
})}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
const ButtonSpinner = UIButton.Spinner;
|
||||
|
||||
type IButtonIcon = React.ComponentPropsWithoutRef<typeof UIButton.Icon> &
|
||||
VariantProps<typeof buttonIconStyle> & {
|
||||
className?: string | undefined;
|
||||
as?: React.ElementType;
|
||||
height?: number;
|
||||
width?: number;
|
||||
};
|
||||
|
||||
const ButtonIcon = React.forwardRef<
|
||||
React.ElementRef<typeof UIButton.Icon>,
|
||||
IButtonIcon
|
||||
>(({ className, size, ...props }, ref) => {
|
||||
const {
|
||||
variant: parentVariant,
|
||||
size: parentSize,
|
||||
action: parentAction,
|
||||
} = useStyleContext(SCOPE);
|
||||
|
||||
if (typeof size === 'number') {
|
||||
return (
|
||||
<UIButton.Icon
|
||||
ref={ref}
|
||||
{...props}
|
||||
className={buttonIconStyle({ class: className })}
|
||||
size={size}
|
||||
/>
|
||||
);
|
||||
} else if (
|
||||
(props.height !== undefined || props.width !== undefined) &&
|
||||
size === undefined
|
||||
) {
|
||||
return (
|
||||
<UIButton.Icon
|
||||
ref={ref}
|
||||
{...props}
|
||||
className={buttonIconStyle({ class: className })}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<UIButton.Icon
|
||||
{...props}
|
||||
className={buttonIconStyle({
|
||||
parentVariants: {
|
||||
size: parentSize,
|
||||
variant: parentVariant,
|
||||
action: parentAction,
|
||||
},
|
||||
size,
|
||||
class: className,
|
||||
})}
|
||||
ref={ref}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
type IButtonGroupProps = React.ComponentPropsWithoutRef<typeof UIButton.Group> &
|
||||
VariantProps<typeof buttonGroupStyle>;
|
||||
|
||||
const ButtonGroup = React.forwardRef<
|
||||
React.ElementRef<typeof UIButton.Group>,
|
||||
IButtonGroupProps
|
||||
>(
|
||||
(
|
||||
{
|
||||
className,
|
||||
space = 'md',
|
||||
isAttached = false,
|
||||
flexDirection = 'column',
|
||||
...props
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
return (
|
||||
<UIButton.Group
|
||||
className={buttonGroupStyle({
|
||||
class: className,
|
||||
space,
|
||||
isAttached,
|
||||
flexDirection,
|
||||
})}
|
||||
{...props}
|
||||
ref={ref}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
Button.displayName = 'Button';
|
||||
ButtonText.displayName = 'ButtonText';
|
||||
ButtonSpinner.displayName = 'ButtonSpinner';
|
||||
ButtonIcon.displayName = 'ButtonIcon';
|
||||
ButtonGroup.displayName = 'ButtonGroup';
|
||||
|
||||
export { Button, ButtonText, ButtonSpinner, ButtonIcon, ButtonGroup };
|
||||
@@ -0,0 +1,309 @@
|
||||
'use client';
|
||||
import { vars } from 'nativewind';
|
||||
|
||||
export const config = {
|
||||
light: vars({
|
||||
'--color-primary-0': '179 179 179',
|
||||
'--color-primary-50': '153 153 153',
|
||||
'--color-primary-100': '128 128 128',
|
||||
'--color-primary-200': '115 115 115',
|
||||
'--color-primary-300': '102 102 102',
|
||||
'--color-primary-400': '82 82 82',
|
||||
'--color-primary-500': '51 51 51',
|
||||
'--color-primary-600': '41 41 41',
|
||||
'--color-primary-700': '31 31 31',
|
||||
'--color-primary-800': '13 13 13',
|
||||
'--color-primary-900': '10 10 10',
|
||||
'--color-primary-950': '8 8 8',
|
||||
|
||||
/* Secondary */
|
||||
'--color-secondary-0': '253 253 253',
|
||||
'--color-secondary-50': '251 251 251',
|
||||
'--color-secondary-100': '246 246 246',
|
||||
'--color-secondary-200': '242 242 242',
|
||||
'--color-secondary-300': '237 237 237',
|
||||
'--color-secondary-400': '230 230 231',
|
||||
'--color-secondary-500': '217 217 219',
|
||||
'--color-secondary-600': '198 199 199',
|
||||
'--color-secondary-700': '189 189 189',
|
||||
'--color-secondary-800': '177 177 177',
|
||||
'--color-secondary-900': '165 164 164',
|
||||
'--color-secondary-950': '157 157 157',
|
||||
|
||||
/* Tertiary */
|
||||
'--color-tertiary-0': '255 250 245',
|
||||
'--color-tertiary-50': '255 242 229',
|
||||
'--color-tertiary-100': '255 233 213',
|
||||
'--color-tertiary-200': '254 209 170',
|
||||
'--color-tertiary-300': '253 180 116',
|
||||
'--color-tertiary-400': '251 157 75',
|
||||
'--color-tertiary-500': '231 129 40',
|
||||
'--color-tertiary-600': '215 117 31',
|
||||
'--color-tertiary-700': '180 98 26',
|
||||
'--color-tertiary-800': '130 73 23',
|
||||
'--color-tertiary-900': '108 61 19',
|
||||
'--color-tertiary-950': '84 49 18',
|
||||
|
||||
/* Error */
|
||||
'--color-error-0': '254 233 233',
|
||||
'--color-error-50': '254 226 226',
|
||||
'--color-error-100': '254 202 202',
|
||||
'--color-error-200': '252 165 165',
|
||||
'--color-error-300': '248 113 113',
|
||||
'--color-error-400': '239 68 68',
|
||||
'--color-error-500': '230 53 53',
|
||||
'--color-error-600': '220 38 38',
|
||||
'--color-error-700': '185 28 28',
|
||||
'--color-error-800': '153 27 27',
|
||||
'--color-error-900': '127 29 29',
|
||||
'--color-error-950': '83 19 19',
|
||||
|
||||
/* Success */
|
||||
'--color-success-0': '228 255 244',
|
||||
'--color-success-50': '202 255 232',
|
||||
'--color-success-100': '162 241 192',
|
||||
'--color-success-200': '132 211 162',
|
||||
'--color-success-300': '102 181 132',
|
||||
'--color-success-400': '72 151 102',
|
||||
'--color-success-500': '52 131 82',
|
||||
'--color-success-600': '42 121 72',
|
||||
'--color-success-700': '32 111 62',
|
||||
'--color-success-800': '22 101 52',
|
||||
'--color-success-900': '20 83 45',
|
||||
'--color-success-950': '27 50 36',
|
||||
|
||||
/* Warning */
|
||||
'--color-warning-0': '255 249 245',
|
||||
'--color-warning-50': '255 244 236',
|
||||
'--color-warning-100': '255 231 213',
|
||||
'--color-warning-200': '254 205 170',
|
||||
'--color-warning-300': '253 173 116',
|
||||
'--color-warning-400': '251 149 75',
|
||||
'--color-warning-500': '231 120 40',
|
||||
'--color-warning-600': '215 108 31',
|
||||
'--color-warning-700': '180 90 26',
|
||||
'--color-warning-800': '130 68 23',
|
||||
'--color-warning-900': '108 56 19',
|
||||
'--color-warning-950': '84 45 18',
|
||||
|
||||
/* Info */
|
||||
'--color-info-0': '236 248 254',
|
||||
'--color-info-50': '199 235 252',
|
||||
'--color-info-100': '162 221 250',
|
||||
'--color-info-200': '124 207 248',
|
||||
'--color-info-300': '87 194 246',
|
||||
'--color-info-400': '50 180 244',
|
||||
'--color-info-500': '13 166 242',
|
||||
'--color-info-600': '11 141 205',
|
||||
'--color-info-700': '9 115 168',
|
||||
'--color-info-800': '7 90 131',
|
||||
'--color-info-900': '5 64 93',
|
||||
'--color-info-950': '3 38 56',
|
||||
|
||||
/* Typography */
|
||||
'--color-typography-0': '254 254 255',
|
||||
'--color-typography-50': '245 245 245',
|
||||
'--color-typography-100': '229 229 229',
|
||||
'--color-typography-200': '219 219 220',
|
||||
'--color-typography-300': '212 212 212',
|
||||
'--color-typography-400': '163 163 163',
|
||||
'--color-typography-500': '140 140 140',
|
||||
'--color-typography-600': '115 115 115',
|
||||
'--color-typography-700': '82 82 82',
|
||||
'--color-typography-800': '64 64 64',
|
||||
'--color-typography-900': '38 38 39',
|
||||
'--color-typography-950': '23 23 23',
|
||||
|
||||
/* Outline */
|
||||
'--color-outline-0': '253 254 254',
|
||||
'--color-outline-50': '243 243 243',
|
||||
'--color-outline-100': '230 230 230',
|
||||
'--color-outline-200': '221 220 219',
|
||||
'--color-outline-300': '211 211 211',
|
||||
'--color-outline-400': '165 163 163',
|
||||
'--color-outline-500': '140 141 141',
|
||||
'--color-outline-600': '115 116 116',
|
||||
'--color-outline-700': '83 82 82',
|
||||
'--color-outline-800': '65 65 65',
|
||||
'--color-outline-900': '39 38 36',
|
||||
'--color-outline-950': '26 23 23',
|
||||
|
||||
/* Background */
|
||||
'--color-background-0': '255 255 255',
|
||||
'--color-background-50': '246 246 246',
|
||||
'--color-background-100': '242 241 241',
|
||||
'--color-background-200': '220 219 219',
|
||||
'--color-background-300': '213 212 212',
|
||||
'--color-background-400': '162 163 163',
|
||||
'--color-background-500': '142 142 142',
|
||||
'--color-background-600': '116 116 116',
|
||||
'--color-background-700': '83 82 82',
|
||||
'--color-background-800': '65 64 64',
|
||||
'--color-background-900': '39 38 37',
|
||||
'--color-background-950': '18 18 18',
|
||||
|
||||
/* Background Special */
|
||||
'--color-background-error': '254 241 241',
|
||||
'--color-background-warning': '255 243 234',
|
||||
'--color-background-success': '237 252 242',
|
||||
'--color-background-muted': '247 248 247',
|
||||
'--color-background-info': '235 248 254',
|
||||
|
||||
/* Focus Ring Indicator */
|
||||
'--color-indicator-primary': '55 55 55',
|
||||
'--color-indicator-info': '83 153 236',
|
||||
'--color-indicator-error': '185 28 28',
|
||||
}),
|
||||
dark: vars({
|
||||
'--color-primary-0': '166 166 166',
|
||||
'--color-primary-50': '175 175 175',
|
||||
'--color-primary-100': '186 186 186',
|
||||
'--color-primary-200': '197 197 197',
|
||||
'--color-primary-300': '212 212 212',
|
||||
'--color-primary-400': '221 221 221',
|
||||
'--color-primary-500': '230 230 230',
|
||||
'--color-primary-600': '240 240 240',
|
||||
'--color-primary-700': '250 250 250',
|
||||
'--color-primary-800': '253 253 253',
|
||||
'--color-primary-900': '254 249 249',
|
||||
'--color-primary-950': '253 252 252',
|
||||
|
||||
/* Secondary */
|
||||
'--color-secondary-0': '20 20 20',
|
||||
'--color-secondary-50': '23 23 23',
|
||||
'--color-secondary-100': '31 31 31',
|
||||
'--color-secondary-200': '39 39 39',
|
||||
'--color-secondary-300': '44 44 44',
|
||||
'--color-secondary-400': '56 57 57',
|
||||
'--color-secondary-500': '63 64 64',
|
||||
'--color-secondary-600': '86 86 86',
|
||||
'--color-secondary-700': '110 110 110',
|
||||
'--color-secondary-800': '135 135 135',
|
||||
'--color-secondary-900': '150 150 150',
|
||||
'--color-secondary-950': '164 164 164',
|
||||
|
||||
/* Tertiary */
|
||||
'--color-tertiary-0': '84 49 18',
|
||||
'--color-tertiary-50': '108 61 19',
|
||||
'--color-tertiary-100': '130 73 23',
|
||||
'--color-tertiary-200': '180 98 26',
|
||||
'--color-tertiary-300': '215 117 31',
|
||||
'--color-tertiary-400': '231 129 40',
|
||||
'--color-tertiary-500': '251 157 75',
|
||||
'--color-tertiary-600': '253 180 116',
|
||||
'--color-tertiary-700': '254 209 170',
|
||||
'--color-tertiary-800': '255 233 213',
|
||||
'--color-tertiary-900': '255 242 229',
|
||||
'--color-tertiary-950': '255 250 245',
|
||||
|
||||
/* Error */
|
||||
'--color-error-0': '83 19 19',
|
||||
'--color-error-50': '127 29 29',
|
||||
'--color-error-100': '153 27 27',
|
||||
'--color-error-200': '185 28 28',
|
||||
'--color-error-300': '220 38 38',
|
||||
'--color-error-400': '230 53 53',
|
||||
'--color-error-500': '239 68 68',
|
||||
'--color-error-600': '249 97 96',
|
||||
'--color-error-700': '229 91 90',
|
||||
'--color-error-800': '254 202 202',
|
||||
'--color-error-900': '254 226 226',
|
||||
'--color-error-950': '254 233 233',
|
||||
|
||||
/* Success */
|
||||
'--color-success-0': '27 50 36',
|
||||
'--color-success-50': '20 83 45',
|
||||
'--color-success-100': '22 101 52',
|
||||
'--color-success-200': '32 111 62',
|
||||
'--color-success-300': '42 121 72',
|
||||
'--color-success-400': '52 131 82',
|
||||
'--color-success-500': '72 151 102',
|
||||
'--color-success-600': '102 181 132',
|
||||
'--color-success-700': '132 211 162',
|
||||
'--color-success-800': '162 241 192',
|
||||
'--color-success-900': '202 255 232',
|
||||
'--color-success-950': '228 255 244',
|
||||
|
||||
/* Warning */
|
||||
'--color-warning-0': '84 45 18',
|
||||
'--color-warning-50': '108 56 19',
|
||||
'--color-warning-100': '130 68 23',
|
||||
'--color-warning-200': '180 90 26',
|
||||
'--color-warning-300': '215 108 31',
|
||||
'--color-warning-400': '231 120 40',
|
||||
'--color-warning-500': '251 149 75',
|
||||
'--color-warning-600': '253 173 116',
|
||||
'--color-warning-700': '254 205 170',
|
||||
'--color-warning-800': '255 231 213',
|
||||
'--color-warning-900': '255 244 237',
|
||||
'--color-warning-950': '255 249 245',
|
||||
|
||||
/* Info */
|
||||
'--color-info-0': '3 38 56',
|
||||
'--color-info-50': '5 64 93',
|
||||
'--color-info-100': '7 90 131',
|
||||
'--color-info-200': '9 115 168',
|
||||
'--color-info-300': '11 141 205',
|
||||
'--color-info-400': '13 166 242',
|
||||
'--color-info-500': '50 180 244',
|
||||
'--color-info-600': '87 194 246',
|
||||
'--color-info-700': '124 207 248',
|
||||
'--color-info-800': '162 221 250',
|
||||
'--color-info-900': '199 235 252',
|
||||
'--color-info-950': '236 248 254',
|
||||
|
||||
/* Typography */
|
||||
'--color-typography-0': '23 23 23',
|
||||
'--color-typography-50': '38 38 39',
|
||||
'--color-typography-100': '64 64 64',
|
||||
'--color-typography-200': '82 82 82',
|
||||
'--color-typography-300': '115 115 115',
|
||||
'--color-typography-400': '140 140 140',
|
||||
'--color-typography-500': '163 163 163',
|
||||
'--color-typography-600': '212 212 212',
|
||||
'--color-typography-700': '219 219 220',
|
||||
'--color-typography-800': '229 229 229',
|
||||
'--color-typography-900': '245 245 245',
|
||||
'--color-typography-950': '254 254 255',
|
||||
|
||||
/* Outline */
|
||||
'--color-outline-0': '26 23 23',
|
||||
'--color-outline-50': '39 38 36',
|
||||
'--color-outline-100': '65 65 65',
|
||||
'--color-outline-200': '83 82 82',
|
||||
'--color-outline-300': '115 116 116',
|
||||
'--color-outline-400': '140 141 141',
|
||||
'--color-outline-500': '165 163 163',
|
||||
'--color-outline-600': '211 211 211',
|
||||
'--color-outline-700': '221 220 219',
|
||||
'--color-outline-800': '230 230 230',
|
||||
'--color-outline-900': '243 243 243',
|
||||
'--color-outline-950': '253 254 254',
|
||||
|
||||
/* Background */
|
||||
'--color-background-0': '18 18 18',
|
||||
'--color-background-50': '39 38 37',
|
||||
'--color-background-100': '65 64 64',
|
||||
'--color-background-200': '83 82 82',
|
||||
'--color-background-300': '116 116 116',
|
||||
'--color-background-400': '142 142 142',
|
||||
'--color-background-500': '162 163 163',
|
||||
'--color-background-600': '213 212 212',
|
||||
'--color-background-700': '229 228 228',
|
||||
'--color-background-800': '242 241 241',
|
||||
'--color-background-900': '246 246 246',
|
||||
'--color-background-950': '255 255 255',
|
||||
|
||||
/* Background Special */
|
||||
'--color-background-error': '66 43 43',
|
||||
'--color-background-warning': '65 47 35',
|
||||
'--color-background-success': '28 43 33',
|
||||
'--color-background-muted': '51 51 51',
|
||||
'--color-background-info': '26 40 46',
|
||||
|
||||
/* Focus Ring Indicator */
|
||||
'--color-indicator-primary': '247 247 247',
|
||||
'--color-indicator-info': '161 199 245',
|
||||
'--color-indicator-error': '232 70 69',
|
||||
}),
|
||||
};
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"dependencies": {},
|
||||
"devDependencies": {},
|
||||
"components": []
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
// This is a Next.js 15 compatible version of the GluestackUIProvider
|
||||
'use client';
|
||||
import React, { useEffect, useLayoutEffect } from 'react';
|
||||
import { config } from './config';
|
||||
import { OverlayProvider } from '@gluestack-ui/core/overlay/creator';
|
||||
import { ToastProvider } from '@gluestack-ui/core/toast/creator';
|
||||
import { setFlushStyles } from '@gluestack-ui/utils/nativewind-utils';
|
||||
import { script } from './script';
|
||||
|
||||
const variableStyleTagId = 'nativewind-style';
|
||||
const createStyle = (styleTagId: string) => {
|
||||
const style = document.createElement('style');
|
||||
style.id = styleTagId;
|
||||
style.appendChild(document.createTextNode(''));
|
||||
return style;
|
||||
};
|
||||
|
||||
export const useSafeLayoutEffect =
|
||||
typeof window !== 'undefined' ? useLayoutEffect : useEffect;
|
||||
|
||||
export function GluestackUIProvider({
|
||||
mode = 'light',
|
||||
...props
|
||||
}: {
|
||||
mode?: 'light' | 'dark' | 'system';
|
||||
children?: React.ReactNode;
|
||||
}) {
|
||||
let cssVariablesWithMode = ``;
|
||||
Object.keys(config).forEach((configKey) => {
|
||||
cssVariablesWithMode +=
|
||||
configKey === 'dark' ? `\n .dark {\n ` : `\n:root {\n`;
|
||||
const cssVariables = Object.keys(
|
||||
config[configKey as keyof typeof config]
|
||||
).reduce((acc: string, curr: string) => {
|
||||
acc += `${curr}:${config[configKey as keyof typeof config][curr]}; `;
|
||||
return acc;
|
||||
}, '');
|
||||
cssVariablesWithMode += `${cssVariables} \n}`;
|
||||
});
|
||||
|
||||
setFlushStyles(cssVariablesWithMode);
|
||||
|
||||
const handleMediaQuery = React.useCallback((e: MediaQueryListEvent) => {
|
||||
script(e.matches ? 'dark' : 'light');
|
||||
}, []);
|
||||
|
||||
useSafeLayoutEffect(() => {
|
||||
if (mode !== 'system') {
|
||||
const documentElement = document.documentElement;
|
||||
if (documentElement) {
|
||||
documentElement.classList.add(mode);
|
||||
documentElement.classList.remove(mode === 'light' ? 'dark' : 'light');
|
||||
documentElement.style.colorScheme = mode;
|
||||
}
|
||||
}
|
||||
}, [mode]);
|
||||
|
||||
useSafeLayoutEffect(() => {
|
||||
if (mode !== 'system') return;
|
||||
const media = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
|
||||
media.addListener(handleMediaQuery);
|
||||
|
||||
return () => media.removeListener(handleMediaQuery);
|
||||
}, [handleMediaQuery]);
|
||||
|
||||
useSafeLayoutEffect(() => {
|
||||
if (typeof window !== 'undefined') {
|
||||
const documentElement = document.documentElement;
|
||||
if (documentElement) {
|
||||
const head = documentElement.querySelector('head');
|
||||
let style = head?.querySelector(`[id='${variableStyleTagId}']`);
|
||||
if (!style) {
|
||||
style = createStyle(variableStyleTagId);
|
||||
style.innerHTML = cssVariablesWithMode;
|
||||
if (head) head.appendChild(style);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<OverlayProvider>
|
||||
<ToastProvider>{props.children}</ToastProvider>
|
||||
</OverlayProvider>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { config } from './config';
|
||||
import { View, ViewProps } from 'react-native';
|
||||
import { OverlayProvider } from '@gluestack-ui/core/overlay/creator';
|
||||
import { ToastProvider } from '@gluestack-ui/core/toast/creator';
|
||||
import { useColorScheme } from 'nativewind';
|
||||
|
||||
export type ModeType = 'light' | 'dark' | 'system';
|
||||
|
||||
export function GluestackUIProvider({
|
||||
mode = 'light',
|
||||
...props
|
||||
}: {
|
||||
mode?: ModeType;
|
||||
children?: React.ReactNode;
|
||||
style?: ViewProps['style'];
|
||||
}) {
|
||||
const { colorScheme, setColorScheme } = useColorScheme();
|
||||
|
||||
useEffect(() => {
|
||||
setColorScheme(mode);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [mode]);
|
||||
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
config[colorScheme!],
|
||||
{ flex: 1, height: '100%', width: '100%' },
|
||||
props.style,
|
||||
]}
|
||||
>
|
||||
<OverlayProvider>
|
||||
<ToastProvider>{props.children}</ToastProvider>
|
||||
</OverlayProvider>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
'use client';
|
||||
import React, { useEffect, useLayoutEffect } from 'react';
|
||||
import { config } from './config';
|
||||
import { OverlayProvider } from '@gluestack-ui/core/overlay/creator';
|
||||
import { ToastProvider } from '@gluestack-ui/core/toast/creator';
|
||||
import { setFlushStyles } from '@gluestack-ui/utils/nativewind-utils';
|
||||
import { script } from './script';
|
||||
|
||||
export type ModeType = 'light' | 'dark' | 'system';
|
||||
|
||||
const variableStyleTagId = 'nativewind-style';
|
||||
const createStyle = (styleTagId: string) => {
|
||||
const style = document.createElement('style');
|
||||
style.id = styleTagId;
|
||||
style.appendChild(document.createTextNode(''));
|
||||
return style;
|
||||
};
|
||||
|
||||
export const useSafeLayoutEffect =
|
||||
typeof window !== 'undefined' ? useLayoutEffect : useEffect;
|
||||
|
||||
export function GluestackUIProvider({
|
||||
mode = 'light',
|
||||
...props
|
||||
}: {
|
||||
mode?: ModeType;
|
||||
children?: React.ReactNode;
|
||||
}) {
|
||||
let cssVariablesWithMode = ``;
|
||||
Object.keys(config).forEach((configKey) => {
|
||||
cssVariablesWithMode +=
|
||||
configKey === 'dark' ? `\n .dark {\n ` : `\n:root {\n`;
|
||||
const cssVariables = Object.keys(
|
||||
config[configKey as keyof typeof config]
|
||||
).reduce((acc: string, curr: string) => {
|
||||
acc += `${curr}:${config[configKey as keyof typeof config][curr]}; `;
|
||||
return acc;
|
||||
}, '');
|
||||
cssVariablesWithMode += `${cssVariables} \n}`;
|
||||
});
|
||||
|
||||
setFlushStyles(cssVariablesWithMode);
|
||||
|
||||
const handleMediaQuery = React.useCallback((e: MediaQueryListEvent) => {
|
||||
script(e.matches ? 'dark' : 'light');
|
||||
}, []);
|
||||
|
||||
useSafeLayoutEffect(() => {
|
||||
if (mode !== 'system') {
|
||||
const documentElement = document.documentElement;
|
||||
if (documentElement) {
|
||||
documentElement.classList.add(mode);
|
||||
documentElement.classList.remove(mode === 'light' ? 'dark' : 'light');
|
||||
documentElement.style.colorScheme = mode;
|
||||
}
|
||||
}
|
||||
}, [mode]);
|
||||
|
||||
useSafeLayoutEffect(() => {
|
||||
if (mode !== 'system') return;
|
||||
const media = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
|
||||
media.addListener(handleMediaQuery);
|
||||
|
||||
return () => media.removeListener(handleMediaQuery);
|
||||
}, [handleMediaQuery]);
|
||||
|
||||
useSafeLayoutEffect(() => {
|
||||
if (typeof window !== 'undefined') {
|
||||
const documentElement = document.documentElement;
|
||||
if (documentElement) {
|
||||
const head = documentElement.querySelector('head');
|
||||
let style = head?.querySelector(`[id='${variableStyleTagId}']`);
|
||||
if (!style) {
|
||||
style = createStyle(variableStyleTagId);
|
||||
style.innerHTML = cssVariablesWithMode;
|
||||
if (head) head.appendChild(style);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<script
|
||||
suppressHydrationWarning
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `(${script.toString()})('${mode}')`,
|
||||
}}
|
||||
/>
|
||||
<OverlayProvider>
|
||||
<ToastProvider>{props.children}</ToastProvider>
|
||||
</OverlayProvider>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
export const script = (mode: string) => {
|
||||
const documentElement = document.documentElement;
|
||||
|
||||
function getSystemColorMode() {
|
||||
return window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||
? 'dark'
|
||||
: 'light';
|
||||
}
|
||||
|
||||
try {
|
||||
const isSystem = mode === 'system';
|
||||
const theme = isSystem ? getSystemColorMode() : mode;
|
||||
documentElement.classList.remove(theme === 'light' ? 'dark' : 'light');
|
||||
documentElement.classList.add(theme);
|
||||
documentElement.style.colorScheme = theme;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,240 @@
|
||||
'use client'
|
||||
import type { VariantProps } from '@gluestack-ui/utils/nativewind-utils'
|
||||
import type {
|
||||
MotionComponentProps,
|
||||
} from '@legendapp/motion'
|
||||
import type { ViewStyle } from 'react-native'
|
||||
import { createToastHook } from '@gluestack-ui/core/toast/creator'
|
||||
import { tva, useStyleContext, withStyleContext } from '@gluestack-ui/utils/nativewind-utils'
|
||||
import {
|
||||
AnimatePresence,
|
||||
Motion,
|
||||
} from '@legendapp/motion'
|
||||
import { cssInterop } from 'nativewind'
|
||||
|
||||
import React from 'react'
|
||||
import { AccessibilityInfo, Text, View } from 'react-native'
|
||||
|
||||
type IMotionViewProps = React.ComponentProps<typeof View>
|
||||
& MotionComponentProps<typeof View, ViewStyle, unknown, unknown, unknown>
|
||||
|
||||
const MotionView = Motion.View as React.ComponentType<IMotionViewProps>
|
||||
|
||||
const useToast = createToastHook(MotionView, AnimatePresence)
|
||||
const SCOPE = 'TOAST'
|
||||
|
||||
cssInterop(MotionView, { className: 'style' })
|
||||
|
||||
const toastStyle = tva({
|
||||
base: 'p-4 m-1 rounded-md gap-1 web:pointer-events-auto shadow-hard-5 border-outline-100',
|
||||
variants: {
|
||||
action: {
|
||||
error: 'bg-error-800',
|
||||
warning: 'bg-warning-700',
|
||||
success: 'bg-success-700',
|
||||
info: 'bg-info-700',
|
||||
muted: 'bg-background-800',
|
||||
},
|
||||
|
||||
variant: {
|
||||
solid: '',
|
||||
outline: 'border bg-background-0',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const toastTitleStyle = tva({
|
||||
base: 'text-typography-0 font-medium font-body tracking-md text-left',
|
||||
variants: {
|
||||
isTruncated: {
|
||||
true: '',
|
||||
},
|
||||
bold: {
|
||||
true: 'font-bold',
|
||||
},
|
||||
underline: {
|
||||
true: 'underline',
|
||||
},
|
||||
strikeThrough: {
|
||||
true: 'line-through',
|
||||
},
|
||||
size: {
|
||||
'2xs': 'text-2xs',
|
||||
'xs': 'text-xs',
|
||||
'sm': 'text-sm',
|
||||
'md': 'text-base',
|
||||
'lg': 'text-lg',
|
||||
'xl': 'text-xl',
|
||||
'2xl': 'text-2xl',
|
||||
'3xl': 'text-3xl',
|
||||
'4xl': 'text-4xl',
|
||||
'5xl': 'text-5xl',
|
||||
'6xl': 'text-6xl',
|
||||
},
|
||||
},
|
||||
parentVariants: {
|
||||
variant: {
|
||||
solid: '',
|
||||
outline: '',
|
||||
},
|
||||
action: {
|
||||
error: '',
|
||||
warning: '',
|
||||
success: '',
|
||||
info: '',
|
||||
muted: '',
|
||||
},
|
||||
},
|
||||
parentCompoundVariants: [
|
||||
{
|
||||
variant: 'outline',
|
||||
action: 'error',
|
||||
class: 'text-error-800',
|
||||
},
|
||||
{
|
||||
variant: 'outline',
|
||||
action: 'warning',
|
||||
class: 'text-warning-800',
|
||||
},
|
||||
{
|
||||
variant: 'outline',
|
||||
action: 'success',
|
||||
class: 'text-success-800',
|
||||
},
|
||||
{
|
||||
variant: 'outline',
|
||||
action: 'info',
|
||||
class: 'text-info-800',
|
||||
},
|
||||
{
|
||||
variant: 'outline',
|
||||
action: 'muted',
|
||||
class: 'text-background-800',
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
const toastDescriptionStyle = tva({
|
||||
base: 'font-normal font-body tracking-md text-left',
|
||||
variants: {
|
||||
isTruncated: {
|
||||
true: '',
|
||||
},
|
||||
bold: {
|
||||
true: 'font-bold',
|
||||
},
|
||||
underline: {
|
||||
true: 'underline',
|
||||
},
|
||||
strikeThrough: {
|
||||
true: 'line-through',
|
||||
},
|
||||
size: {
|
||||
'2xs': 'text-2xs',
|
||||
'xs': 'text-xs',
|
||||
'sm': 'text-sm',
|
||||
'md': 'text-base',
|
||||
'lg': 'text-lg',
|
||||
'xl': 'text-xl',
|
||||
'2xl': 'text-2xl',
|
||||
'3xl': 'text-3xl',
|
||||
'4xl': 'text-4xl',
|
||||
'5xl': 'text-5xl',
|
||||
'6xl': 'text-6xl',
|
||||
},
|
||||
},
|
||||
parentVariants: {
|
||||
variant: {
|
||||
solid: 'text-typography-50',
|
||||
outline: 'text-typography-900',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const Root = withStyleContext(View, SCOPE)
|
||||
type IToastProps = React.ComponentProps<typeof Root> & {
|
||||
className?: string
|
||||
} & VariantProps<typeof toastStyle>
|
||||
|
||||
const Toast = React.forwardRef<React.ComponentRef<typeof Root>, IToastProps>(
|
||||
(
|
||||
{ className, variant = 'solid', action = 'muted', ...props },
|
||||
ref,
|
||||
) => {
|
||||
return (
|
||||
<Root
|
||||
ref={ref}
|
||||
className={toastStyle({ variant, action, class: className })}
|
||||
context={{ variant, action }}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
type IToastTitleProps = React.ComponentProps<typeof Text> & {
|
||||
className?: string
|
||||
} & VariantProps<typeof toastTitleStyle>
|
||||
|
||||
const ToastTitle = React.forwardRef<
|
||||
React.ComponentRef<typeof Text>,
|
||||
IToastTitleProps
|
||||
>(({ className, size = 'md', children, ...props }, ref) => {
|
||||
const { variant: parentVariant, action: parentAction }
|
||||
= useStyleContext(SCOPE)
|
||||
React.useEffect(() => {
|
||||
// Issue from react-native side
|
||||
// Hack for now, will fix this later
|
||||
AccessibilityInfo.announceForAccessibility(children as string)
|
||||
}, [children])
|
||||
|
||||
return (
|
||||
<Text
|
||||
{...props}
|
||||
ref={ref}
|
||||
aria-live="assertive"
|
||||
aria-atomic="true"
|
||||
role="alert"
|
||||
className={toastTitleStyle({
|
||||
size,
|
||||
class: className,
|
||||
parentVariants: {
|
||||
variant: parentVariant,
|
||||
action: parentAction,
|
||||
},
|
||||
})}
|
||||
>
|
||||
{children}
|
||||
</Text>
|
||||
)
|
||||
})
|
||||
|
||||
type IToastDescriptionProps = React.ComponentProps<typeof Text> & {
|
||||
className?: string
|
||||
} & VariantProps<typeof toastDescriptionStyle>
|
||||
|
||||
const ToastDescription = React.forwardRef<
|
||||
React.ComponentRef<typeof Text>,
|
||||
IToastDescriptionProps
|
||||
>(({ className, size = 'md', ...props }, ref) => {
|
||||
const { variant: parentVariant } = useStyleContext(SCOPE)
|
||||
return (
|
||||
<Text
|
||||
ref={ref}
|
||||
{...props}
|
||||
className={toastDescriptionStyle({
|
||||
size,
|
||||
class: className,
|
||||
parentVariants: {
|
||||
variant: parentVariant,
|
||||
},
|
||||
})}
|
||||
/>
|
||||
)
|
||||
})
|
||||
|
||||
Toast.displayName = 'Toast'
|
||||
ToastTitle.displayName = 'ToastTitle'
|
||||
ToastDescription.displayName = 'ToastDescription'
|
||||
|
||||
export { Toast, ToastDescription, ToastTitle, useToast }
|
||||
|
After Width: | Height: | Size: 122 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="800" height="430.493" viewBox="0 0 800 430.493" xmlns:xlink="http://www.w3.org/1999/xlink" role="img" artist="Katerina Limpitsouni" source="https://undraw.co/"><g transform="translate(-368.866 -254.661)"><ellipse cx="149.199" cy="26.107" rx="149.199" ry="26.107" transform="translate(368.866 481.453)" fill="#090814"/><ellipse cx="191.906" cy="26.107" rx="191.906" ry="26.107" transform="translate(785.054 632.939)" fill="#090814"/><path d="M527.508,703.017a139.876,139.876,0,0,1-34.251-3.807l1.007-3.942c61.693,15.766,176.375-20.484,188.785-59.673l3.879,1.228C676.145,670.877,591.731,703.017,527.508,703.017Z" transform="translate(229.585 -61.587)" fill="#2f2e41"/><path d="M422.29,529.663c-14.453-3.693-21.922-7.965-24.218-13.849-2.856-7.316,2.5-15.561,9.919-26.972,11.315-17.409,26.811-41.252,24.3-77.646-1.418-20.568-8.78-35.7-21.879-44.978-26.656-18.878-68.707-7.567-69.129-7.451l-1.085-3.921c1.8-.5,44.3-11.949,72.551,8.041,14.153,10.017,22.094,26.176,23.6,48.028,2.6,37.751-13.977,63.262-24.949,80.143-6.756,10.394-11.637,17.9-9.54,23.275,1.748,4.477,8.559,8.1,21.436,11.386Z" transform="translate(278.852 29.977)" fill="#2f2e41"/><path d="M816.039,661.123,795.384,696.05s-26.3,13.971-7.807,14.382,105.6,0,105.6,0,16.847,0-9.862-14.793l-19.389-37.478Z" transform="translate(136.968 -68.851)" fill="#e6e6e6"/><rect width="348.044" height="242.439" rx="18.046" transform="translate(801.887 355.38)" fill="#e6e6e6"/><path d="M2.712,0h319.2a2.712,2.712,0,0,1,2.712,2.712V193.294a2.712,2.712,0,0,1-2.712,2.712H2.712A2.712,2.712,0,0,1,0,193.294V2.712A2.712,2.712,0,0,1,2.712,0Z" transform="translate(813.598 368.323)" fill="#6c63ff"/><circle cx="2.055" cy="2.055" r="2.055" transform="translate(973.854 360.516)" fill="#f2f2f2"/><path d="M957.868,621.322v21.252a12.236,12.236,0,0,1-12.239,12.237H622.061a12.236,12.236,0,0,1-12.237-12.239V621.322Z" transform="translate(192.063 -56.993)" fill="#e6e6e6"/><path d="M864.04,660.444v4.109H642.968v-3.288l.3-.822,5.449-14.793h210.8Z" transform="translate(202.681 3.326)" fill="#f2f2f2"/><path d="M1096.367,755.317c-.4,1.722-1.923,3.538-5.358,5.256-12.327,6.164-37.393-1.644-37.393-1.644s-19.313-3.287-19.313-11.917a15.428,15.428,0,0,1,1.693-1.007c5.183-2.742,22.367-9.508,52.837.286a12.709,12.709,0,0,1,5.792,3.8C1095.859,751.534,1096.828,753.367,1096.367,755.317Z" transform="translate(55.429 -95.57)" fill="#f2f2f2"/><path d="M1097.171,755.317c-15.089,5.782-28.538,6.213-42.34-3.374-6.961-4.832-13.285-6.028-18.031-5.938,5.183-2.742,22.367-9.508,52.837.286a12.708,12.708,0,0,1,5.792,3.8C1096.663,751.534,1097.631,753.367,1097.171,755.317Z" transform="translate(54.626 -95.57)" fill="#e6e6e6"/><path d="M864.04,667.466v4.109H642.968v-3.288l.3-.822Z" transform="translate(202.681 -3.696)" opacity="0.1"/><path d="M794.285,488.635H592.452a6.086,6.086,0,0,1-6.079-6.079V414.467a6.086,6.086,0,0,1,6.079-6.079H794.285a6.086,6.086,0,0,1,6.079,6.079v68.088A6.086,6.086,0,0,1,794.285,488.635Z" transform="translate(-181.314 -153.727)" fill="#6c63ff"/><rect width="53.498" height="6.079" transform="translate(436.671 272.899)" fill="#f2f2f2"/><circle cx="3.648" cy="3.648" r="3.648" transform="translate(557.041 272.899)" fill="#f2f2f2"/><circle cx="3.648" cy="3.648" r="3.648" transform="translate(569.2 272.899)" fill="#f2f2f2"/><circle cx="3.648" cy="3.648" r="3.648" transform="translate(581.358 272.899)" fill="#f2f2f2"/><path d="M794.285,601.689H592.452a6.087,6.087,0,0,1-6.079-6.079V527.521a6.086,6.086,0,0,1,6.079-6.079H794.285a6.086,6.086,0,0,1,6.079,6.079v68.088A6.086,6.086,0,0,1,794.285,601.689Z" transform="translate(-181.314 -180.455)" fill="#e6e6e6"/><rect width="53.498" height="6.079" transform="translate(436.671 359.225)" fill="#6c63ff"/><circle cx="3.648" cy="3.648" r="3.648" transform="translate(557.041 359.225)" fill="#6c63ff"/><circle cx="3.648" cy="3.648" r="3.648" transform="translate(569.2 359.225)" fill="#6c63ff"/><circle cx="3.648" cy="3.648" r="3.648" transform="translate(581.358 359.225)" fill="#6c63ff"/><path d="M794.285,714.743H592.452a6.087,6.087,0,0,1-6.079-6.079V640.575a6.086,6.086,0,0,1,6.079-6.079H794.285a6.086,6.086,0,0,1,6.079,6.079v68.089A6.086,6.086,0,0,1,794.285,714.743Z" transform="translate(-181.314 -207.183)" fill="#e6e6e6"/><rect width="53.498" height="6.079" transform="translate(436.671 445.552)" fill="#6c63ff"/><circle cx="3.648" cy="3.648" r="3.648" transform="translate(557.041 445.552)" fill="#6c63ff"/><circle cx="3.648" cy="3.648" r="3.648" transform="translate(569.2 445.552)" fill="#6c63ff"/><circle cx="3.648" cy="3.648" r="3.648" transform="translate(581.358 445.552)" fill="#6c63ff"/><g transform="translate(933.489 421.851)"><path d="M0,0H88.949V88.949H0Z" fill="none"/><path d="M39.062,2A37.062,37.062,0,0,1,76.125,39.062a37.06,37.06,0,1,1-74.11,1.2L2,39.062l.015-1.038A37.061,37.061,0,0,1,39.062,2ZM39.1,50.181l-.471.026a3.706,3.706,0,0,0,0,7.361l.434.026.471-.026a3.706,3.706,0,0,0,0-7.361Zm-.037-32.362a3.706,3.706,0,0,0-3.68,3.272l-.026.43Line truncated
|
||||
|
After Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 171 KiB |
|
After Width: | Height: | Size: 52 KiB |
@@ -0,0 +1,3 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
@@ -0,0 +1,71 @@
|
||||
import type { IToastProps } from '@gluestack-ui/core/lib/esm/toast/creator/types'
|
||||
import React, { useRef } from 'react'
|
||||
import { Toast, ToastDescription, ToastTitle, useToast as useGluestackToast } from '@/components/ui/toast'
|
||||
|
||||
type ToastVariant = 'solid' | 'outline'
|
||||
type ToastAction = 'error' | 'warning' | 'success' | 'info' | 'muted'
|
||||
|
||||
interface ShowToastOptions extends IToastProps {
|
||||
title?: React.ReactNode
|
||||
description?: React.ReactNode
|
||||
variant?: ToastVariant
|
||||
action?: ToastAction
|
||||
}
|
||||
|
||||
export function ToastProviderWrapper({ children }: { children: React.ReactNode }) {
|
||||
const toast = useGluestackToast()
|
||||
const toastRef = useRef(toast)
|
||||
toastRef.current = toast
|
||||
|
||||
return <>{children}</>
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义 Hook,用法:
|
||||
* const toast = useToast();
|
||||
* toast.show({ ... });
|
||||
* toast.close(id);
|
||||
* toast.closeAll();
|
||||
*/
|
||||
export function useToast() {
|
||||
const toast = useGluestackToast()
|
||||
|
||||
return {
|
||||
show: (options: ShowToastOptions) => {
|
||||
const id = Math.random().toString()
|
||||
|
||||
toast.show({
|
||||
id,
|
||||
placement: options.placement ?? 'top',
|
||||
duration: options.duration !== undefined ? options.duration : 3000,
|
||||
avoidKeyboard: options.avoidKeyboard ?? false,
|
||||
containerStyle: options.containerStyle,
|
||||
onCloseComplete: options.onCloseComplete,
|
||||
render: options.render
|
||||
? () => options.render!({ id })
|
||||
: ({ id }) => (
|
||||
<Toast
|
||||
nativeID={`toast-${id}`}
|
||||
variant={options.variant ?? 'solid'}
|
||||
action={options.action ?? 'muted'}
|
||||
>
|
||||
{options.title && <ToastTitle className="font-blod">{options.title}</ToastTitle>}
|
||||
{options.description && <ToastDescription>{options.description}</ToastDescription>}
|
||||
</Toast>
|
||||
),
|
||||
})
|
||||
|
||||
return id
|
||||
},
|
||||
|
||||
close: (id: string) => {
|
||||
toast.close(id)
|
||||
},
|
||||
|
||||
closeAll: () => {
|
||||
toast.closeAll()
|
||||
},
|
||||
|
||||
isActive: (id: string) => toast.isActive(id),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
darkMode: process.env.DARK_MODE ? process.env.DARK_MODE : 'class',
|
||||
content: [
|
||||
'./app/**/*.{html,js,jsx,ts,tsx,mdx}',
|
||||
'./components/**/*.{html,js,jsx,ts,tsx,mdx}',
|
||||
'./utils/**/*.{html,js,jsx,ts,tsx,mdx}',
|
||||
'./*.{html,js,jsx,ts,tsx,mdx}',
|
||||
'./src/**/*.{html,js,jsx,ts,tsx,mdx}',
|
||||
],
|
||||
presets: [require('nativewind/preset')],
|
||||
important: 'html',
|
||||
safelist: [
|
||||
{
|
||||
pattern:
|
||||
// eslint-disable-next-line regexp/no-dupe-disjunctions
|
||||
/(bg|border|text|stroke|fill)-(primary|secondary|tertiary|error|success|warning|info|typography|outline|background|indicator)-(0|50|100|200|300|400|500|600|700|800|900|950|white|gray|black|error|warning|muted|success|info|light|dark|primary)/,
|
||||
},
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
primary: {
|
||||
0: 'rgb(var(--color-primary-0)/<alpha-value>)',
|
||||
50: 'rgb(var(--color-primary-50)/<alpha-value>)',
|
||||
100: 'rgb(var(--color-primary-100)/<alpha-value>)',
|
||||
200: 'rgb(var(--color-primary-200)/<alpha-value>)',
|
||||
300: 'rgb(var(--color-primary-300)/<alpha-value>)',
|
||||
400: 'rgb(var(--color-primary-400)/<alpha-value>)',
|
||||
500: 'rgb(var(--color-primary-500)/<alpha-value>)',
|
||||
600: 'rgb(var(--color-primary-600)/<alpha-value>)',
|
||||
700: 'rgb(var(--color-primary-700)/<alpha-value>)',
|
||||
800: 'rgb(var(--color-primary-800)/<alpha-value>)',
|
||||
900: 'rgb(var(--color-primary-900)/<alpha-value>)',
|
||||
950: 'rgb(var(--color-primary-950)/<alpha-value>)',
|
||||
},
|
||||
secondary: {
|
||||
0: 'rgb(var(--color-secondary-0)/<alpha-value>)',
|
||||
50: 'rgb(var(--color-secondary-50)/<alpha-value>)',
|
||||
100: 'rgb(var(--color-secondary-100)/<alpha-value>)',
|
||||
200: 'rgb(var(--color-secondary-200)/<alpha-value>)',
|
||||
300: 'rgb(var(--color-secondary-300)/<alpha-value>)',
|
||||
400: 'rgb(var(--color-secondary-400)/<alpha-value>)',
|
||||
500: 'rgb(var(--color-secondary-500)/<alpha-value>)',
|
||||
600: 'rgb(var(--color-secondary-600)/<alpha-value>)',
|
||||
700: 'rgb(var(--color-secondary-700)/<alpha-value>)',
|
||||
800: 'rgb(var(--color-secondary-800)/<alpha-value>)',
|
||||
900: 'rgb(var(--color-secondary-900)/<alpha-value>)',
|
||||
950: 'rgb(var(--color-secondary-950)/<alpha-value>)',
|
||||
},
|
||||
tertiary: {
|
||||
50: 'rgb(var(--color-tertiary-50)/<alpha-value>)',
|
||||
100: 'rgb(var(--color-tertiary-100)/<alpha-value>)',
|
||||
200: 'rgb(var(--color-tertiary-200)/<alpha-value>)',
|
||||
300: 'rgb(var(--color-tertiary-300)/<alpha-value>)',
|
||||
400: 'rgb(var(--color-tertiary-400)/<alpha-value>)',
|
||||
500: 'rgb(var(--color-tertiary-500)/<alpha-value>)',
|
||||
600: 'rgb(var(--color-tertiary-600)/<alpha-value>)',
|
||||
700: 'rgb(var(--color-tertiary-700)/<alpha-value>)',
|
||||
800: 'rgb(var(--color-tertiary-800)/<alpha-value>)',
|
||||
900: 'rgb(var(--color-tertiary-900)/<alpha-value>)',
|
||||
950: 'rgb(var(--color-tertiary-950)/<alpha-value>)',
|
||||
},
|
||||
error: {
|
||||
0: 'rgb(var(--color-error-0)/<alpha-value>)',
|
||||
50: 'rgb(var(--color-error-50)/<alpha-value>)',
|
||||
100: 'rgb(var(--color-error-100)/<alpha-value>)',
|
||||
200: 'rgb(var(--color-error-200)/<alpha-value>)',
|
||||
300: 'rgb(var(--color-error-300)/<alpha-value>)',
|
||||
400: 'rgb(var(--color-error-400)/<alpha-value>)',
|
||||
500: 'rgb(var(--color-error-500)/<alpha-value>)',
|
||||
600: 'rgb(var(--color-error-600)/<alpha-value>)',
|
||||
700: 'rgb(var(--color-error-700)/<alpha-value>)',
|
||||
800: 'rgb(var(--color-error-800)/<alpha-value>)',
|
||||
900: 'rgb(var(--color-error-900)/<alpha-value>)',
|
||||
950: 'rgb(var(--color-error-950)/<alpha-value>)',
|
||||
},
|
||||
success: {
|
||||
0: 'rgb(var(--color-success-0)/<alpha-value>)',
|
||||
50: 'rgb(var(--color-success-50)/<alpha-value>)',
|
||||
100: 'rgb(var(--color-success-100)/<alpha-value>)',
|
||||
200: 'rgb(var(--color-success-200)/<alpha-value>)',
|
||||
300: 'rgb(var(--color-success-300)/<alpha-value>)',
|
||||
400: 'rgb(var(--color-success-400)/<alpha-value>)',
|
||||
500: 'rgb(var(--color-success-500)/<alpha-value>)',
|
||||
600: 'rgb(var(--color-success-600)/<alpha-value>)',
|
||||
700: 'rgb(var(--color-success-700)/<alpha-value>)',
|
||||
800: 'rgb(var(--color-success-800)/<alpha-value>)',
|
||||
900: 'rgb(var(--color-success-900)/<alpha-value>)',
|
||||
950: 'rgb(var(--color-success-950)/<alpha-value>)',
|
||||
},
|
||||
warning: {
|
||||
0: 'rgb(var(--color-warning-0)/<alpha-value>)',
|
||||
50: 'rgb(var(--color-warning-50)/<alpha-value>)',
|
||||
100: 'rgb(var(--color-warning-100)/<alpha-value>)',
|
||||
200: 'rgb(var(--color-warning-200)/<alpha-value>)',
|
||||
300: 'rgb(var(--color-warning-300)/<alpha-value>)',
|
||||
400: 'rgb(var(--color-warning-400)/<alpha-value>)',
|
||||
500: 'rgb(var(--color-warning-500)/<alpha-value>)',
|
||||
600: 'rgb(var(--color-warning-600)/<alpha-value>)',
|
||||
700: 'rgb(var(--color-warning-700)/<alpha-value>)',
|
||||
800: 'rgb(var(--color-warning-800)/<alpha-value>)',
|
||||
900: 'rgb(var(--color-warning-900)/<alpha-value>)',
|
||||
950: 'rgb(var(--color-warning-950)/<alpha-value>)',
|
||||
},
|
||||
info: {
|
||||
0: 'rgb(var(--color-info-0)/<alpha-value>)',
|
||||
50: 'rgb(var(--color-info-50)/<alpha-value>)',
|
||||
100: 'rgb(var(--color-info-100)/<alpha-value>)',
|
||||
200: 'rgb(var(--color-info-200)/<alpha-value>)',
|
||||
300: 'rgb(var(--color-info-300)/<alpha-value>)',
|
||||
400: 'rgb(var(--color-info-400)/<alpha-value>)',
|
||||
500: 'rgb(var(--color-info-500)/<alpha-value>)',
|
||||
600: 'rgb(var(--color-info-600)/<alpha-value>)',
|
||||
700: 'rgb(var(--color-info-700)/<alpha-value>)',
|
||||
800: 'rgb(var(--color-info-800)/<alpha-value>)',
|
||||
900: 'rgb(var(--color-info-900)/<alpha-value>)',
|
||||
950: 'rgb(var(--color-info-950)/<alpha-value>)',
|
||||
},
|
||||
typography: {
|
||||
0: 'rgb(var(--color-typography-0)/<alpha-value>)',
|
||||
50: 'rgb(var(--color-typography-50)/<alpha-value>)',
|
||||
100: 'rgb(var(--color-typography-100)/<alpha-value>)',
|
||||
200: 'rgb(var(--color-typography-200)/<alpha-value>)',
|
||||
300: 'rgb(var(--color-typography-300)/<alpha-value>)',
|
||||
400: 'rgb(var(--color-typography-400)/<alpha-value>)',
|
||||
500: 'rgb(var(--color-typography-500)/<alpha-value>)',
|
||||
600: 'rgb(var(--color-typography-600)/<alpha-value>)',
|
||||
700: 'rgb(var(--color-typography-700)/<alpha-value>)',
|
||||
800: 'rgb(var(--color-typography-800)/<alpha-value>)',
|
||||
900: 'rgb(var(--color-typography-900)/<alpha-value>)',
|
||||
950: 'rgb(var(--color-typography-950)/<alpha-value>)',
|
||||
white: '#FFFFFF',
|
||||
gray: '#D4D4D4',
|
||||
black: '#181718',
|
||||
},
|
||||
outline: {
|
||||
0: 'rgb(var(--color-outline-0)/<alpha-value>)',
|
||||
50: 'rgb(var(--color-outline-50)/<alpha-value>)',
|
||||
100: 'rgb(var(--color-outline-100)/<alpha-value>)',
|
||||
200: 'rgb(var(--color-outline-200)/<alpha-value>)',
|
||||
300: 'rgb(var(--color-outline-300)/<alpha-value>)',
|
||||
400: 'rgb(var(--color-outline-400)/<alpha-value>)',
|
||||
500: 'rgb(var(--color-outline-500)/<alpha-value>)',
|
||||
600: 'rgb(var(--color-outline-600)/<alpha-value>)',
|
||||
700: 'rgb(var(--color-outline-700)/<alpha-value>)',
|
||||
800: 'rgb(var(--color-outline-800)/<alpha-value>)',
|
||||
900: 'rgb(var(--color-outline-900)/<alpha-value>)',
|
||||
950: 'rgb(var(--color-outline-950)/<alpha-value>)',
|
||||
},
|
||||
background: {
|
||||
0: 'rgb(var(--color-background-0)/<alpha-value>)',
|
||||
50: 'rgb(var(--color-background-50)/<alpha-value>)',
|
||||
100: 'rgb(var(--color-background-100)/<alpha-value>)',
|
||||
200: 'rgb(var(--color-background-200)/<alpha-value>)',
|
||||
300: 'rgb(var(--color-background-300)/<alpha-value>)',
|
||||
400: 'rgb(var(--color-background-400)/<alpha-value>)',
|
||||
500: 'rgb(var(--color-background-500)/<alpha-value>)',
|
||||
600: 'rgb(var(--color-background-600)/<alpha-value>)',
|
||||
700: 'rgb(var(--color-background-700)/<alpha-value>)',
|
||||
800: 'rgb(var(--color-background-800)/<alpha-value>)',
|
||||
900: 'rgb(var(--color-background-900)/<alpha-value>)',
|
||||
950: 'rgb(var(--color-background-950)/<alpha-value>)',
|
||||
error: 'rgb(var(--color-background-error)/<alpha-value>)',
|
||||
warning: 'rgb(var(--color-background-warning)/<alpha-value>)',
|
||||
muted: 'rgb(var(--color-background-muted)/<alpha-value>)',
|
||||
success: 'rgb(var(--color-background-success)/<alpha-value>)',
|
||||
info: 'rgb(var(--color-background-info)/<alpha-value>)',
|
||||
light: '#FBFBFB',
|
||||
dark: '#181719',
|
||||
},
|
||||
indicator: {
|
||||
primary: 'rgb(var(--color-indicator-primary)/<alpha-value>)',
|
||||
info: 'rgb(var(--color-indicator-info)/<alpha-value>)',
|
||||
error: 'rgb(var(--color-indicator-error)/<alpha-value>)',
|
||||
},
|
||||
},
|
||||
fontFamily: {
|
||||
'heading': undefined,
|
||||
'body': undefined,
|
||||
'mono': undefined,
|
||||
'jakarta': ['var(--font-plus-jakarta-sans)'],
|
||||
'roboto': ['var(--font-roboto)'],
|
||||
'code': ['var(--font-source-code-pro)'],
|
||||
'inter': ['var(--font-inter)'],
|
||||
'space-mono': ['var(--font-space-mono)'],
|
||||
},
|
||||
fontWeight: {
|
||||
extrablack: '950',
|
||||
},
|
||||
fontSize: {
|
||||
'2xs': '10px',
|
||||
},
|
||||
boxShadow: {
|
||||
'hard-1': '-2px 2px 8px 0px rgba(38, 38, 38, 0.20)',
|
||||
'hard-2': '0px 3px 10px 0px rgba(38, 38, 38, 0.20)',
|
||||
'hard-3': '2px 2px 8px 0px rgba(38, 38, 38, 0.20)',
|
||||
'hard-4': '0px -3px 10px 0px rgba(38, 38, 38, 0.20)',
|
||||
'hard-5': '0px 2px 10px 0px rgba(38, 38, 38, 0.10)',
|
||||
'soft-1': '0px 0px 10px rgba(38, 38, 38, 0.1)',
|
||||
'soft-2': '0px 0px 20px rgba(38, 38, 38, 0.2)',
|
||||
'soft-3': '0px 0px 30px rgba(38, 38, 38, 0.1)',
|
||||
'soft-4': '0px 0px 40px rgba(38, 38, 38, 0.1)',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"extends": "expo/tsconfig.base",
|
||||
"compilerOptions": {
|
||||
"baseUrl": "./",
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"src/*"
|
||||
]
|
||||
},
|
||||
"strict": true
|
||||
},
|
||||
"include": [
|
||||
"./**/*.ts",
|
||||
"./**/*.tsx",
|
||||
"./src/types/*.d.ts",
|
||||
"nativewind-env.d.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist"
|
||||
]
|
||||
}
|
||||