@evolution-soft/ui 1.0.0 → 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/cli/index.mjs +386 -0
- package/components/button-icon-lottie/index.tsx +46 -0
- package/components/fullscreen-mode/index.tsx +82 -0
- package/components/header/components/buttons.tsx +102 -0
- package/components/header/index.tsx +146 -0
- package/components/loading-default/index.tsx +90 -0
- package/components/lottie-icon/index.tsx +78 -0
- package/components/not-found-default/index.tsx +68 -0
- package/components/settings-modal/index.tsx +225 -0
- package/components/sidebar/index.tsx +645 -0
- package/components/subtitle/index.tsx +60 -0
- package/components/theme-transition/index.tsx +142 -0
- package/components/title/index.tsx +66 -0
- package/components/tooltip-indicator/index.tsx +30 -0
- package/components/ui/accordion.tsx +66 -0
- package/components/ui/alert-dialog.tsx +157 -0
- package/components/ui/alert.tsx +66 -0
- package/components/ui/aspect-ratio.tsx +11 -0
- package/components/ui/avatar.tsx +53 -0
- package/components/ui/badge.tsx +46 -0
- package/components/ui/breadcrumb.tsx +109 -0
- package/components/ui/button.tsx +58 -0
- package/components/ui/calendar.tsx +78 -0
- package/components/ui/card.tsx +92 -0
- package/components/ui/carousel.tsx +241 -0
- package/components/ui/chart.tsx +360 -0
- package/components/ui/checkbox.tsx +32 -0
- package/components/ui/collapsible.tsx +33 -0
- package/components/ui/command.tsx +177 -0
- package/components/ui/context-menu.tsx +252 -0
- package/components/ui/dialog.tsx +135 -0
- package/components/ui/divisor.tsx +9 -0
- package/components/ui/drawer.tsx +132 -0
- package/components/ui/dropdown-menu.tsx +257 -0
- package/components/ui/emoji-picker.tsx +76 -0
- package/components/ui/form.tsx +168 -0
- package/components/ui/hover-card.tsx +44 -0
- package/components/ui/input-mask.tsx +46 -0
- package/components/ui/input-otp.tsx +77 -0
- package/components/ui/input.tsx +61 -0
- package/components/ui/label.tsx +24 -0
- package/components/ui/menubar.tsx +276 -0
- package/components/ui/multiselect.tsx +105 -0
- package/components/ui/navigation-menu.tsx +168 -0
- package/components/ui/pagination.tsx +127 -0
- package/components/ui/popover.tsx +48 -0
- package/components/ui/progress.tsx +31 -0
- package/components/ui/radio-group.tsx +45 -0
- package/components/ui/resizable.tsx +65 -0
- package/components/ui/scroll-area.tsx +58 -0
- package/components/ui/searchable-select.tsx +211 -0
- package/components/ui/select.tsx +189 -0
- package/components/ui/separator.tsx +28 -0
- package/components/ui/sheet.tsx +139 -0
- package/components/ui/sidebar.tsx +727 -0
- package/components/ui/skeleton.tsx +144 -0
- package/components/ui/slider.tsx +63 -0
- package/components/ui/sonner.tsx +26 -0
- package/components/ui/switch.tsx +31 -0
- package/components/ui/table.tsx +116 -0
- package/components/ui/tabs.tsx +76 -0
- package/components/ui/textarea.tsx +18 -0
- package/components/ui/theme-toggle.tsx +89 -0
- package/components/ui/toggle-group.tsx +73 -0
- package/components/ui/toggle.tsx +47 -0
- package/components/ui/tooltip.tsx +61 -0
- package/components/ui/use-mobile.ts +21 -0
- package/components/ui/utils.ts +6 -0
- package/contexts/AnimationSettingsContext.tsx +85 -0
- package/contexts/AuthContext.tsx +80 -0
- package/contexts/ThemeContext.tsx +70 -0
- package/hooks/useAnimationSettings.ts +2 -0
- package/hooks/usePermissions.ts +4 -0
- package/lib/persistentFilters.ts +120 -0
- package/lib/utils.ts +2 -0
- package/package.json +11 -2
- package/stores/theme.ts +30 -0
- package/stores/useThemeStore.ts +32 -0
package/cli/index.mjs
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
|
+
}
|