@srcroot/ui 0.0.28 → 0.0.32
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/dist/index.js +377 -574
- package/package.json +2 -2
- package/src/registry/analytics/google-analytics.tsx +36 -0
- package/src/registry/analytics/google-tag-manager.tsx +62 -0
- package/src/registry/analytics/meta-pixel.tsx +44 -0
- package/src/registry/analytics/microsoft-clarity.tsx +31 -0
- package/src/registry/analytics/tiktok-pixel.tsx +34 -0
- package/src/registry/lib/utils.ts +6 -0
- package/src/registry/themes/gradients.css +236 -0
- package/{registry/themes → src/registry/themes/v3}/gray.css +45 -1
- package/{registry/themes → src/registry/themes/v3}/neutral.css +45 -1
- package/{registry/themes → src/registry/themes/v3}/slate.css +45 -1
- package/{registry/themes → src/registry/themes/v3}/stone.css +45 -1
- package/{registry/themes → src/registry/themes/v3}/zinc.css +45 -1
- package/src/registry/themes/v4/gray.css +183 -0
- package/src/registry/themes/v4/neutral.css +183 -0
- package/src/registry/themes/v4/slate.css +183 -0
- package/src/registry/themes/v4/stone.css +183 -0
- package/src/registry/themes/v4/zinc.css +183 -0
- package/{registry → src/registry/ui}/carousel.tsx +82 -38
- package/src/registry/ui/chatbot.tsx +96 -0
- package/registry/design-tokens.css +0 -217
- package/registry/themes/index.css +0 -19
- /package/{registry → src/registry/ui}/accordion.tsx +0 -0
- /package/{registry → src/registry/ui}/alert-dialog.tsx +0 -0
- /package/{registry → src/registry/ui}/alert.tsx +0 -0
- /package/{registry → src/registry/ui}/aspect-ratio.tsx +0 -0
- /package/{registry → src/registry/ui}/avatar.tsx +0 -0
- /package/{registry → src/registry/ui}/badge.tsx +0 -0
- /package/{registry → src/registry/ui}/breadcrumb.tsx +0 -0
- /package/{registry → src/registry/ui}/button-group.tsx +0 -0
- /package/{registry → src/registry/ui}/button.tsx +0 -0
- /package/{registry → src/registry/ui}/calendar.tsx +0 -0
- /package/{registry → src/registry/ui}/card.tsx +0 -0
- /package/{registry → src/registry/ui}/checkbox.tsx +0 -0
- /package/{registry → src/registry/ui}/collapsible.tsx +0 -0
- /package/{registry → src/registry/ui}/combobox.tsx +0 -0
- /package/{registry → src/registry/ui}/command.tsx +0 -0
- /package/{registry → src/registry/ui}/container.tsx +0 -0
- /package/{registry → src/registry/ui}/context-menu.tsx +0 -0
- /package/{registry → src/registry/ui}/date-picker.tsx +0 -0
- /package/{registry → src/registry/ui}/dialog.tsx +0 -0
- /package/{registry → src/registry/ui}/drawer.tsx +0 -0
- /package/{registry → src/registry/ui}/dropdown-menu.tsx +0 -0
- /package/{registry → src/registry/ui}/file-upload.tsx +0 -0
- /package/{registry → src/registry/ui}/hover-card.tsx +0 -0
- /package/{registry → src/registry/ui}/image.tsx +0 -0
- /package/{registry → src/registry/ui}/input.tsx +0 -0
- /package/{registry → src/registry/ui}/kbd.tsx +0 -0
- /package/{registry → src/registry/ui}/label.tsx +0 -0
- /package/{registry → src/registry/ui}/loading-spinner.tsx +0 -0
- /package/{registry → src/registry/ui}/menubar.tsx +0 -0
- /package/{registry → src/registry/ui}/native-select.tsx +0 -0
- /package/{registry → src/registry/ui}/otp-input.tsx +0 -0
- /package/{registry → src/registry/ui}/pagination.tsx +0 -0
- /package/{registry → src/registry/ui}/popover.tsx +0 -0
- /package/{registry → src/registry/ui}/progress.tsx +0 -0
- /package/{registry → src/registry/ui}/radio.tsx +0 -0
- /package/{registry → src/registry/ui}/resizable.tsx +0 -0
- /package/{registry → src/registry/ui}/scroll-area.tsx +0 -0
- /package/{registry → src/registry/ui}/search.tsx +0 -0
- /package/{registry → src/registry/ui}/select.tsx +0 -0
- /package/{registry → src/registry/ui}/separator.tsx +0 -0
- /package/{registry → src/registry/ui}/sheet.tsx +0 -0
- /package/{registry → src/registry/ui}/sidebar.tsx +0 -0
- /package/{registry → src/registry/ui}/skeleton.tsx +0 -0
- /package/{registry → src/registry/ui}/slider.tsx +0 -0
- /package/{registry → src/registry/ui}/star-rating.tsx +0 -0
- /package/{registry → src/registry/ui}/switch.tsx +0 -0
- /package/{registry → src/registry/ui}/table.tsx +0 -0
- /package/{registry → src/registry/ui}/tabs.tsx +0 -0
- /package/{registry → src/registry/ui}/text.tsx +0 -0
- /package/{registry → src/registry/ui}/textarea.tsx +0 -0
- /package/{registry → src/registry/ui}/toast.tsx +0 -0
- /package/{registry → src/registry/ui}/toggle-group.tsx +0 -0
- /package/{registry → src/registry/ui}/toggle.tsx +0 -0
- /package/{registry → src/registry/ui}/tooltip.tsx +0 -0
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @srcroot/ui - Stone Theme (Tailwind 4)
|
|
3
|
+
* Warm gray with subtle brown undertones
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
@import "tailwindcss";
|
|
7
|
+
|
|
8
|
+
:root {
|
|
9
|
+
--background: hsl(0 0% 100%);
|
|
10
|
+
--foreground: hsl(24 9.8% 10%);
|
|
11
|
+
|
|
12
|
+
--card: hsl(0 0% 100%);
|
|
13
|
+
--card-foreground: hsl(24 9.8% 10%);
|
|
14
|
+
|
|
15
|
+
--popover: hsl(0 0% 100%);
|
|
16
|
+
--popover-foreground: hsl(24 9.8% 10%);
|
|
17
|
+
|
|
18
|
+
--primary: hsl(24 9.8% 10%);
|
|
19
|
+
--primary-foreground: hsl(60 9.1% 97.8%);
|
|
20
|
+
|
|
21
|
+
--secondary: hsl(60 4.8% 95.9%);
|
|
22
|
+
--secondary-foreground: hsl(24 9.8% 10%);
|
|
23
|
+
|
|
24
|
+
--muted: hsl(60 4.8% 95.9%);
|
|
25
|
+
--muted-foreground: hsl(25 5.3% 44.7%);
|
|
26
|
+
|
|
27
|
+
--accent: hsl(60 4.8% 95.9%);
|
|
28
|
+
--accent-foreground: hsl(24 9.8% 10%);
|
|
29
|
+
|
|
30
|
+
--destructive: hsl(0 84.2% 60.2%);
|
|
31
|
+
--destructive-foreground: hsl(60 9.1% 97.8%);
|
|
32
|
+
|
|
33
|
+
--success: hsl(142.1 76.2% 36.3%);
|
|
34
|
+
--success-foreground: hsl(60 9.1% 97.8%);
|
|
35
|
+
|
|
36
|
+
--warning: hsl(45.4 93.4% 47.5%);
|
|
37
|
+
--warning-foreground: hsl(24 9.8% 10%);
|
|
38
|
+
|
|
39
|
+
--info: hsl(201.3 96.3% 32.2%);
|
|
40
|
+
--info-foreground: hsl(60 9.1% 97.8%);
|
|
41
|
+
|
|
42
|
+
--border: hsl(20 5.9% 90%);
|
|
43
|
+
--input: hsl(20 5.9% 90%);
|
|
44
|
+
--ring: hsl(24 9.8% 10%);
|
|
45
|
+
|
|
46
|
+
--radius: 0.5rem;
|
|
47
|
+
|
|
48
|
+
--sidebar-width: 16rem;
|
|
49
|
+
--sidebar-width-mobile: 18rem;
|
|
50
|
+
--sidebar-width-collapsed: 3rem;
|
|
51
|
+
--sidebar-width-icon: 3rem;
|
|
52
|
+
--header-height: 3.5rem;
|
|
53
|
+
|
|
54
|
+
--sidebar-background: hsl(0 0% 98%);
|
|
55
|
+
--sidebar-foreground: hsl(24 9.8% 10%);
|
|
56
|
+
--sidebar-primary: hsl(24 9.8% 10%);
|
|
57
|
+
--sidebar-primary-foreground: hsl(60 9.1% 97.8%);
|
|
58
|
+
--sidebar-accent: hsl(60 4.8% 95.9%);
|
|
59
|
+
--sidebar-accent-foreground: hsl(24 9.8% 10%);
|
|
60
|
+
--sidebar-border: hsl(20 5.9% 90%);
|
|
61
|
+
--sidebar-ring: hsl(24 9.8% 10%);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
@theme inline {
|
|
65
|
+
--color-border: var(--border);
|
|
66
|
+
--color-input: var(--input);
|
|
67
|
+
--color-ring: var(--ring);
|
|
68
|
+
--color-background: var(--background);
|
|
69
|
+
--color-foreground: var(--foreground);
|
|
70
|
+
|
|
71
|
+
--color-primary: var(--primary);
|
|
72
|
+
--color-primary-foreground: var(--primary-foreground);
|
|
73
|
+
|
|
74
|
+
--color-secondary: var(--secondary);
|
|
75
|
+
--color-secondary-foreground: var(--secondary-foreground);
|
|
76
|
+
|
|
77
|
+
--color-destructive: var(--destructive);
|
|
78
|
+
--color-destructive-foreground: var(--destructive-foreground);
|
|
79
|
+
|
|
80
|
+
--color-muted: var(--muted);
|
|
81
|
+
--color-muted-foreground: var(--muted-foreground);
|
|
82
|
+
|
|
83
|
+
--color-accent: var(--accent);
|
|
84
|
+
--color-accent-foreground: var(--accent-foreground);
|
|
85
|
+
|
|
86
|
+
--color-popover: var(--popover);
|
|
87
|
+
--color-popover-foreground: var(--popover-foreground);
|
|
88
|
+
|
|
89
|
+
--color-card: var(--card);
|
|
90
|
+
--color-card-foreground: var(--card-foreground);
|
|
91
|
+
|
|
92
|
+
--color-sidebar: var(--sidebar-background);
|
|
93
|
+
--color-sidebar-foreground: var(--sidebar-foreground);
|
|
94
|
+
--color-sidebar-primary: var(--sidebar-primary);
|
|
95
|
+
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
|
96
|
+
--color-sidebar-accent: var(--sidebar-accent);
|
|
97
|
+
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
|
98
|
+
--color-sidebar-border: var(--sidebar-border);
|
|
99
|
+
--color-sidebar-ring: var(--sidebar-ring);
|
|
100
|
+
|
|
101
|
+
--radius-lg: var(--radius);
|
|
102
|
+
--radius-md: calc(var(--radius) - 2px);
|
|
103
|
+
--radius-sm: calc(var(--radius) - 4px);
|
|
104
|
+
|
|
105
|
+
--animate-accordion-down: accordion-down 0.2s ease-out;
|
|
106
|
+
--animate-accordion-up: accordion-up 0.2s ease-out;
|
|
107
|
+
|
|
108
|
+
@keyframes accordion-down {
|
|
109
|
+
from {
|
|
110
|
+
height: 0;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
to {
|
|
114
|
+
height: var(--radix-accordion-content-height);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
@keyframes accordion-up {
|
|
119
|
+
from {
|
|
120
|
+
height: var(--radix-accordion-content-height);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
to {
|
|
124
|
+
height: 0;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
@media (prefers-color-scheme: dark) {
|
|
130
|
+
:root {
|
|
131
|
+
--background: hsl(24 9.8% 10%);
|
|
132
|
+
--foreground: hsl(60 9.1% 97.8%);
|
|
133
|
+
|
|
134
|
+
--card: hsl(24 9.8% 10%);
|
|
135
|
+
--card-foreground: hsl(60 9.1% 97.8%);
|
|
136
|
+
|
|
137
|
+
--popover: hsl(24 9.8% 10%);
|
|
138
|
+
--popover-foreground: hsl(60 9.1% 97.8%);
|
|
139
|
+
|
|
140
|
+
--primary: hsl(60 9.1% 97.8%);
|
|
141
|
+
--primary-foreground: hsl(24 9.8% 10%);
|
|
142
|
+
|
|
143
|
+
--secondary: hsl(12 6.5% 15.1%);
|
|
144
|
+
--secondary-foreground: hsl(60 9.1% 97.8%);
|
|
145
|
+
|
|
146
|
+
--muted: hsl(12 6.5% 15.1%);
|
|
147
|
+
--muted-foreground: hsl(24 5.4% 63.9%);
|
|
148
|
+
|
|
149
|
+
--accent: hsl(12 6.5% 15.1%);
|
|
150
|
+
--accent-foreground: hsl(60 9.1% 97.8%);
|
|
151
|
+
|
|
152
|
+
--destructive: hsl(0 62.8% 30.6%);
|
|
153
|
+
--destructive-foreground: hsl(60 9.1% 97.8%);
|
|
154
|
+
|
|
155
|
+
--success: hsl(142.1 70.6% 45.3%);
|
|
156
|
+
--success-foreground: hsl(24 9.8% 10%);
|
|
157
|
+
|
|
158
|
+
--warning: hsl(48 96.5% 53.1%);
|
|
159
|
+
--warning-foreground: hsl(24 9.8% 10%);
|
|
160
|
+
|
|
161
|
+
--info: hsl(199.4 95.5% 53.8%);
|
|
162
|
+
--info-foreground: hsl(24 9.8% 10%);
|
|
163
|
+
|
|
164
|
+
--border: hsl(12 6.5% 15.1%);
|
|
165
|
+
--input: hsl(12 6.5% 15.1%);
|
|
166
|
+
--ring: hsl(24 5.7% 82.9%);
|
|
167
|
+
|
|
168
|
+
--sidebar-background: hsl(24 9.8% 14%);
|
|
169
|
+
--sidebar-foreground: hsl(60 9.1% 97.8%);
|
|
170
|
+
--sidebar-primary: hsl(60 9.1% 97.8%);
|
|
171
|
+
--sidebar-primary-foreground: hsl(24 9.8% 10%);
|
|
172
|
+
--sidebar-accent: hsl(12 6.5% 15.1%);
|
|
173
|
+
--sidebar-accent-foreground: hsl(60 9.1% 97.8%);
|
|
174
|
+
--sidebar-border: hsl(12 6.5% 15.1%);
|
|
175
|
+
--sidebar-ring: hsl(24 5.7% 82.9%);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
body {
|
|
180
|
+
background: var(--background);
|
|
181
|
+
color: var(--foreground);
|
|
182
|
+
font-family: Arial, Helvetica, sans-serif;
|
|
183
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @srcroot/ui - Zinc Theme (Tailwind 4)
|
|
3
|
+
* Cool gray with subtle blue undertones
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
@import "tailwindcss";
|
|
7
|
+
|
|
8
|
+
:root {
|
|
9
|
+
--background: hsl(0 0% 100%);
|
|
10
|
+
--foreground: hsl(240 10% 3.9%);
|
|
11
|
+
|
|
12
|
+
--card: hsl(0 0% 100%);
|
|
13
|
+
--card-foreground: hsl(240 10% 3.9%);
|
|
14
|
+
|
|
15
|
+
--popover: hsl(0 0% 100%);
|
|
16
|
+
--popover-foreground: hsl(240 10% 3.9%);
|
|
17
|
+
|
|
18
|
+
--primary: hsl(240 5.9% 10%);
|
|
19
|
+
--primary-foreground: hsl(0 0% 98%);
|
|
20
|
+
|
|
21
|
+
--secondary: hsl(240 4.8% 95.9%);
|
|
22
|
+
--secondary-foreground: hsl(240 5.9% 10%);
|
|
23
|
+
|
|
24
|
+
--muted: hsl(240 4.8% 95.9%);
|
|
25
|
+
--muted-foreground: hsl(240 3.8% 46.1%);
|
|
26
|
+
|
|
27
|
+
--accent: hsl(240 4.8% 95.9%);
|
|
28
|
+
--accent-foreground: hsl(240 5.9% 10%);
|
|
29
|
+
|
|
30
|
+
--destructive: hsl(0 84.2% 60.2%);
|
|
31
|
+
--destructive-foreground: hsl(0 0% 98%);
|
|
32
|
+
|
|
33
|
+
--success: hsl(142.1 76.2% 36.3%);
|
|
34
|
+
--success-foreground: hsl(0 0% 98%);
|
|
35
|
+
|
|
36
|
+
--warning: hsl(45.4 93.4% 47.5%);
|
|
37
|
+
--warning-foreground: hsl(240 5.9% 10%);
|
|
38
|
+
|
|
39
|
+
--info: hsl(201.3 96.3% 32.2%);
|
|
40
|
+
--info-foreground: hsl(0 0% 98%);
|
|
41
|
+
|
|
42
|
+
--border: hsl(240 5.9% 90%);
|
|
43
|
+
--input: hsl(240 5.9% 90%);
|
|
44
|
+
--ring: hsl(240 10% 3.9%);
|
|
45
|
+
|
|
46
|
+
--radius: 0.5rem;
|
|
47
|
+
|
|
48
|
+
--sidebar-width: 16rem;
|
|
49
|
+
--sidebar-width-mobile: 18rem;
|
|
50
|
+
--sidebar-width-collapsed: 3rem;
|
|
51
|
+
--sidebar-width-icon: 3rem;
|
|
52
|
+
--header-height: 3.5rem;
|
|
53
|
+
|
|
54
|
+
--sidebar-background: hsl(0 0% 98%);
|
|
55
|
+
--sidebar-foreground: hsl(240 5.3% 26.1%);
|
|
56
|
+
--sidebar-primary: hsl(240 5.9% 10%);
|
|
57
|
+
--sidebar-primary-foreground: hsl(0 0% 98%);
|
|
58
|
+
--sidebar-accent: hsl(240 4.8% 95.9%);
|
|
59
|
+
--sidebar-accent-foreground: hsl(240 5.9% 10%);
|
|
60
|
+
--sidebar-border: hsl(220 13% 91%);
|
|
61
|
+
--sidebar-ring: hsl(217.2 91.2% 59.8%);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
@theme inline {
|
|
65
|
+
--color-border: var(--border);
|
|
66
|
+
--color-input: var(--input);
|
|
67
|
+
--color-ring: var(--ring);
|
|
68
|
+
--color-background: var(--background);
|
|
69
|
+
--color-foreground: var(--foreground);
|
|
70
|
+
|
|
71
|
+
--color-primary: var(--primary);
|
|
72
|
+
--color-primary-foreground: var(--primary-foreground);
|
|
73
|
+
|
|
74
|
+
--color-secondary: var(--secondary);
|
|
75
|
+
--color-secondary-foreground: var(--secondary-foreground);
|
|
76
|
+
|
|
77
|
+
--color-destructive: var(--destructive);
|
|
78
|
+
--color-destructive-foreground: var(--destructive-foreground);
|
|
79
|
+
|
|
80
|
+
--color-muted: var(--muted);
|
|
81
|
+
--color-muted-foreground: var(--muted-foreground);
|
|
82
|
+
|
|
83
|
+
--color-accent: var(--accent);
|
|
84
|
+
--color-accent-foreground: var(--accent-foreground);
|
|
85
|
+
|
|
86
|
+
--color-popover: var(--popover);
|
|
87
|
+
--color-popover-foreground: var(--popover-foreground);
|
|
88
|
+
|
|
89
|
+
--color-card: var(--card);
|
|
90
|
+
--color-card-foreground: var(--card-foreground);
|
|
91
|
+
|
|
92
|
+
--color-sidebar: var(--sidebar-background);
|
|
93
|
+
--color-sidebar-foreground: var(--sidebar-foreground);
|
|
94
|
+
--color-sidebar-primary: var(--sidebar-primary);
|
|
95
|
+
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
|
96
|
+
--color-sidebar-accent: var(--sidebar-accent);
|
|
97
|
+
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
|
98
|
+
--color-sidebar-border: var(--sidebar-border);
|
|
99
|
+
--color-sidebar-ring: var(--sidebar-ring);
|
|
100
|
+
|
|
101
|
+
--radius-lg: var(--radius);
|
|
102
|
+
--radius-md: calc(var(--radius) - 2px);
|
|
103
|
+
--radius-sm: calc(var(--radius) - 4px);
|
|
104
|
+
|
|
105
|
+
--animate-accordion-down: accordion-down 0.2s ease-out;
|
|
106
|
+
--animate-accordion-up: accordion-up 0.2s ease-out;
|
|
107
|
+
|
|
108
|
+
@keyframes accordion-down {
|
|
109
|
+
from {
|
|
110
|
+
height: 0;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
to {
|
|
114
|
+
height: var(--radix-accordion-content-height);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
@keyframes accordion-up {
|
|
119
|
+
from {
|
|
120
|
+
height: var(--radix-accordion-content-height);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
to {
|
|
124
|
+
height: 0;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
@media (prefers-color-scheme: dark) {
|
|
130
|
+
:root {
|
|
131
|
+
--background: hsl(240 10% 3.9%);
|
|
132
|
+
--foreground: hsl(0 0% 98%);
|
|
133
|
+
|
|
134
|
+
--card: hsl(240 10% 3.9%);
|
|
135
|
+
--card-foreground: hsl(0 0% 98%);
|
|
136
|
+
|
|
137
|
+
--popover: hsl(240 10% 3.9%);
|
|
138
|
+
--popover-foreground: hsl(0 0% 98%);
|
|
139
|
+
|
|
140
|
+
--primary: hsl(0 0% 98%);
|
|
141
|
+
--primary-foreground: hsl(240 5.9% 10%);
|
|
142
|
+
|
|
143
|
+
--secondary: hsl(240 3.7% 15.9%);
|
|
144
|
+
--secondary-foreground: hsl(0 0% 98%);
|
|
145
|
+
|
|
146
|
+
--muted: hsl(240 3.7% 15.9%);
|
|
147
|
+
--muted-foreground: hsl(240 5% 64.9%);
|
|
148
|
+
|
|
149
|
+
--accent: hsl(240 3.7% 15.9%);
|
|
150
|
+
--accent-foreground: hsl(0 0% 98%);
|
|
151
|
+
|
|
152
|
+
--destructive: hsl(0 62.8% 30.6%);
|
|
153
|
+
--destructive-foreground: hsl(0 0% 98%);
|
|
154
|
+
|
|
155
|
+
--success: hsl(142.1 70.6% 45.3%);
|
|
156
|
+
--success-foreground: hsl(240 5.9% 10%);
|
|
157
|
+
|
|
158
|
+
--warning: hsl(48 96.5% 53.1%);
|
|
159
|
+
--warning-foreground: hsl(240 5.9% 10%);
|
|
160
|
+
|
|
161
|
+
--info: hsl(199.4 95.5% 53.8%);
|
|
162
|
+
--info-foreground: hsl(240 5.9% 10%);
|
|
163
|
+
|
|
164
|
+
--border: hsl(240 3.7% 15.9%);
|
|
165
|
+
--input: hsl(240 3.7% 15.9%);
|
|
166
|
+
--ring: hsl(240 4.9% 83.9%);
|
|
167
|
+
|
|
168
|
+
--sidebar-background: hsl(240 5.9% 10%);
|
|
169
|
+
--sidebar-foreground: hsl(240 4.8% 95.9%);
|
|
170
|
+
--sidebar-primary: hsl(224.3 76.3% 48%);
|
|
171
|
+
--sidebar-primary-foreground: hsl(0 0% 100%);
|
|
172
|
+
--sidebar-accent: hsl(240 3.7% 15.9%);
|
|
173
|
+
--sidebar-accent-foreground: hsl(240 4.8% 95.9%);
|
|
174
|
+
--sidebar-border: hsl(240 3.7% 15.9%);
|
|
175
|
+
--sidebar-ring: hsl(217.2 91.2% 59.8%);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
body {
|
|
180
|
+
background: var(--background);
|
|
181
|
+
color: var(--foreground);
|
|
182
|
+
font-family: Arial, Helvetica, sans-serif;
|
|
183
|
+
}
|
|
@@ -4,9 +4,11 @@ import { cn } from "@/lib/utils"
|
|
|
4
4
|
|
|
5
5
|
interface CarouselContextValue {
|
|
6
6
|
currentIndex: number
|
|
7
|
-
setCurrentIndex:
|
|
7
|
+
setCurrentIndex: React.Dispatch<React.SetStateAction<number>>
|
|
8
8
|
itemsCount: number
|
|
9
|
-
setItemsCount:
|
|
9
|
+
setItemsCount: React.Dispatch<React.SetStateAction<number>>
|
|
10
|
+
isTransitioning: boolean
|
|
11
|
+
setIsTransitioning: React.Dispatch<React.SetStateAction<boolean>>
|
|
10
12
|
}
|
|
11
13
|
|
|
12
14
|
const CarouselContext = React.createContext<CarouselContextValue | null>(null)
|
|
@@ -14,44 +16,42 @@ const CarouselContext = React.createContext<CarouselContextValue | null>(null)
|
|
|
14
16
|
interface CarouselProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
15
17
|
/** Auto-play interval in ms (0 to disable) */
|
|
16
18
|
autoPlay?: number
|
|
17
|
-
/** Loop back to start */
|
|
19
|
+
/** Loop back to start (ignored in this infinite implementation as it's always true-ish, but kept for API) */
|
|
18
20
|
loop?: boolean
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
/**
|
|
22
|
-
* Carousel/Slider component
|
|
23
|
-
*
|
|
24
|
-
* @example
|
|
25
|
-
* <Carousel>
|
|
26
|
-
* <CarouselContent>
|
|
27
|
-
* <CarouselItem>Slide 1</CarouselItem>
|
|
28
|
-
* <CarouselItem>Slide 2</CarouselItem>
|
|
29
|
-
* </CarouselContent>
|
|
30
|
-
* <CarouselPrevious />
|
|
31
|
-
* <CarouselNext />
|
|
32
|
-
* </Carousel>
|
|
24
|
+
* Carousel/Slider component with Infinite Looping
|
|
33
25
|
*/
|
|
34
26
|
const Carousel = React.forwardRef<HTMLDivElement, CarouselProps>(
|
|
35
27
|
({ className, children, autoPlay = 0, loop = true, ...props }, ref) => {
|
|
36
|
-
|
|
28
|
+
// Start at 1 because 0 is the clone of the last item
|
|
29
|
+
const [currentIndex, setCurrentIndex] = React.useState(1)
|
|
37
30
|
const [itemsCount, setItemsCount] = React.useState(0)
|
|
31
|
+
const [isTransitioning, setIsTransitioning] = React.useState(true)
|
|
32
|
+
const intervalRef = React.useRef<NodeJS.Timeout | null>(null)
|
|
38
33
|
|
|
39
34
|
React.useEffect(() => {
|
|
40
|
-
if (autoPlay > 0 && itemsCount >
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
return loop ? 0 : prev
|
|
45
|
-
}
|
|
46
|
-
return prev + 1
|
|
47
|
-
})
|
|
35
|
+
if (autoPlay > 0 && itemsCount > 1) {
|
|
36
|
+
intervalRef.current = setInterval(() => {
|
|
37
|
+
setIsTransitioning(true)
|
|
38
|
+
setCurrentIndex((prev) => prev + 1)
|
|
48
39
|
}, autoPlay)
|
|
49
|
-
return () =>
|
|
40
|
+
return () => {
|
|
41
|
+
if (intervalRef.current) clearInterval(intervalRef.current)
|
|
42
|
+
}
|
|
50
43
|
}
|
|
51
|
-
}, [autoPlay, itemsCount
|
|
44
|
+
}, [autoPlay, itemsCount])
|
|
52
45
|
|
|
53
46
|
return (
|
|
54
|
-
<CarouselContext.Provider value={{
|
|
47
|
+
<CarouselContext.Provider value={{
|
|
48
|
+
currentIndex,
|
|
49
|
+
setCurrentIndex,
|
|
50
|
+
itemsCount,
|
|
51
|
+
setItemsCount,
|
|
52
|
+
isTransitioning,
|
|
53
|
+
setIsTransitioning
|
|
54
|
+
}}>
|
|
55
55
|
<div
|
|
56
56
|
ref={ref}
|
|
57
57
|
className={cn("relative", className)}
|
|
@@ -72,19 +72,59 @@ const CarouselContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HT
|
|
|
72
72
|
const context = React.useContext(CarouselContext)
|
|
73
73
|
if (!context) throw new Error("CarouselContent must be used within Carousel")
|
|
74
74
|
|
|
75
|
-
const
|
|
75
|
+
const items = React.Children.toArray(children)
|
|
76
76
|
|
|
77
77
|
React.useEffect(() => {
|
|
78
|
-
context.setItemsCount(
|
|
79
|
-
}, [
|
|
78
|
+
context.setItemsCount(items.length)
|
|
79
|
+
}, [items.length, context])
|
|
80
|
+
|
|
81
|
+
// Clone first and last items for infinite loop illusion
|
|
82
|
+
const firstClone = items.length > 0 && React.isValidElement(items[0])
|
|
83
|
+
? React.cloneElement(items[0] as React.ReactElement, { key: "clone-first" })
|
|
84
|
+
: null
|
|
85
|
+
const lastClone = items.length > 0 && React.isValidElement(items[items.length - 1])
|
|
86
|
+
? React.cloneElement(items[items.length - 1] as React.ReactElement, { key: "clone-last" })
|
|
87
|
+
: null
|
|
88
|
+
|
|
89
|
+
// If we have items, prepend last-clone and append first-clone
|
|
90
|
+
const displayItems = items.length > 1 ? [lastClone, ...items, firstClone] : items
|
|
91
|
+
|
|
92
|
+
const handleTransitionEnd = () => {
|
|
93
|
+
if (items.length <= 1) return
|
|
94
|
+
|
|
95
|
+
// If reached the end clone (index = N + 1), snap back to first real item (index = 1)
|
|
96
|
+
if (context.currentIndex >= items.length + 1) {
|
|
97
|
+
context.setIsTransitioning(false)
|
|
98
|
+
context.setCurrentIndex(1)
|
|
99
|
+
}
|
|
100
|
+
// If reached the start clone (index = 0), snap forward to last real item (index = N)
|
|
101
|
+
else if (context.currentIndex <= 0) {
|
|
102
|
+
context.setIsTransitioning(false)
|
|
103
|
+
context.setCurrentIndex(items.length)
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Re-enable transition after a snap (on next frame)
|
|
108
|
+
React.useEffect(() => {
|
|
109
|
+
if (!context.isTransitioning) {
|
|
110
|
+
const timer = setTimeout(() => {
|
|
111
|
+
context.setIsTransitioning(true)
|
|
112
|
+
}, 50)
|
|
113
|
+
return () => clearTimeout(timer)
|
|
114
|
+
}
|
|
115
|
+
}, [context.isTransitioning, context])
|
|
80
116
|
|
|
81
117
|
return (
|
|
82
118
|
<div ref={ref} className={cn("overflow-hidden", className)} {...props}>
|
|
83
119
|
<div
|
|
84
|
-
className="
|
|
85
|
-
style={{
|
|
120
|
+
className="flex h-full w-full"
|
|
121
|
+
style={{
|
|
122
|
+
transform: `translateX(-${context.currentIndex * 100}%)`,
|
|
123
|
+
transition: context.isTransitioning ? 'transform 300ms ease-in-out' : 'none'
|
|
124
|
+
}}
|
|
125
|
+
onTransitionEnd={handleTransitionEnd}
|
|
86
126
|
>
|
|
87
|
-
{
|
|
127
|
+
{displayItems}
|
|
88
128
|
</div>
|
|
89
129
|
</div>
|
|
90
130
|
)
|
|
@@ -112,7 +152,10 @@ const CarouselPrevious = React.forwardRef<
|
|
|
112
152
|
const context = React.useContext(CarouselContext)
|
|
113
153
|
if (!context) throw new Error("CarouselPrevious must be used within Carousel")
|
|
114
154
|
|
|
115
|
-
const
|
|
155
|
+
const handlePrev = () => {
|
|
156
|
+
context.setIsTransitioning(true)
|
|
157
|
+
context.setCurrentIndex((prev) => prev - 1)
|
|
158
|
+
}
|
|
116
159
|
|
|
117
160
|
return (
|
|
118
161
|
<button
|
|
@@ -123,8 +166,7 @@ const CarouselPrevious = React.forwardRef<
|
|
|
123
166
|
"hover:bg-accent disabled:opacity-50",
|
|
124
167
|
className
|
|
125
168
|
)}
|
|
126
|
-
|
|
127
|
-
onClick={() => context.setCurrentIndex(context.currentIndex - 1)}
|
|
169
|
+
onClick={handlePrev}
|
|
128
170
|
aria-label="Previous slide"
|
|
129
171
|
{...props}
|
|
130
172
|
>
|
|
@@ -143,7 +185,10 @@ const CarouselNext = React.forwardRef<
|
|
|
143
185
|
const context = React.useContext(CarouselContext)
|
|
144
186
|
if (!context) throw new Error("CarouselNext must be used within Carousel")
|
|
145
187
|
|
|
146
|
-
const
|
|
188
|
+
const handleNext = () => {
|
|
189
|
+
context.setIsTransitioning(true)
|
|
190
|
+
context.setCurrentIndex((prev) => prev + 1)
|
|
191
|
+
}
|
|
147
192
|
|
|
148
193
|
return (
|
|
149
194
|
<button
|
|
@@ -154,8 +199,7 @@ const CarouselNext = React.forwardRef<
|
|
|
154
199
|
"hover:bg-accent disabled:opacity-50",
|
|
155
200
|
className
|
|
156
201
|
)}
|
|
157
|
-
|
|
158
|
-
onClick={() => context.setCurrentIndex(context.currentIndex + 1)}
|
|
202
|
+
onClick={handleNext}
|
|
159
203
|
aria-label="Next slide"
|
|
160
204
|
{...props}
|
|
161
205
|
>
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
3
|
+
import { cn } from "@/lib/utils"
|
|
4
|
+
|
|
5
|
+
import { ScrollArea } from "@/components/ui/scroll-area"
|
|
6
|
+
|
|
7
|
+
const Chatbot = React.forwardRef<
|
|
8
|
+
HTMLDivElement,
|
|
9
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
10
|
+
>(({ className, ...props }, ref) => (
|
|
11
|
+
<div
|
|
12
|
+
ref={ref}
|
|
13
|
+
className={cn(
|
|
14
|
+
"flex flex-col w-full h-[500px] border rounded-lg bg-background text-foreground shadow-sm overflow-hidden",
|
|
15
|
+
className
|
|
16
|
+
)}
|
|
17
|
+
{...props}
|
|
18
|
+
/>
|
|
19
|
+
))
|
|
20
|
+
Chatbot.displayName = "Chatbot"
|
|
21
|
+
|
|
22
|
+
const ChatbotHeader = React.forwardRef<
|
|
23
|
+
HTMLDivElement,
|
|
24
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
25
|
+
>(({ className, ...props }, ref) => (
|
|
26
|
+
<div
|
|
27
|
+
ref={ref}
|
|
28
|
+
className={cn(
|
|
29
|
+
"flex items-center px-4 py-3 border-b bg-muted/40",
|
|
30
|
+
className
|
|
31
|
+
)}
|
|
32
|
+
{...props}
|
|
33
|
+
/>
|
|
34
|
+
))
|
|
35
|
+
ChatbotHeader.displayName = "ChatbotHeader"
|
|
36
|
+
|
|
37
|
+
const ChatbotContent = React.forwardRef<
|
|
38
|
+
HTMLDivElement,
|
|
39
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
40
|
+
>(({ className, ...props }, ref) => (
|
|
41
|
+
<ScrollArea
|
|
42
|
+
ref={ref}
|
|
43
|
+
className={cn("flex-1 p-2 space-y-4 flex flex-col", className)}
|
|
44
|
+
scrollbarSize="thin"
|
|
45
|
+
{...props}
|
|
46
|
+
/>
|
|
47
|
+
))
|
|
48
|
+
ChatbotContent.displayName = "ChatbotContent"
|
|
49
|
+
|
|
50
|
+
const chatbotMessageVariants = cva(
|
|
51
|
+
"max-w-[80%] rounded-2xl px-4 py-2 text-sm break-words",
|
|
52
|
+
{
|
|
53
|
+
variants: {
|
|
54
|
+
variant: {
|
|
55
|
+
user: "bg-primary text-primary-foreground rounded-br-none ml-auto",
|
|
56
|
+
bot: "bg-muted text-foreground rounded-bl-none mr-auto",
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
defaultVariants: {
|
|
60
|
+
variant: "bot",
|
|
61
|
+
},
|
|
62
|
+
}
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
const ChatbotMessage = React.forwardRef<
|
|
66
|
+
HTMLDivElement,
|
|
67
|
+
React.HTMLAttributes<HTMLDivElement> &
|
|
68
|
+
VariantProps<typeof chatbotMessageVariants>
|
|
69
|
+
>(({ className, variant, ...props }, ref) => (
|
|
70
|
+
<div
|
|
71
|
+
ref={ref}
|
|
72
|
+
className={cn(chatbotMessageVariants({ variant }), className)}
|
|
73
|
+
{...props}
|
|
74
|
+
/>
|
|
75
|
+
))
|
|
76
|
+
ChatbotMessage.displayName = "ChatbotMessage"
|
|
77
|
+
|
|
78
|
+
const ChatbotFooter = React.forwardRef<
|
|
79
|
+
HTMLDivElement,
|
|
80
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
81
|
+
>(({ className, ...props }, ref) => (
|
|
82
|
+
<div
|
|
83
|
+
ref={ref}
|
|
84
|
+
className={cn("flex items-center p-3 border-t bg-background rounded-b-lg", className)}
|
|
85
|
+
{...props}
|
|
86
|
+
/>
|
|
87
|
+
))
|
|
88
|
+
ChatbotFooter.displayName = "ChatbotFooter"
|
|
89
|
+
|
|
90
|
+
export {
|
|
91
|
+
Chatbot,
|
|
92
|
+
ChatbotHeader,
|
|
93
|
+
ChatbotContent,
|
|
94
|
+
ChatbotMessage,
|
|
95
|
+
ChatbotFooter,
|
|
96
|
+
}
|