@djangocfg/ui-core 1.0.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 +135 -0
- package/package.json +111 -0
- package/src/components/accordion.tsx +56 -0
- package/src/components/alert-dialog.tsx +142 -0
- package/src/components/alert.tsx +59 -0
- package/src/components/aspect-ratio.tsx +7 -0
- package/src/components/avatar.tsx +50 -0
- package/src/components/badge.tsx +36 -0
- package/src/components/button-group.tsx +85 -0
- package/src/components/button.tsx +111 -0
- package/src/components/calendar.tsx +213 -0
- package/src/components/card.tsx +76 -0
- package/src/components/carousel.tsx +261 -0
- package/src/components/chart.tsx +369 -0
- package/src/components/checkbox.tsx +29 -0
- package/src/components/collapsible.tsx +11 -0
- package/src/components/combobox.tsx +182 -0
- package/src/components/command.tsx +170 -0
- package/src/components/context-menu.tsx +200 -0
- package/src/components/copy.tsx +144 -0
- package/src/components/dialog.tsx +122 -0
- package/src/components/drawer.tsx +137 -0
- package/src/components/empty.tsx +104 -0
- package/src/components/field.tsx +244 -0
- package/src/components/form.tsx +178 -0
- package/src/components/hover-card.tsx +29 -0
- package/src/components/image-with-fallback.tsx +170 -0
- package/src/components/index.ts +86 -0
- package/src/components/input-group.tsx +170 -0
- package/src/components/input-otp.tsx +81 -0
- package/src/components/input.tsx +22 -0
- package/src/components/item.tsx +195 -0
- package/src/components/kbd.tsx +28 -0
- package/src/components/label.tsx +26 -0
- package/src/components/multi-select.tsx +222 -0
- package/src/components/og-image.tsx +47 -0
- package/src/components/popover.tsx +33 -0
- package/src/components/portal.tsx +106 -0
- package/src/components/preloader.tsx +250 -0
- package/src/components/progress.tsx +28 -0
- package/src/components/radio-group.tsx +43 -0
- package/src/components/resizable.tsx +111 -0
- package/src/components/scroll-area.tsx +102 -0
- package/src/components/section.tsx +58 -0
- package/src/components/select.tsx +158 -0
- package/src/components/separator.tsx +31 -0
- package/src/components/sheet.tsx +140 -0
- package/src/components/skeleton.tsx +15 -0
- package/src/components/slider.tsx +28 -0
- package/src/components/spinner.tsx +16 -0
- package/src/components/sticky.tsx +117 -0
- package/src/components/switch.tsx +29 -0
- package/src/components/table.tsx +120 -0
- package/src/components/tabs.tsx +238 -0
- package/src/components/textarea.tsx +22 -0
- package/src/components/toast.tsx +129 -0
- package/src/components/toaster.tsx +41 -0
- package/src/components/toggle-group.tsx +61 -0
- package/src/components/toggle.tsx +45 -0
- package/src/components/token-icon.tsx +156 -0
- package/src/components/tooltip-provider-safe.tsx +43 -0
- package/src/components/tooltip.tsx +32 -0
- package/src/hooks/index.ts +15 -0
- package/src/hooks/useCopy.ts +41 -0
- package/src/hooks/useCountdown.ts +73 -0
- package/src/hooks/useDebounce.ts +25 -0
- package/src/hooks/useDebouncedCallback.ts +58 -0
- package/src/hooks/useDebugTools.ts +52 -0
- package/src/hooks/useEventsBus.ts +53 -0
- package/src/hooks/useImageLoader.ts +95 -0
- package/src/hooks/useMediaQuery.ts +40 -0
- package/src/hooks/useMobile.tsx +22 -0
- package/src/hooks/useToast.ts +194 -0
- package/src/index.ts +14 -0
- package/src/lib/index.ts +2 -0
- package/src/lib/og-image.ts +151 -0
- package/src/lib/utils.ts +6 -0
- package/src/styles/base.css +20 -0
- package/src/styles/globals.css +12 -0
- package/src/styles/index.css +25 -0
- package/src/styles/sources.css +11 -0
- package/src/styles/theme/animations.css +65 -0
- package/src/styles/theme/dark.css +49 -0
- package/src/styles/theme/light.css +50 -0
- package/src/styles/theme/tokens.css +134 -0
- package/src/styles/theme.css +22 -0
- package/src/styles/utilities.css +187 -0
- package/src/types/index.ts +0 -0
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
// Inspired by react-hot-toast library
|
|
4
|
+
import * as React from 'react';
|
|
5
|
+
|
|
6
|
+
import type {
|
|
7
|
+
ToastActionElement,
|
|
8
|
+
ToastProps,
|
|
9
|
+
} from "../components/toast"
|
|
10
|
+
|
|
11
|
+
const TOAST_LIMIT = 1
|
|
12
|
+
const TOAST_REMOVE_DELAY = 1000000
|
|
13
|
+
|
|
14
|
+
type ToasterToast = ToastProps & {
|
|
15
|
+
id: string
|
|
16
|
+
title?: React.ReactNode
|
|
17
|
+
description?: React.ReactNode
|
|
18
|
+
action?: ToastActionElement
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const actionTypes = {
|
|
22
|
+
ADD_TOAST: "ADD_TOAST",
|
|
23
|
+
UPDATE_TOAST: "UPDATE_TOAST",
|
|
24
|
+
DISMISS_TOAST: "DISMISS_TOAST",
|
|
25
|
+
REMOVE_TOAST: "REMOVE_TOAST",
|
|
26
|
+
} as const
|
|
27
|
+
|
|
28
|
+
let count = 0
|
|
29
|
+
|
|
30
|
+
function genId() {
|
|
31
|
+
count = (count + 1) % Number.MAX_SAFE_INTEGER
|
|
32
|
+
return count.toString()
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
type ActionType = typeof actionTypes
|
|
36
|
+
|
|
37
|
+
type Action =
|
|
38
|
+
| {
|
|
39
|
+
type: ActionType["ADD_TOAST"]
|
|
40
|
+
toast: ToasterToast
|
|
41
|
+
}
|
|
42
|
+
| {
|
|
43
|
+
type: ActionType["UPDATE_TOAST"]
|
|
44
|
+
toast: Partial<ToasterToast>
|
|
45
|
+
}
|
|
46
|
+
| {
|
|
47
|
+
type: ActionType["DISMISS_TOAST"]
|
|
48
|
+
toastId?: ToasterToast["id"]
|
|
49
|
+
}
|
|
50
|
+
| {
|
|
51
|
+
type: ActionType["REMOVE_TOAST"]
|
|
52
|
+
toastId?: ToasterToast["id"]
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
interface State {
|
|
56
|
+
toasts: ToasterToast[]
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>()
|
|
60
|
+
|
|
61
|
+
const addToRemoveQueue = (toastId: string) => {
|
|
62
|
+
if (toastTimeouts.has(toastId)) {
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const timeout = setTimeout(() => {
|
|
67
|
+
toastTimeouts.delete(toastId)
|
|
68
|
+
dispatch({
|
|
69
|
+
type: "REMOVE_TOAST",
|
|
70
|
+
toastId: toastId,
|
|
71
|
+
})
|
|
72
|
+
}, TOAST_REMOVE_DELAY)
|
|
73
|
+
|
|
74
|
+
toastTimeouts.set(toastId, timeout)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export const reducer = (state: State, action: Action): State => {
|
|
78
|
+
switch (action.type) {
|
|
79
|
+
case "ADD_TOAST":
|
|
80
|
+
return {
|
|
81
|
+
...state,
|
|
82
|
+
toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
case "UPDATE_TOAST":
|
|
86
|
+
return {
|
|
87
|
+
...state,
|
|
88
|
+
toasts: state.toasts.map((t) =>
|
|
89
|
+
t.id === action.toast.id ? { ...t, ...action.toast } : t
|
|
90
|
+
),
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
case "DISMISS_TOAST": {
|
|
94
|
+
const { toastId } = action
|
|
95
|
+
|
|
96
|
+
// ! Side effects ! - This could be extracted into a dismissToast() action,
|
|
97
|
+
// but I'll keep it here for simplicity
|
|
98
|
+
if (toastId) {
|
|
99
|
+
addToRemoveQueue(toastId)
|
|
100
|
+
} else {
|
|
101
|
+
state.toasts.forEach((toast) => {
|
|
102
|
+
addToRemoveQueue(toast.id)
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
...state,
|
|
108
|
+
toasts: state.toasts.map((t) =>
|
|
109
|
+
t.id === toastId || toastId === undefined
|
|
110
|
+
? {
|
|
111
|
+
...t,
|
|
112
|
+
open: false,
|
|
113
|
+
}
|
|
114
|
+
: t
|
|
115
|
+
),
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
case "REMOVE_TOAST":
|
|
119
|
+
if (action.toastId === undefined) {
|
|
120
|
+
return {
|
|
121
|
+
...state,
|
|
122
|
+
toasts: [],
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
...state,
|
|
127
|
+
toasts: state.toasts.filter((t) => t.id !== action.toastId),
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const listeners: Array<(state: State) => void> = []
|
|
133
|
+
|
|
134
|
+
let memoryState: State = { toasts: [] }
|
|
135
|
+
|
|
136
|
+
function dispatch(action: Action) {
|
|
137
|
+
memoryState = reducer(memoryState, action)
|
|
138
|
+
listeners.forEach((listener) => {
|
|
139
|
+
listener(memoryState)
|
|
140
|
+
})
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
type Toast = Omit<ToasterToast, "id">
|
|
144
|
+
|
|
145
|
+
function toast({ ...props }: Toast) {
|
|
146
|
+
const id = genId()
|
|
147
|
+
|
|
148
|
+
const update = (props: ToasterToast) =>
|
|
149
|
+
dispatch({
|
|
150
|
+
type: "UPDATE_TOAST",
|
|
151
|
+
toast: { ...props, id },
|
|
152
|
+
})
|
|
153
|
+
const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id })
|
|
154
|
+
|
|
155
|
+
dispatch({
|
|
156
|
+
type: "ADD_TOAST",
|
|
157
|
+
toast: {
|
|
158
|
+
...props,
|
|
159
|
+
id,
|
|
160
|
+
open: true,
|
|
161
|
+
onOpenChange: (open) => {
|
|
162
|
+
if (!open) dismiss()
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
id: id,
|
|
169
|
+
dismiss,
|
|
170
|
+
update,
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function useToast() {
|
|
175
|
+
const [state, setState] = React.useState<State>(memoryState)
|
|
176
|
+
|
|
177
|
+
React.useEffect(() => {
|
|
178
|
+
listeners.push(setState)
|
|
179
|
+
return () => {
|
|
180
|
+
const index = listeners.indexOf(setState)
|
|
181
|
+
if (index > -1) {
|
|
182
|
+
listeners.splice(index, 1)
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}, [state])
|
|
186
|
+
|
|
187
|
+
return {
|
|
188
|
+
...state,
|
|
189
|
+
toast,
|
|
190
|
+
dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export { useToast, toast }
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// @djangocfg/ui-core - Main Export File
|
|
3
|
+
// Pure React components without Next.js/Browser Storage dependencies
|
|
4
|
+
// For use in Electron, Vite, CRA apps
|
|
5
|
+
// ============================================================================
|
|
6
|
+
|
|
7
|
+
// Re-export everything from components
|
|
8
|
+
export * from './components';
|
|
9
|
+
|
|
10
|
+
// Re-export hooks
|
|
11
|
+
export * from './hooks';
|
|
12
|
+
|
|
13
|
+
// Re-export lib utilities
|
|
14
|
+
export * from './lib';
|
package/src/lib/index.ts
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OG Image URL Generation Utilities
|
|
3
|
+
*
|
|
4
|
+
* Client-side utilities for generating OG image URLs.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** Default OG Image API base URL */
|
|
8
|
+
const DEFAULT_OG_IMAGE_BASE_URL = 'https://djangocfg.com/api/og';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Encode string to base64 with Unicode support
|
|
12
|
+
* Works in both browser and Node.js environments
|
|
13
|
+
*/
|
|
14
|
+
function encodeBase64(str: string): string {
|
|
15
|
+
// Node.js environment
|
|
16
|
+
if (typeof Buffer !== 'undefined') {
|
|
17
|
+
return Buffer.from(str, 'utf-8').toString('base64');
|
|
18
|
+
}
|
|
19
|
+
// Browser environment - handle Unicode via UTF-8 encoding
|
|
20
|
+
const utf8Bytes = new TextEncoder().encode(str);
|
|
21
|
+
const binaryString = Array.from(utf8Bytes, byte => String.fromCharCode(byte)).join('');
|
|
22
|
+
return btoa(binaryString);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* OG Image URL parameters
|
|
27
|
+
*/
|
|
28
|
+
export interface OgImageUrlParams {
|
|
29
|
+
/** Page title */
|
|
30
|
+
title: string;
|
|
31
|
+
/** Page description (optional) */
|
|
32
|
+
description?: string;
|
|
33
|
+
/** Site name (optional) */
|
|
34
|
+
siteName?: string;
|
|
35
|
+
/** Logo URL (optional) */
|
|
36
|
+
logo?: string;
|
|
37
|
+
/** Background type: 'gradient' or 'solid' */
|
|
38
|
+
backgroundType?: 'gradient' | 'solid';
|
|
39
|
+
/** Gradient start color (hex) */
|
|
40
|
+
gradientStart?: string;
|
|
41
|
+
/** Gradient end color (hex) */
|
|
42
|
+
gradientEnd?: string;
|
|
43
|
+
/** Background color (for solid type) */
|
|
44
|
+
backgroundColor?: string;
|
|
45
|
+
/** Title font size (px) */
|
|
46
|
+
titleSize?: number;
|
|
47
|
+
/** Title font weight */
|
|
48
|
+
titleWeight?: number;
|
|
49
|
+
/** Title text color */
|
|
50
|
+
titleColor?: string;
|
|
51
|
+
/** Description font size (px) */
|
|
52
|
+
descriptionSize?: number;
|
|
53
|
+
/** Description text color */
|
|
54
|
+
descriptionColor?: string;
|
|
55
|
+
/** Site name font size (px) */
|
|
56
|
+
siteNameSize?: number;
|
|
57
|
+
/** Site name text color */
|
|
58
|
+
siteNameColor?: string;
|
|
59
|
+
/** Padding (px) */
|
|
60
|
+
padding?: number;
|
|
61
|
+
/** Logo size (px) */
|
|
62
|
+
logoSize?: number;
|
|
63
|
+
/** Show logo flag */
|
|
64
|
+
showLogo?: boolean;
|
|
65
|
+
/** Show site name flag */
|
|
66
|
+
showSiteName?: boolean;
|
|
67
|
+
/** Additional custom parameters */
|
|
68
|
+
[key: string]: string | number | boolean | undefined;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Options for generating OG image URL
|
|
73
|
+
*/
|
|
74
|
+
export interface GenerateOgImageUrlOptions {
|
|
75
|
+
/**
|
|
76
|
+
* Base URL of the OG image API route
|
|
77
|
+
* @default 'https://djangocfg.com/api/og'
|
|
78
|
+
*/
|
|
79
|
+
baseUrl?: string;
|
|
80
|
+
/**
|
|
81
|
+
* If true, encode params as base64 for safer URLs
|
|
82
|
+
* @default true
|
|
83
|
+
*/
|
|
84
|
+
useBase64?: boolean;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Generate OG image URL with query parameters or base64 encoding
|
|
89
|
+
*
|
|
90
|
+
* @example
|
|
91
|
+
* ```typescript
|
|
92
|
+
* // Using default baseUrl (https://djangocfg.com/api/og)
|
|
93
|
+
* const url = generateOgImageUrl({ title: 'My Page Title' });
|
|
94
|
+
*
|
|
95
|
+
* // With custom baseUrl
|
|
96
|
+
* const url = generateOgImageUrl({ title: 'My Page' }, { baseUrl: '/api/og' });
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
export function generateOgImageUrl(
|
|
100
|
+
params: OgImageUrlParams,
|
|
101
|
+
options: GenerateOgImageUrlOptions = {}
|
|
102
|
+
): string {
|
|
103
|
+
const { baseUrl = DEFAULT_OG_IMAGE_BASE_URL, useBase64 = true } = options;
|
|
104
|
+
|
|
105
|
+
if (useBase64) {
|
|
106
|
+
const cleanParams: Record<string, string | number | boolean> = {};
|
|
107
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
108
|
+
if (value !== undefined && value !== null && value !== '') {
|
|
109
|
+
cleanParams[key] = value;
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
const jsonString = JSON.stringify(cleanParams);
|
|
114
|
+
const base64Data = encodeBase64(jsonString);
|
|
115
|
+
return `${baseUrl}/${base64Data}`;
|
|
116
|
+
} else {
|
|
117
|
+
const searchParams = new URLSearchParams();
|
|
118
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
119
|
+
if (value !== undefined && value !== null && value !== '') {
|
|
120
|
+
searchParams.append(key, String(value));
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
const query = searchParams.toString();
|
|
124
|
+
return query ? `${baseUrl}?${query}` : baseUrl;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Get absolute OG image URL from relative path
|
|
130
|
+
*/
|
|
131
|
+
export function getAbsoluteOgImageUrl(relativePath: string, siteUrl: string): string {
|
|
132
|
+
// If path is already an absolute URL, return as-is
|
|
133
|
+
if (relativePath.startsWith('http://') || relativePath.startsWith('https://')) {
|
|
134
|
+
return relativePath;
|
|
135
|
+
}
|
|
136
|
+
const cleanSiteUrl = siteUrl.replace(/\/$/, '');
|
|
137
|
+
const cleanPath = relativePath.startsWith('/') ? relativePath : `/${relativePath}`;
|
|
138
|
+
return `${cleanSiteUrl}${cleanPath}`;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Create OG image URL builder with preset configuration
|
|
143
|
+
*/
|
|
144
|
+
export function createOgImageUrlBuilder(
|
|
145
|
+
defaults: Partial<OgImageUrlParams> = {},
|
|
146
|
+
options: GenerateOgImageUrlOptions = {}
|
|
147
|
+
) {
|
|
148
|
+
return (params: OgImageUrlParams): string => {
|
|
149
|
+
return generateOgImageUrl({ ...defaults, ...params }, options);
|
|
150
|
+
};
|
|
151
|
+
}
|
package/src/lib/utils.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base Styles
|
|
3
|
+
* Foundation styles that apply to the entire application
|
|
4
|
+
* Compatible with Tailwind CSS v4
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/* Base element styles */
|
|
8
|
+
* {
|
|
9
|
+
border-color: hsl(var(--border));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
html {
|
|
13
|
+
font-weight: 500;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
body {
|
|
17
|
+
background-color: hsl(var(--background));
|
|
18
|
+
color: hsl(var(--foreground));
|
|
19
|
+
font-feature-settings: "rlig" 1, "calt" 1;
|
|
20
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @deprecated
|
|
3
|
+
* This file is kept for backward compatibility.
|
|
4
|
+
* Please import from './index.css' instead.
|
|
5
|
+
*
|
|
6
|
+
* The styles are now modular and split into:
|
|
7
|
+
* - theme.css (CSS variables)
|
|
8
|
+
* - base.css (base styles)
|
|
9
|
+
* - utilities.css (custom utilities)
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
@import "./index.css";
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unrealon UI Styles
|
|
3
|
+
* Main entry point for @djangocfg/ui package styles
|
|
4
|
+
*
|
|
5
|
+
* Import order (critical for Tailwind v4):
|
|
6
|
+
* 1. Theme variables FIRST (so Tailwind can use them)
|
|
7
|
+
* 2. Tailwind CSS v4 (compiles with access to theme variables)
|
|
8
|
+
* 3. Animation utilities (tw-animate-css for Tailwind v4)
|
|
9
|
+
* 4. Base styles (override Tailwind defaults)
|
|
10
|
+
* 5. Custom utilities (extend Tailwind utilities)
|
|
11
|
+
*
|
|
12
|
+
* Usage: Import this file in your app's main CSS or _app.tsx
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/* 1. Theme variables MUST come first */
|
|
16
|
+
@import "./theme.css";
|
|
17
|
+
|
|
18
|
+
/* 2. Source detection for Tailwind v4 monorepo */
|
|
19
|
+
@import "./sources.css";
|
|
20
|
+
|
|
21
|
+
/* 3. Base styles */
|
|
22
|
+
@import "./base.css";
|
|
23
|
+
|
|
24
|
+
/* 5. Custom utilities */
|
|
25
|
+
@import "./utilities.css";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tailwind v4 Source Detection for UI Package
|
|
3
|
+
*
|
|
4
|
+
* @source directive tells Tailwind v4 where to scan for utility classes.
|
|
5
|
+
* In monorepo, this is critical because automatic detection may not work.
|
|
6
|
+
*
|
|
7
|
+
* "./" scans the entire UI package starting from this file's location.
|
|
8
|
+
*/
|
|
9
|
+
@source "../components";
|
|
10
|
+
@source "../layouts";
|
|
11
|
+
@source "../blocks";
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom Animations
|
|
3
|
+
*
|
|
4
|
+
* Smooth, professional animations for UI elements
|
|
5
|
+
* Optimized for performance with transform and opacity only
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/* Smooth Floating Animation - Multi-directional movement */
|
|
9
|
+
@keyframes float {
|
|
10
|
+
0%, 100% { transform: translate(0, 0) scale(1); }
|
|
11
|
+
33% { transform: translate(30px, -30px) scale(1.05); }
|
|
12
|
+
66% { transform: translate(-20px, 20px) scale(0.95); }
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/* Slow Vertical Float - Gentle up/down movement */
|
|
16
|
+
@keyframes float-slow {
|
|
17
|
+
0%, 100% { transform: translate(0, 0); }
|
|
18
|
+
50% { transform: translate(0, -50px); }
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/* Horizontal Float with Rotation */
|
|
22
|
+
@keyframes float-x {
|
|
23
|
+
0%, 100% { transform: translate(0, 0) rotate(0deg); }
|
|
24
|
+
50% { transform: translate(100px, 0) rotate(180deg); }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/* Diagonal Float - Complex multi-point movement */
|
|
28
|
+
@keyframes float-diagonal {
|
|
29
|
+
0%, 100% { transform: translate(0, 0) scale(1); }
|
|
30
|
+
25% { transform: translate(50px, 50px) scale(1.1); }
|
|
31
|
+
50% { transform: translate(100px, -50px) scale(0.9); }
|
|
32
|
+
75% { transform: translate(-50px, 50px) scale(1.05); }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/* Morph - Organic shape transformation */
|
|
36
|
+
@keyframes morph {
|
|
37
|
+
0%, 100% {
|
|
38
|
+
border-radius: 60% 40% 30% 70% / 60% 30% 70% 40%;
|
|
39
|
+
opacity: 0.3;
|
|
40
|
+
}
|
|
41
|
+
25% {
|
|
42
|
+
border-radius: 30% 60% 70% 40% / 50% 60% 30% 60%;
|
|
43
|
+
opacity: 0.5;
|
|
44
|
+
}
|
|
45
|
+
50% {
|
|
46
|
+
border-radius: 50% 60% 30% 60% / 30% 60% 70% 40%;
|
|
47
|
+
opacity: 0.4;
|
|
48
|
+
}
|
|
49
|
+
75% {
|
|
50
|
+
border-radius: 60% 40% 60% 30% / 60% 30% 60% 40%;
|
|
51
|
+
opacity: 0.6;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/* Gradient Shift - Color animation with hue rotation */
|
|
56
|
+
@keyframes gradient-shift {
|
|
57
|
+
0%, 100% {
|
|
58
|
+
background-position: 0% 50%;
|
|
59
|
+
filter: hue-rotate(0deg);
|
|
60
|
+
}
|
|
61
|
+
50% {
|
|
62
|
+
background-position: 100% 50%;
|
|
63
|
+
filter: hue-rotate(45deg);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dark Theme Colors
|
|
3
|
+
*
|
|
4
|
+
* Vercel-inspired dark theme with true black backgrounds
|
|
5
|
+
* High contrast with subtle grays for comfortable viewing
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
.dark {
|
|
9
|
+
/* Base colors - True black background with subtle grays */
|
|
10
|
+
--background: 0 0% 4%;
|
|
11
|
+
--foreground: 0 0% 98%;
|
|
12
|
+
--card: 0 0% 8%;
|
|
13
|
+
--card-foreground: 0 0% 98%;
|
|
14
|
+
--popover: 0 0% 12%;
|
|
15
|
+
--popover-foreground: 0 0% 98%;
|
|
16
|
+
--primary: 217 91% 60%;
|
|
17
|
+
--primary-foreground: 0 0% 100%;
|
|
18
|
+
--secondary: 0 0% 98%;
|
|
19
|
+
--secondary-foreground: 0 0% 9%;
|
|
20
|
+
--muted: 0 0% 10%;
|
|
21
|
+
--muted-foreground: 0 0% 60%;
|
|
22
|
+
--accent: 0 0% 15%;
|
|
23
|
+
--accent-foreground: 0 0% 98%;
|
|
24
|
+
--destructive: 0 84% 60%;
|
|
25
|
+
--destructive-foreground: 0 0% 98%;
|
|
26
|
+
--border: 0 0% 15%;
|
|
27
|
+
--input: 0 0% 15%;
|
|
28
|
+
--ring: 217 91% 60%;
|
|
29
|
+
|
|
30
|
+
/* Sidebar Dark Theme - Darker black sidebar */
|
|
31
|
+
--sidebar-background: 0 0% 0%;
|
|
32
|
+
--sidebar-foreground: 0 0% 98%;
|
|
33
|
+
--sidebar-primary: 217 91% 60%;
|
|
34
|
+
--sidebar-primary-foreground: 0 0% 100%;
|
|
35
|
+
--sidebar-accent: 0 0% 10%;
|
|
36
|
+
--sidebar-accent-foreground: 0 0% 98%;
|
|
37
|
+
--sidebar-border: 0 0% 15%;
|
|
38
|
+
--sidebar-ring: 217 91% 60%;
|
|
39
|
+
|
|
40
|
+
/* Surface gradient dark */
|
|
41
|
+
--surface-gradient: linear-gradient(to bottom, hsl(var(--background)), hsl(var(--background) / 0.8));
|
|
42
|
+
|
|
43
|
+
/* Chart colors - same as light theme for consistency */
|
|
44
|
+
--chart-1: 217 91% 60%; /* Blue - CPU */
|
|
45
|
+
--chart-2: 142 76% 36%; /* Green - Memory */
|
|
46
|
+
--chart-3: 262 83% 58%; /* Purple - Disk */
|
|
47
|
+
--chart-4: 26 90% 57%; /* Orange */
|
|
48
|
+
--chart-5: 346 77% 50%; /* Red */
|
|
49
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Light Theme Colors
|
|
3
|
+
*
|
|
4
|
+
* Vercel-inspired light theme with clean whites and subtle grays
|
|
5
|
+
* High contrast for optimal readability
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
:root {
|
|
9
|
+
/* Base colors - Clean white with neutral grays */
|
|
10
|
+
--background: 0 0% 96%;
|
|
11
|
+
--foreground: 0 0% 9%;
|
|
12
|
+
--card: 0 0% 100%;
|
|
13
|
+
--card-foreground: 0 0% 9%;
|
|
14
|
+
--popover: 0 0% 100%;
|
|
15
|
+
--popover-foreground: 0 0% 9%;
|
|
16
|
+
--primary: 217 91% 60%;
|
|
17
|
+
--primary-foreground: 0 0% 100%;
|
|
18
|
+
--secondary: 0 0% 9%;
|
|
19
|
+
--secondary-foreground: 0 0% 98%;
|
|
20
|
+
--muted: 0 0% 96%;
|
|
21
|
+
--muted-foreground: 0 0% 45%;
|
|
22
|
+
--accent: 0 0% 92%;
|
|
23
|
+
--accent-foreground: 0 0% 9%;
|
|
24
|
+
--destructive: 0 84% 60%;
|
|
25
|
+
--destructive-foreground: 0 0% 98%;
|
|
26
|
+
--border: 0 0% 90%;
|
|
27
|
+
--input: 0 0% 90%;
|
|
28
|
+
--ring: 217 91% 60%;
|
|
29
|
+
--radius: 0.5rem;
|
|
30
|
+
|
|
31
|
+
/* Sidebar Light Theme - Subtle gray background */
|
|
32
|
+
--sidebar-background: 0 0% 98%;
|
|
33
|
+
--sidebar-foreground: 0 0% 9%;
|
|
34
|
+
--sidebar-primary: 217 91% 60%;
|
|
35
|
+
--sidebar-primary-foreground: 0 0% 100%;
|
|
36
|
+
--sidebar-accent: 0 0% 94%;
|
|
37
|
+
--sidebar-accent-foreground: 0 0% 9%;
|
|
38
|
+
--sidebar-border: 0 0% 90%;
|
|
39
|
+
--sidebar-ring: 217 91% 60%;
|
|
40
|
+
|
|
41
|
+
/* Surface gradient */
|
|
42
|
+
--surface-gradient: linear-gradient(to bottom, hsl(var(--background)), hsl(var(--background) / 0.8));
|
|
43
|
+
|
|
44
|
+
/* Chart colors - vibrant and distinct */
|
|
45
|
+
--chart-1: 217 91% 60%; /* Blue - CPU */
|
|
46
|
+
--chart-2: 142 76% 36%; /* Green - Memory */
|
|
47
|
+
--chart-3: 262 83% 58%; /* Purple - Disk */
|
|
48
|
+
--chart-4: 26 90% 57%; /* Orange */
|
|
49
|
+
--chart-5: 346 77% 50%; /* Red */
|
|
50
|
+
}
|