@evolution-soft/ui 1.0.0 → 1.0.3

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.
Files changed (78) hide show
  1. package/cli/index.js +386 -0
  2. package/components/button-icon-lottie/index.tsx +46 -0
  3. package/components/fullscreen-mode/index.tsx +82 -0
  4. package/components/header/components/buttons.tsx +102 -0
  5. package/components/header/index.tsx +146 -0
  6. package/components/loading-default/index.tsx +90 -0
  7. package/components/lottie-icon/index.tsx +78 -0
  8. package/components/not-found-default/index.tsx +68 -0
  9. package/components/settings-modal/index.tsx +225 -0
  10. package/components/sidebar/index.tsx +645 -0
  11. package/components/subtitle/index.tsx +60 -0
  12. package/components/theme-transition/index.tsx +142 -0
  13. package/components/title/index.tsx +66 -0
  14. package/components/tooltip-indicator/index.tsx +30 -0
  15. package/components/ui/accordion.tsx +66 -0
  16. package/components/ui/alert-dialog.tsx +157 -0
  17. package/components/ui/alert.tsx +66 -0
  18. package/components/ui/aspect-ratio.tsx +11 -0
  19. package/components/ui/avatar.tsx +53 -0
  20. package/components/ui/badge.tsx +46 -0
  21. package/components/ui/breadcrumb.tsx +109 -0
  22. package/components/ui/button.tsx +58 -0
  23. package/components/ui/calendar.tsx +78 -0
  24. package/components/ui/card.tsx +92 -0
  25. package/components/ui/carousel.tsx +241 -0
  26. package/components/ui/chart.tsx +360 -0
  27. package/components/ui/checkbox.tsx +32 -0
  28. package/components/ui/collapsible.tsx +33 -0
  29. package/components/ui/command.tsx +177 -0
  30. package/components/ui/context-menu.tsx +252 -0
  31. package/components/ui/dialog.tsx +135 -0
  32. package/components/ui/divisor.tsx +9 -0
  33. package/components/ui/drawer.tsx +132 -0
  34. package/components/ui/dropdown-menu.tsx +257 -0
  35. package/components/ui/emoji-picker.tsx +76 -0
  36. package/components/ui/form.tsx +168 -0
  37. package/components/ui/hover-card.tsx +44 -0
  38. package/components/ui/input-mask.tsx +46 -0
  39. package/components/ui/input-otp.tsx +77 -0
  40. package/components/ui/input.tsx +61 -0
  41. package/components/ui/label.tsx +24 -0
  42. package/components/ui/menubar.tsx +276 -0
  43. package/components/ui/multiselect.tsx +105 -0
  44. package/components/ui/navigation-menu.tsx +168 -0
  45. package/components/ui/pagination.tsx +127 -0
  46. package/components/ui/popover.tsx +48 -0
  47. package/components/ui/progress.tsx +31 -0
  48. package/components/ui/radio-group.tsx +45 -0
  49. package/components/ui/resizable.tsx +65 -0
  50. package/components/ui/scroll-area.tsx +58 -0
  51. package/components/ui/searchable-select.tsx +211 -0
  52. package/components/ui/select.tsx +189 -0
  53. package/components/ui/separator.tsx +28 -0
  54. package/components/ui/sheet.tsx +139 -0
  55. package/components/ui/sidebar.tsx +727 -0
  56. package/components/ui/skeleton.tsx +144 -0
  57. package/components/ui/slider.tsx +63 -0
  58. package/components/ui/sonner.tsx +26 -0
  59. package/components/ui/switch.tsx +31 -0
  60. package/components/ui/table.tsx +116 -0
  61. package/components/ui/tabs.tsx +76 -0
  62. package/components/ui/textarea.tsx +18 -0
  63. package/components/ui/theme-toggle.tsx +89 -0
  64. package/components/ui/toggle-group.tsx +73 -0
  65. package/components/ui/toggle.tsx +47 -0
  66. package/components/ui/tooltip.tsx +61 -0
  67. package/components/ui/use-mobile.ts +21 -0
  68. package/components/ui/utils.ts +6 -0
  69. package/contexts/AnimationSettingsContext.tsx +85 -0
  70. package/contexts/AuthContext.tsx +80 -0
  71. package/contexts/ThemeContext.tsx +70 -0
  72. package/hooks/useAnimationSettings.ts +2 -0
  73. package/hooks/usePermissions.ts +4 -0
  74. package/lib/persistentFilters.ts +120 -0
  75. package/lib/utils.ts +2 -0
  76. package/package.json +11 -2
  77. package/stores/theme.ts +30 -0
  78. package/stores/useThemeStore.ts +32 -0
package/cli/index.js ADDED
@@ -0,0 +1,386 @@
1
+ #!/usr/bin/env node
2
+ import { existsSync, mkdirSync, copyFileSync, readdirSync, statSync, readFileSync } from 'node:fs';
3
+ import { join, dirname, extname } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { createInterface } from 'node:readline';
6
+
7
+ // ─── ANSI colours (zero deps) ────────────────────────────────────────────────
8
+ const c = {
9
+ reset: '\x1b[0m',
10
+ bold: '\x1b[1m',
11
+ dim: '\x1b[2m',
12
+ green: '\x1b[32m',
13
+ cyan: '\x1b[36m',
14
+ yellow: '\x1b[33m',
15
+ red: '\x1b[31m',
16
+ blue: '\x1b[34m',
17
+ white: '\x1b[37m',
18
+ };
19
+ const clr = (color, text) => `${c[color]}${text}${c.reset}`;
20
+ const ok = (msg) => console.log(`${clr('green', '✔')} ${msg}`);
21
+ const info = (msg) => console.log(`${clr('cyan', 'ℹ')} ${msg}`);
22
+ const warn = (msg) => console.log(`${clr('yellow', '⚠')} ${msg}`);
23
+ const err = (msg) => console.error(`${clr('red', '✖')} ${msg}`);
24
+ const title = (msg) => console.log(`\n${clr('bold', clr('blue', msg))}`);
25
+
26
+ // ─── Paths ────────────────────────────────────────────────────────────────────
27
+ const PKG_ROOT = join(dirname(fileURLToPath(import.meta.url)), '..');
28
+ const CWD = process.cwd();
29
+
30
+ // ─── Component registry ───────────────────────────────────────────────────────
31
+ // Each entry: { name, src relative to PKG_ROOT, dest relative to CWD, category }
32
+ function buildRegistry() {
33
+ const registry = [];
34
+
35
+ // UI primitives — components/ui/*.{tsx,ts}
36
+ const uiDir = join(PKG_ROOT, 'components', 'ui');
37
+ for (const file of readdirSync(uiDir)) {
38
+ const ext = extname(file);
39
+ if (ext !== '.tsx' && ext !== '.ts') continue;
40
+ const name = file.replace(ext, '');
41
+ registry.push({
42
+ name,
43
+ src: join('components', 'ui', file),
44
+ dest: join('components', 'ui', file),
45
+ category: 'ui',
46
+ });
47
+ }
48
+
49
+ // Custom components — components/<folder>/index.tsx
50
+ const compDir = join(PKG_ROOT, 'components');
51
+ for (const folder of readdirSync(compDir)) {
52
+ if (folder === 'ui') continue;
53
+ const folderPath = join(compDir, folder);
54
+ if (!statSync(folderPath).isDirectory()) continue;
55
+ const indexFile = join(folderPath, 'index.tsx');
56
+ if (!existsSync(indexFile)) continue;
57
+ registry.push({
58
+ name: folder,
59
+ src: join('components', folder, 'index.tsx'),
60
+ dest: join('components', folder, 'index.tsx'),
61
+ category: 'custom',
62
+ });
63
+ // Sub-components (e.g. header/components/*.tsx)
64
+ const subCompDir = join(folderPath, 'components');
65
+ if (existsSync(subCompDir) && statSync(subCompDir).isDirectory()) {
66
+ for (const sub of readdirSync(subCompDir)) {
67
+ const ext = extname(sub);
68
+ if (ext !== '.tsx' && ext !== '.ts') continue;
69
+ registry.push({
70
+ name: `${folder}/${sub.replace(ext, '')}`,
71
+ src: join('components', folder, 'components', sub),
72
+ dest: join('components', folder, 'components', sub),
73
+ category: 'custom',
74
+ });
75
+ }
76
+ }
77
+ }
78
+
79
+ // Contexts
80
+ const ctxDir = join(PKG_ROOT, 'contexts');
81
+ if (existsSync(ctxDir)) {
82
+ for (const file of readdirSync(ctxDir)) {
83
+ const ext = extname(file);
84
+ if (ext !== '.tsx' && ext !== '.ts') continue;
85
+ registry.push({
86
+ name: `context/${file.replace(ext, '')}`,
87
+ src: join('contexts', file),
88
+ dest: join('contexts', file),
89
+ category: 'context',
90
+ });
91
+ }
92
+ }
93
+
94
+ // Hooks
95
+ const hooksDir = join(PKG_ROOT, 'hooks');
96
+ if (existsSync(hooksDir)) {
97
+ for (const file of readdirSync(hooksDir)) {
98
+ const ext = extname(file);
99
+ if (ext !== '.tsx' && ext !== '.ts') continue;
100
+ registry.push({
101
+ name: `hook/${file.replace(ext, '')}`,
102
+ src: join('hooks', file),
103
+ dest: join('hooks', file),
104
+ category: 'hook',
105
+ });
106
+ }
107
+ }
108
+
109
+ // Lib
110
+ const libDir = join(PKG_ROOT, 'lib');
111
+ if (existsSync(libDir)) {
112
+ for (const file of readdirSync(libDir)) {
113
+ const ext = extname(file);
114
+ if (ext !== '.tsx' && ext !== '.ts') continue;
115
+ registry.push({
116
+ name: `lib/${file.replace(ext, '')}`,
117
+ src: join('lib', file),
118
+ dest: join('lib', file),
119
+ category: 'lib',
120
+ });
121
+ }
122
+ }
123
+
124
+ // Stores
125
+ const storesDir = join(PKG_ROOT, 'stores');
126
+ if (existsSync(storesDir)) {
127
+ for (const file of readdirSync(storesDir)) {
128
+ const ext = extname(file);
129
+ if (ext !== '.tsx' && ext !== '.ts') continue;
130
+ registry.push({
131
+ name: `store/${file.replace(ext, '')}`,
132
+ src: join('stores', file),
133
+ dest: join('stores', file),
134
+ category: 'store',
135
+ });
136
+ }
137
+ }
138
+
139
+ return registry;
140
+ }
141
+
142
+ // ─── File copy helper ─────────────────────────────────────────────────────────
143
+ function copyComponent(entry, force = false) {
144
+ const srcPath = join(PKG_ROOT, entry.src);
145
+ const destPath = join(CWD, entry.dest);
146
+ const destDir = dirname(destPath);
147
+
148
+ if (!existsSync(srcPath)) {
149
+ err(`Source not found: ${entry.src}`);
150
+ return false;
151
+ }
152
+
153
+ if (existsSync(destPath) && !force) {
154
+ warn(`Already exists (use --force to overwrite): ${entry.dest}`);
155
+ return false;
156
+ }
157
+
158
+ mkdirSync(destDir, { recursive: true });
159
+ copyFileSync(srcPath, destPath);
160
+ ok(`${entry.dest}`);
161
+ return true;
162
+ }
163
+
164
+ // ─── Commands ─────────────────────────────────────────────────────────────────
165
+ function cmdList(registry) {
166
+ title('@evolution-soft/ui — Componentes disponíveis');
167
+
168
+ const categories = {
169
+ ui: { label: 'UI Primitivos', items: [] },
170
+ custom: { label: 'Componentes Customizados', items: [] },
171
+ context: { label: 'Contextos', items: [] },
172
+ hook: { label: 'Hooks', items: [] },
173
+ lib: { label: 'Lib', items: [] },
174
+ store: { label: 'Stores', items: [] },
175
+ };
176
+
177
+ for (const entry of registry) {
178
+ categories[entry.category]?.items.push(entry.name);
179
+ }
180
+
181
+ for (const [, { label, items }] of Object.entries(categories)) {
182
+ if (!items.length) continue;
183
+ console.log(`\n ${clr('bold', label)}`);
184
+ for (const name of items) {
185
+ console.log(` ${clr('dim', '·')} ${name}`);
186
+ }
187
+ }
188
+ console.log('');
189
+ }
190
+
191
+ function cmdAdd(registry, names, force) {
192
+ if (!names.length) {
193
+ err('Especifica pelo menos um componente. Exemplo: npx @evolution-soft/ui add button');
194
+ err('Ou adiciona todos: npx @evolution-soft/ui add --all');
195
+ process.exit(1);
196
+ }
197
+
198
+ const all = names.includes('--all');
199
+ const targets = all ? registry : registry.filter(e => names.includes(e.name));
200
+
201
+ if (!all && targets.length === 0) {
202
+ err(`Componente(s) não encontrado(s): ${names.join(', ')}`);
203
+ info('Usa "npx @evolution-soft/ui list" para ver os disponíveis.');
204
+ process.exit(1);
205
+ }
206
+
207
+ title(all ? 'Adicionando todos os componentes…' : `Adicionando: ${names.join(', ')}`);
208
+
209
+ let added = 0;
210
+ let skipped = 0;
211
+ for (const entry of targets) {
212
+ const result = copyComponent(entry, force);
213
+ result ? added++ : skipped++;
214
+ }
215
+
216
+ console.log('');
217
+ ok(`${added} ficheiro(s) adicionado(s). ${skipped ? clr('yellow', `${skipped} ignorado(s).`) : ''}`);
218
+ if (skipped) info('Usa --force para sobrescrever ficheiros existentes.');
219
+ }
220
+
221
+ function cmdUpdate(registry, force) {
222
+ title('Actualizando componentes existentes…');
223
+
224
+ const existing = registry.filter(e => existsSync(join(CWD, e.dest)));
225
+
226
+ if (!existing.length) {
227
+ warn('Nenhum componente encontrado no projecto. Usa "add" para instalar.');
228
+ process.exit(0);
229
+ }
230
+
231
+ info(`${existing.length} componente(s) encontrado(s) no projecto.`);
232
+ console.log('');
233
+
234
+ let updated = 0;
235
+ for (const entry of existing) {
236
+ copyComponent(entry, true);
237
+ updated++;
238
+ }
239
+
240
+ console.log('');
241
+ ok(`${updated} componente(s) actualizado(s).`);
242
+ }
243
+
244
+ async function cmdInteractive(registry) {
245
+ title('@evolution-soft/ui — Assistente de instalação');
246
+ console.log(`\n ${clr('dim', 'Selecciona os componentes que queres adicionar ao projecto.')}\n`);
247
+
248
+ const categories = {};
249
+ for (const entry of registry) {
250
+ if (!categories[entry.category]) categories[entry.category] = [];
251
+ categories[entry.category].push(entry);
252
+ }
253
+
254
+ const categoryLabels = {
255
+ ui: 'UI Primitivos',
256
+ custom: 'Componentes Customizados',
257
+ context: 'Contextos',
258
+ hook: 'Hooks',
259
+ lib: 'Lib',
260
+ store: 'Stores',
261
+ };
262
+
263
+ const allEntries = [];
264
+ let idx = 1;
265
+ for (const [cat, entries] of Object.entries(categories)) {
266
+ console.log(` ${clr('bold', categoryLabels[cat] ?? cat)}`);
267
+ for (const entry of entries) {
268
+ const exists = existsSync(join(CWD, entry.dest));
269
+ const statusTag = exists ? clr('dim', '[instalado]') : '';
270
+ console.log(` ${clr('cyan', String(idx).padStart(2, ' '))}. ${entry.name} ${statusTag}`);
271
+ allEntries.push(entry);
272
+ idx++;
273
+ }
274
+ console.log('');
275
+ }
276
+
277
+ console.log(` ${clr('cyan', ' 0')}. ${clr('bold', 'Todos os componentes')}`);
278
+ console.log('');
279
+
280
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
281
+ const question = (q) => new Promise(resolve => rl.question(q, resolve));
282
+
283
+ const answer = await question(` ${clr('bold', 'Números dos componentes (ex: 1,3,5 ou 0 para todos):')} `);
284
+ rl.close();
285
+
286
+ const selected = answer.trim() === '0'
287
+ ? allEntries
288
+ : answer.split(',').map(n => allEntries[parseInt(n.trim()) - 1]).filter(Boolean);
289
+
290
+ if (!selected.length) {
291
+ warn('Nenhum componente seleccionado.');
292
+ process.exit(0);
293
+ }
294
+
295
+ const forceAnswer = await new Promise(resolve => {
296
+ const rl2 = createInterface({ input: process.stdin, output: process.stdout });
297
+ rl2.question(`\n ${clr('bold', 'Sobrescrever ficheiros existentes? (s/N):')} `, ans => {
298
+ rl2.close();
299
+ resolve(ans.trim().toLowerCase() === 's');
300
+ });
301
+ });
302
+
303
+ console.log('');
304
+ title(`Adicionando ${selected.length} componente(s)…`);
305
+
306
+ let added = 0;
307
+ let skipped = 0;
308
+ for (const entry of selected) {
309
+ const result = copyComponent(entry, forceAnswer);
310
+ result ? added++ : skipped++;
311
+ }
312
+
313
+ console.log('');
314
+ ok(`${added} ficheiro(s) adicionado(s). ${skipped ? clr('yellow', `${skipped} ignorado(s).`) : ''}`);
315
+ }
316
+
317
+ function printHelp() {
318
+ console.log(`
319
+ ${clr('bold', '@evolution-soft/ui')} — CLI de componentes
320
+
321
+ ${clr('bold', 'Uso:')}
322
+ ${clr('cyan', 'npx @evolution-soft/ui')} Assistente interactivo
323
+ ${clr('cyan', 'npx @evolution-soft/ui list')} Lista todos os componentes
324
+ ${clr('cyan', 'npx @evolution-soft/ui add <nome>')} Adiciona componente(s) específico(s)
325
+ ${clr('cyan', 'npx @evolution-soft/ui add --all')} Adiciona todos os componentes
326
+ ${clr('cyan', 'npx @evolution-soft/ui update')} Actualiza componentes já instalados
327
+ ${clr('cyan', 'npx @evolution-soft/ui help')} Mostra esta ajuda
328
+
329
+ ${clr('bold', 'Opções:')}
330
+ ${clr('cyan', '--force')} Sobrescreve ficheiros existentes
331
+
332
+ ${clr('bold', 'Exemplos:')}
333
+ npx @evolution-soft/ui add button
334
+ npx @evolution-soft/ui add button card badge
335
+ npx @evolution-soft/ui add sidebar --force
336
+ npx @evolution-soft/ui add --all
337
+ npx @evolution-soft/ui update
338
+ `);
339
+ }
340
+
341
+ // ─── Main ─────────────────────────────────────────────────────────────────────
342
+ (async () => {
343
+ const args = process.argv.slice(2);
344
+ const command = args[0];
345
+ const force = args.includes('--force');
346
+ const filteredArgs = args.filter(a => a !== '--force');
347
+
348
+ let registry;
349
+ try {
350
+ registry = buildRegistry();
351
+ } catch (e) {
352
+ err('Erro ao carregar o registo de componentes: ' + e.message);
353
+ process.exit(1);
354
+ }
355
+
356
+ switch (command) {
357
+ case 'list':
358
+ cmdList(registry);
359
+ break;
360
+
361
+ case 'add': {
362
+ const names = filteredArgs.slice(1);
363
+ cmdAdd(registry, names, force);
364
+ break;
365
+ }
366
+
367
+ case 'update':
368
+ cmdUpdate(registry, force);
369
+ break;
370
+
371
+ case 'help':
372
+ case '--help':
373
+ case '-h':
374
+ printHelp();
375
+ break;
376
+
377
+ case undefined:
378
+ await cmdInteractive(registry);
379
+ break;
380
+
381
+ default:
382
+ err(`Comando desconhecido: "${command}"`);
383
+ printHelp();
384
+ process.exit(1);
385
+ }
386
+ })();
@@ -0,0 +1,46 @@
1
+ import { useEffect, useState } from "react";
2
+
3
+ import { LottieIcon } from "@/components/lottie-icon";
4
+
5
+ import { Button } from "@/components/ui/button";
6
+
7
+ export function ButtonIconLottie({ onToggle, icon, darkIcon, disabled }: { onToggle: () => void, icon: any, darkIcon: any, disabled?: boolean }) {
8
+ const [isHovered, setIsHovered] = useState(false);
9
+ const [isDark, setIsDark] = useState(false);
10
+
11
+ useEffect(() => {
12
+ const checkDarkMode = () => {
13
+ setIsDark(document.documentElement.classList.contains('dark'));
14
+ };
15
+
16
+ checkDarkMode();
17
+
18
+ const observer = new MutationObserver(checkDarkMode);
19
+ observer.observe(document.documentElement, {
20
+ attributes: true,
21
+ attributeFilter: ['class']
22
+ });
23
+
24
+ return () => observer.disconnect();
25
+ }, []);
26
+
27
+ return (
28
+ <Button
29
+ variant="outline"
30
+ size="sm"
31
+ onClick={onToggle}
32
+ className="text-gray-500 hover:text-gray-700 p-2"
33
+ disabled={disabled}
34
+ onMouseEnter={() => setIsHovered(true)}
35
+ onMouseLeave={() => setIsHovered(false)}
36
+ >
37
+ <LottieIcon
38
+ lightAnimationData={icon}
39
+ darkAnimationData={darkIcon}
40
+ className="h-4 w-4 lg:h-6 lg:w-6"
41
+ shouldPlay={isHovered}
42
+ isDark={isDark}
43
+ />
44
+ </Button>
45
+ );
46
+ }
@@ -0,0 +1,82 @@
1
+ 'use client'
2
+ import { useEffect, useState } from 'react'
3
+ import { BsFullscreen, BsFullscreenExit } from 'react-icons/bs';
4
+ import { motion } from 'framer-motion';
5
+ import { Button } from '@/components/ui/button';
6
+ import { cn } from '@/components/ui/utils';
7
+
8
+ interface ToggleFullScreenModeButtonProps {
9
+ collapsed?: boolean;
10
+ className?: string;
11
+ }
12
+
13
+ export function ToggleFullScreenModeButton({ collapsed = false, className }: ToggleFullScreenModeButtonProps) {
14
+ const [isFullscreen, setIsFullscreen] = useState(false);
15
+ const [isToggling, setIsToggling] = useState(false);
16
+
17
+ useEffect(() => {
18
+ const handleFullscreenChange = () => {
19
+ setIsFullscreen(!!document.fullscreenElement);
20
+ setIsToggling(false);
21
+ };
22
+
23
+ document.addEventListener('fullscreenchange', handleFullscreenChange);
24
+
25
+ return () => {
26
+ document.removeEventListener('fullscreenchange', handleFullscreenChange);
27
+ };
28
+ }, []);
29
+
30
+ const toggleFullscreen = () => {
31
+ setIsToggling(true);
32
+
33
+ if (!document.fullscreenElement) {
34
+ document.documentElement.requestFullscreen()
35
+ .catch((err) => {
36
+ console.error(`Error attempting to enable full-screen mode: ${err.message} (${err.name})`);
37
+ setIsToggling(false);
38
+ });
39
+ } else {
40
+ document.exitFullscreen()
41
+ .catch((err) => {
42
+ console.error(`Error attempting to disable full-screen mode: ${err.message} (${err.name})`);
43
+ setIsToggling(false);
44
+ });
45
+ }
46
+ };
47
+
48
+ return (
49
+ <Button
50
+ variant="ghost"
51
+ size={collapsed ? 'icon' : 'default'}
52
+ className={cn(
53
+ "transition-none hover:bg-accent",
54
+ collapsed
55
+ ? "h-10 w-10 p-0"
56
+ : "w-full justify-start gap-3 px-3 py-2 h-auto",
57
+ className
58
+ )}
59
+ onClick={toggleFullscreen}
60
+ title={isFullscreen ? 'Sair da tela cheia' : 'Entrar em tela cheia'}
61
+ disabled={isToggling}
62
+ >
63
+ <motion.div
64
+ className="relative flex items-center"
65
+ initial={{ rotate: 0 }}
66
+ animate={{ rotate: isFullscreen ? 180 : 0 }}
67
+ transition={{ duration: 0.3 }}
68
+ >
69
+ {isFullscreen ? (
70
+ <BsFullscreenExit className="h-4 w-4 shrink-0" />
71
+ ) : (
72
+ <BsFullscreen className="h-4 w-4 shrink-0" />
73
+ )}
74
+ </motion.div>
75
+ {!collapsed && (
76
+ <span className="font-medium">
77
+ {isFullscreen ? 'Modo Janela' : 'Tela Cheia'}
78
+ </span>
79
+ )}
80
+ </Button>
81
+ )
82
+ }
@@ -0,0 +1,102 @@
1
+ 'use client'
2
+ import React from "react";
3
+
4
+ import { Button } from "@/components/ui/button";
5
+
6
+ import { Transition } from "@headlessui/react";
7
+
8
+ type ButtonProps = React.ComponentProps<typeof Button>;
9
+
10
+ interface ButtonsProps {
11
+ button?: ButtonProps;
12
+ secondButton?: ButtonProps;
13
+ thirdButton?: ButtonProps;
14
+ fourButton?: ButtonProps;
15
+ customItem?: React.ReactNode;
16
+ }
17
+
18
+ export function Buttons(props: ButtonsProps) {
19
+ return (
20
+ <>
21
+ <div className="flex gap-4 flex-col items-center justify-center md:flex-row">
22
+ <Transition
23
+ show={!!props.fourButton}
24
+ enter="transition ease-out duration-300"
25
+ enterFrom="transform opacity-0 scale-95"
26
+ enterTo="transform opacity-100 scale-100"
27
+ leave="transition ease-in duration-300"
28
+ leaveFrom="transform opacity-100 scale-100"
29
+ leaveTo="transform opacity-0 scale-95"
30
+ >
31
+ {props.fourButton && <Button {...props.fourButton} />}
32
+ </Transition>
33
+ <Transition
34
+ show={!!props.customItem}
35
+ enter="transition ease-out duration-300"
36
+ enterFrom="transform opacity-0 scale-95"
37
+ enterTo="transform opacity-100 scale-100"
38
+ leave="transition ease-in duration-300"
39
+ leaveFrom="transform opacity-100 scale-100"
40
+ leaveTo="transform opacity-0 scale-95"
41
+ >
42
+ {props.customItem}
43
+ </Transition>
44
+ <Transition
45
+ show={!!props.thirdButton}
46
+ enter="transition ease-out duration-300"
47
+ enterFrom="transform opacity-0 scale-95"
48
+ enterTo="transform opacity-100 scale-100"
49
+ leave="transition ease-in duration-300"
50
+ leaveFrom="transform opacity-100 scale-100"
51
+ leaveTo="transform opacity-0 scale-95"
52
+ >
53
+ {props.thirdButton && <Button {...props.thirdButton} />}
54
+ </Transition>
55
+ <Transition
56
+ show={!!props.customItem}
57
+ enter="transition ease-out duration-300"
58
+ enterFrom="transform opacity-0 scale-95"
59
+ enterTo="transform opacity-100 scale-100"
60
+ leave="transition ease-in duration-300"
61
+ leaveFrom="transform opacity-100 scale-100"
62
+ leaveTo="transform opacity-0 scale-95"
63
+ >
64
+ {props.customItem}
65
+ </Transition>
66
+ <Transition
67
+ show={!!props.secondButton}
68
+ enter="transition ease-out duration-300"
69
+ enterFrom="transform opacity-0 scale-95"
70
+ enterTo="transform opacity-100 scale-100"
71
+ leave="transition ease-in duration-300"
72
+ leaveFrom="transform opacity-100 scale-100"
73
+ leaveTo="transform opacity-0 scale-95"
74
+ >
75
+ {props.secondButton && <Button {...props.secondButton} />}
76
+ </Transition>
77
+ <Transition
78
+ show={!!props.customItem}
79
+ enter="transition ease-out duration-300"
80
+ enterFrom="transform opacity-0 scale-95"
81
+ enterTo="transform opacity-100 scale-100"
82
+ leave="transition ease-in duration-300"
83
+ leaveFrom="transform opacity-100 scale-100"
84
+ leaveTo="transform opacity-0 scale-95"
85
+ >
86
+ {props.customItem}
87
+ </Transition>
88
+ <Transition
89
+ show={!!props.button}
90
+ enter="transition ease-out duration-300"
91
+ enterFrom="transform opacity-0 scale-95"
92
+ enterTo="transform opacity-100 scale-100"
93
+ leave="transition ease-in duration-300"
94
+ leaveFrom="transform opacity-100 scale-100"
95
+ leaveTo="transform opacity-0 scale-95"
96
+ >
97
+ {props.button && <Button {...props.button} />}
98
+ </Transition>
99
+ </div>
100
+ </>
101
+ )
102
+ }