@xaui/hybrid 0.0.2

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.
@@ -0,0 +1,65 @@
1
+ import React, { ButtonHTMLAttributes, ReactNode } from 'react';
2
+ import { T as ThemeColor } from '../theme-qvIXI4kF.cjs';
3
+
4
+ type ButtonVariant = 'solid' | 'outlined' | 'flat' | 'light' | 'elevated' | 'faded';
5
+ type ButtonSize = 'xs' | 'sm' | 'md' | 'lg';
6
+ type ButtonRadius = 'none' | 'sm' | 'md' | 'lg' | 'full';
7
+ type SpinnerPlacement = 'start' | 'end';
8
+ type ButtonProps = Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'disabled' | 'children' | 'onAnimationStart' | 'onDragStart' | 'onDragEnd' | 'onDrag'> & {
9
+ /**
10
+ * The content to display in the button.
11
+ */
12
+ children: ReactNode;
13
+ /**
14
+ * The theme color of the button.
15
+ * @default 'default'
16
+ */
17
+ themeColor?: ThemeColor;
18
+ /**
19
+ * The variant of the button.
20
+ * @default 'solid'
21
+ */
22
+ variant?: ButtonVariant;
23
+ /**
24
+ * The size of the button.
25
+ * @default 'md'
26
+ */
27
+ size?: ButtonSize;
28
+ /**
29
+ * The border radius of the button.
30
+ * @default 'md'
31
+ */
32
+ radius?: ButtonRadius;
33
+ /**
34
+ * Content to display at the start of the button.
35
+ */
36
+ startContent?: ReactNode;
37
+ /**
38
+ * Content to display at the end of the button.
39
+ */
40
+ endContent?: ReactNode;
41
+ /**
42
+ * The placement of the spinner when isLoading is true.
43
+ * @default 'start'
44
+ */
45
+ spinnerPlacement?: SpinnerPlacement;
46
+ /**
47
+ * Whether the button should take the full width of its container.
48
+ * @default false
49
+ */
50
+ fullWidth?: boolean;
51
+ /**
52
+ * Whether the button is disabled.
53
+ * @default false
54
+ */
55
+ isDisabled?: boolean;
56
+ /**
57
+ * Whether the button is in a loading state.
58
+ * @default false
59
+ */
60
+ isLoading?: boolean;
61
+ };
62
+
63
+ declare const Button: React.FC<ButtonProps>;
64
+
65
+ export { Button, type ButtonProps, type ButtonRadius, type ButtonSize, type ButtonVariant, type SpinnerPlacement };
@@ -0,0 +1,65 @@
1
+ import React, { ButtonHTMLAttributes, ReactNode } from 'react';
2
+ import { T as ThemeColor } from '../theme-qvIXI4kF.js';
3
+
4
+ type ButtonVariant = 'solid' | 'outlined' | 'flat' | 'light' | 'elevated' | 'faded';
5
+ type ButtonSize = 'xs' | 'sm' | 'md' | 'lg';
6
+ type ButtonRadius = 'none' | 'sm' | 'md' | 'lg' | 'full';
7
+ type SpinnerPlacement = 'start' | 'end';
8
+ type ButtonProps = Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'disabled' | 'children' | 'onAnimationStart' | 'onDragStart' | 'onDragEnd' | 'onDrag'> & {
9
+ /**
10
+ * The content to display in the button.
11
+ */
12
+ children: ReactNode;
13
+ /**
14
+ * The theme color of the button.
15
+ * @default 'default'
16
+ */
17
+ themeColor?: ThemeColor;
18
+ /**
19
+ * The variant of the button.
20
+ * @default 'solid'
21
+ */
22
+ variant?: ButtonVariant;
23
+ /**
24
+ * The size of the button.
25
+ * @default 'md'
26
+ */
27
+ size?: ButtonSize;
28
+ /**
29
+ * The border radius of the button.
30
+ * @default 'md'
31
+ */
32
+ radius?: ButtonRadius;
33
+ /**
34
+ * Content to display at the start of the button.
35
+ */
36
+ startContent?: ReactNode;
37
+ /**
38
+ * Content to display at the end of the button.
39
+ */
40
+ endContent?: ReactNode;
41
+ /**
42
+ * The placement of the spinner when isLoading is true.
43
+ * @default 'start'
44
+ */
45
+ spinnerPlacement?: SpinnerPlacement;
46
+ /**
47
+ * Whether the button should take the full width of its container.
48
+ * @default false
49
+ */
50
+ fullWidth?: boolean;
51
+ /**
52
+ * Whether the button is disabled.
53
+ * @default false
54
+ */
55
+ isDisabled?: boolean;
56
+ /**
57
+ * Whether the button is in a loading state.
58
+ * @default false
59
+ */
60
+ isLoading?: boolean;
61
+ };
62
+
63
+ declare const Button: React.FC<ButtonProps>;
64
+
65
+ export { Button, type ButtonProps, type ButtonRadius, type ButtonSize, type ButtonVariant, type SpinnerPlacement };
@@ -0,0 +1,261 @@
1
+ import {
2
+ ActivityIndicator
3
+ } from "../chunk-57VNE2OC.js";
4
+ import {
5
+ useXUITheme
6
+ } from "../chunk-4GTYR4N2.js";
7
+
8
+ // src/components/button/button.tsx
9
+ import { useState } from "react";
10
+ import { motion } from "framer-motion";
11
+
12
+ // src/components/button/button.style.ts
13
+ import { tv } from "tailwind-variants";
14
+ var buttonStyles = tv({
15
+ slots: {
16
+ base: [
17
+ "inline-flex",
18
+ "items-center",
19
+ "justify-center",
20
+ "gap-2",
21
+ "overflow-hidden",
22
+ "font-medium",
23
+ "text-center",
24
+ "outline-none",
25
+ "cursor-pointer",
26
+ "select-none",
27
+ "transition-all",
28
+ "subpixel-antialiased",
29
+ "tap-highlight-transparent",
30
+ "transform-gpu"
31
+ ]
32
+ },
33
+ variants: {
34
+ fullWidth: {
35
+ true: {
36
+ base: "w-full"
37
+ },
38
+ false: {
39
+ base: "inline-flex"
40
+ }
41
+ },
42
+ isDisabled: {
43
+ true: {
44
+ base: "opacity-50 cursor-not-allowed"
45
+ }
46
+ }
47
+ },
48
+ defaultVariants: {
49
+ fullWidth: false
50
+ }
51
+ });
52
+
53
+ // src/components/button/button.hook.ts
54
+ import { useMemo } from "react";
55
+ var useButtonStyles = (themeColor, variant, size, radius) => {
56
+ const theme = useXUITheme();
57
+ const colorScheme = theme.colors[themeColor];
58
+ const sizeStyles = useMemo(() => {
59
+ const sizes = {
60
+ xs: {
61
+ paddingLeft: `${theme.spacing.sm}px`,
62
+ paddingRight: `${theme.spacing.sm}px`,
63
+ paddingTop: `${theme.spacing.xs}px`,
64
+ paddingBottom: `${theme.spacing.xs}px`,
65
+ minHeight: "28px",
66
+ fontSize: `${theme.fontSizes.xs}px`,
67
+ lineHeight: "1"
68
+ },
69
+ sm: {
70
+ paddingLeft: `${theme.spacing.md}px`,
71
+ paddingRight: `${theme.spacing.md}px`,
72
+ paddingTop: `${theme.spacing.xs}px`,
73
+ paddingBottom: `${theme.spacing.xs}px`,
74
+ minHeight: "32px",
75
+ fontSize: `${theme.fontSizes.sm}px`,
76
+ lineHeight: "1"
77
+ },
78
+ md: {
79
+ paddingLeft: `${theme.spacing.md}px`,
80
+ paddingRight: `${theme.spacing.md}px`,
81
+ paddingTop: `${theme.spacing.sm}px`,
82
+ paddingBottom: `${theme.spacing.sm}px`,
83
+ minHeight: "40px",
84
+ fontSize: `${theme.fontSizes.md}px`,
85
+ lineHeight: "1"
86
+ },
87
+ lg: {
88
+ paddingLeft: `${theme.spacing.lg}px`,
89
+ paddingRight: `${theme.spacing.lg}px`,
90
+ paddingTop: `${theme.spacing.md}px`,
91
+ paddingBottom: `${theme.spacing.md}px`,
92
+ minHeight: "48px",
93
+ fontSize: `${theme.fontSizes.lg}px`,
94
+ lineHeight: "1"
95
+ }
96
+ };
97
+ return sizes[size];
98
+ }, [size, theme]);
99
+ const radiusStyles = useMemo(() => {
100
+ const radii = {
101
+ none: theme.borderRadius.none,
102
+ sm: theme.borderRadius.sm,
103
+ md: theme.borderRadius.md,
104
+ lg: theme.borderRadius.lg,
105
+ full: theme.borderRadius.full
106
+ };
107
+ return { borderRadius: `${radii[radius]}px` };
108
+ }, [radius, theme]);
109
+ const variantStyles = useMemo(() => {
110
+ const styles = {
111
+ solid: {
112
+ backgroundColor: colorScheme.main,
113
+ borderWidth: "0",
114
+ color: colorScheme.foreground
115
+ },
116
+ outlined: {
117
+ backgroundColor: "transparent",
118
+ borderWidth: `${theme.borderWidth.md}px`,
119
+ borderStyle: "solid",
120
+ borderColor: colorScheme.main,
121
+ color: colorScheme.main
122
+ },
123
+ flat: {
124
+ backgroundColor: colorScheme.background,
125
+ borderWidth: "0",
126
+ color: colorScheme.main
127
+ },
128
+ light: {
129
+ backgroundColor: "transparent",
130
+ borderWidth: "0",
131
+ color: colorScheme.main
132
+ },
133
+ elevated: {
134
+ backgroundColor: colorScheme.main,
135
+ borderWidth: "0",
136
+ color: colorScheme.foreground,
137
+ boxShadow: typeof theme.shadows.md === "string" ? theme.shadows.md : "0 2px 4px rgba(0,0,0,0.1)"
138
+ },
139
+ faded: {
140
+ backgroundColor: `${colorScheme.background}90`,
141
+ borderWidth: `${theme.borderWidth.md}px`,
142
+ borderStyle: "solid",
143
+ borderColor: colorScheme.main,
144
+ color: colorScheme.main
145
+ }
146
+ };
147
+ return styles[variant];
148
+ }, [variant, colorScheme, theme]);
149
+ const textColor = useMemo(() => {
150
+ if (variant === "solid" || variant === "elevated") {
151
+ return colorScheme.foreground;
152
+ }
153
+ return colorScheme.main;
154
+ }, [variant, colorScheme]);
155
+ const spinnerSize = useMemo(() => {
156
+ const sizes = {
157
+ xs: 14,
158
+ sm: 16,
159
+ md: 18,
160
+ lg: 20
161
+ };
162
+ return sizes[size];
163
+ }, [size]);
164
+ return {
165
+ sizeStyles,
166
+ radiusStyles,
167
+ variantStyles,
168
+ textColor,
169
+ spinnerSize
170
+ };
171
+ };
172
+
173
+ // src/components/button/button.tsx
174
+ import { jsx, jsxs } from "react/jsx-runtime";
175
+ var Button = ({
176
+ children,
177
+ themeColor = "default",
178
+ variant = "solid",
179
+ size = "md",
180
+ radius = "md",
181
+ startContent,
182
+ endContent,
183
+ spinnerPlacement = "start",
184
+ fullWidth = false,
185
+ isDisabled = false,
186
+ isLoading = false,
187
+ className,
188
+ style: userStyle,
189
+ onMouseLeave,
190
+ onMouseDown,
191
+ onMouseUp,
192
+ ...restProps
193
+ }) => {
194
+ const [isPressed, setIsPressed] = useState(false);
195
+ const { base } = buttonStyles({ fullWidth, isDisabled });
196
+ const { sizeStyles, radiusStyles, variantStyles, textColor, spinnerSize } = useButtonStyles(themeColor, variant, size, radius);
197
+ const handleMouseDown = (event) => {
198
+ if (!isDisabled && !isLoading) {
199
+ setIsPressed(true);
200
+ }
201
+ onMouseDown?.(event);
202
+ };
203
+ const handleMouseUp = (event) => {
204
+ if (!isDisabled && !isLoading) {
205
+ setIsPressed(false);
206
+ }
207
+ onMouseUp?.(event);
208
+ };
209
+ const handleMouseLeave = (event) => {
210
+ if (!isDisabled && !isLoading) {
211
+ setIsPressed(false);
212
+ }
213
+ onMouseLeave?.(event);
214
+ };
215
+ const spinnerElement = /* @__PURE__ */ jsx(
216
+ ActivityIndicator,
217
+ {
218
+ variant: "circular",
219
+ showTrack: false,
220
+ themeColor: variant === "solid" || variant === "elevated" ? void 0 : themeColor,
221
+ color: variant === "solid" || variant === "elevated" ? textColor : void 0,
222
+ size: spinnerSize
223
+ }
224
+ );
225
+ return /* @__PURE__ */ jsxs(
226
+ motion.button,
227
+ {
228
+ type: "button",
229
+ className: base({ className }),
230
+ disabled: isDisabled || isLoading,
231
+ onMouseLeave: handleMouseLeave,
232
+ onMouseDown: handleMouseDown,
233
+ onMouseUp: handleMouseUp,
234
+ ...restProps,
235
+ style: {
236
+ ...sizeStyles,
237
+ ...radiusStyles,
238
+ ...variantStyles,
239
+ ...userStyle
240
+ },
241
+ animate: {
242
+ scale: isPressed ? 0.95 : 1,
243
+ opacity: isPressed ? 0.8 : 1
244
+ },
245
+ transition: {
246
+ duration: 0.1,
247
+ ease: "easeInOut"
248
+ },
249
+ children: [
250
+ startContent,
251
+ isLoading && spinnerPlacement === "start" && spinnerElement,
252
+ children,
253
+ isLoading && spinnerPlacement === "end" && spinnerElement,
254
+ endContent
255
+ ]
256
+ }
257
+ );
258
+ };
259
+ export {
260
+ Button
261
+ };
@@ -0,0 +1,86 @@
1
+ // src/core/theme-context.tsx
2
+ import React, { createContext } from "react";
3
+
4
+ // src/core/theme-hooks.ts
5
+ import { useContext, useEffect, useState } from "react";
6
+ var getWebColorMode = () => {
7
+ if (typeof globalThis === "undefined") return "light";
8
+ const globalScope = globalThis;
9
+ if (!globalScope.matchMedia) {
10
+ return "light";
11
+ }
12
+ return globalScope.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
13
+ };
14
+ function useColorMode() {
15
+ const [webScheme, setWebScheme] = useState(() => getWebColorMode());
16
+ useEffect(() => {
17
+ if (typeof globalThis === "undefined") return;
18
+ const globalScope = globalThis;
19
+ if (!globalScope.matchMedia) return;
20
+ const media = globalScope.matchMedia("(prefers-color-scheme: dark)");
21
+ const handleChange = () => {
22
+ setWebScheme(media.matches ? "dark" : "light");
23
+ };
24
+ handleChange();
25
+ if (typeof media.addEventListener === "function") {
26
+ media.addEventListener("change", handleChange);
27
+ return () => media.removeEventListener?.("change", handleChange);
28
+ }
29
+ const legacyMedia = media;
30
+ legacyMedia.addListener?.(handleChange);
31
+ return () => legacyMedia.removeListener?.(handleChange);
32
+ }, []);
33
+ return webScheme;
34
+ }
35
+ function useXUITheme() {
36
+ const theme = useContext(XUIThemeContext);
37
+ if (!theme) {
38
+ throw new Error("useXUITheme must be used within XUIProvider");
39
+ }
40
+ return theme;
41
+ }
42
+ function useXUIColors() {
43
+ const theme = useXUITheme();
44
+ return theme.colors;
45
+ }
46
+
47
+ // src/core/theme-context.tsx
48
+ import { defaultTheme } from "@xaui/core/theme";
49
+ import { jsx } from "react/jsx-runtime";
50
+ var XUIThemeContext = createContext(null);
51
+ function XUIProvider({
52
+ children,
53
+ theme: lightTheme,
54
+ darkTheme
55
+ }) {
56
+ const colorScheme = useColorMode();
57
+ const theme = React.useMemo(() => {
58
+ if (!darkTheme && !lightTheme) return defaultTheme;
59
+ const activeTheme = colorScheme === "dark" && darkTheme ? darkTheme : lightTheme;
60
+ if (!activeTheme) return defaultTheme;
61
+ return {
62
+ ...defaultTheme,
63
+ ...activeTheme,
64
+ colors: {
65
+ ...defaultTheme.colors,
66
+ ...activeTheme.colors
67
+ },
68
+ fontFamilies: {
69
+ ...defaultTheme.fontFamilies,
70
+ ...activeTheme.fontFamilies
71
+ },
72
+ fontSizes: {
73
+ ...defaultTheme.fontSizes,
74
+ ...activeTheme.fontSizes
75
+ }
76
+ };
77
+ }, [lightTheme, darkTheme, colorScheme]);
78
+ return /* @__PURE__ */ jsx(XUIThemeContext.Provider, { value: theme, children });
79
+ }
80
+
81
+ export {
82
+ useColorMode,
83
+ useXUITheme,
84
+ useXUIColors,
85
+ XUIProvider
86
+ };