@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.
Files changed (78) hide show
  1. package/cli/index.mjs +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
@@ -0,0 +1,61 @@
1
+
2
+
3
+ import * as React from "react";
4
+ import * as TooltipPrimitive from "@radix-ui/react-tooltip";
5
+
6
+ import { cn } from "./utils";
7
+
8
+ function TooltipProvider({
9
+ delayDuration = 0,
10
+ ...props
11
+ }: React.ComponentProps<typeof TooltipPrimitive.Provider>) {
12
+ return (
13
+ <TooltipPrimitive.Provider
14
+ data-slot="tooltip-provider"
15
+ delayDuration={delayDuration}
16
+ {...props}
17
+ />
18
+ );
19
+ }
20
+
21
+ function Tooltip({
22
+ ...props
23
+ }: React.ComponentProps<typeof TooltipPrimitive.Root>) {
24
+ return (
25
+ <TooltipProvider>
26
+ <TooltipPrimitive.Root data-slot="tooltip" {...props} />
27
+ </TooltipProvider>
28
+ );
29
+ }
30
+
31
+ function TooltipTrigger({
32
+ ...props
33
+ }: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
34
+ return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />;
35
+ }
36
+
37
+ function TooltipContent({
38
+ className,
39
+ sideOffset = 0,
40
+ children,
41
+ ...props
42
+ }: React.ComponentProps<typeof TooltipPrimitive.Content>) {
43
+ return (
44
+ <TooltipPrimitive.Portal>
45
+ <TooltipPrimitive.Content
46
+ data-slot="tooltip-content"
47
+ sideOffset={sideOffset}
48
+ className={cn(
49
+ "bg-primary text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance",
50
+ className,
51
+ )}
52
+ {...props}
53
+ >
54
+ {children}
55
+ <TooltipPrimitive.Arrow className="bg-primary fill-primary z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]" />
56
+ </TooltipPrimitive.Content>
57
+ </TooltipPrimitive.Portal>
58
+ );
59
+ }
60
+
61
+ export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
@@ -0,0 +1,21 @@
1
+ import * as React from "react";
2
+
3
+ const MOBILE_BREAKPOINT = 768;
4
+
5
+ export function useIsMobile() {
6
+ const [isMobile, setIsMobile] = React.useState<boolean | undefined>(
7
+ undefined,
8
+ );
9
+
10
+ React.useEffect(() => {
11
+ const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
12
+ const onChange = () => {
13
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
14
+ };
15
+ mql.addEventListener("change", onChange);
16
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
17
+ return () => mql.removeEventListener("change", onChange);
18
+ }, []);
19
+
20
+ return !!isMobile;
21
+ }
@@ -0,0 +1,6 @@
1
+ import { clsx, type ClassValue } from "clsx";
2
+ import { twMerge } from "tailwind-merge";
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs));
6
+ }
@@ -0,0 +1,85 @@
1
+ 'use client';
2
+
3
+ import { createContext, useContext, useState, useEffect } from 'react';
4
+ import type { ReactNode } from 'react';
5
+
6
+ interface AnimationSettingsContextType {
7
+ lottieAnimationsEnabled: boolean;
8
+ otherLottieAnimationsEnabled: boolean;
9
+ setLottieAnimationsEnabled: (enabled: boolean) => void;
10
+ setOtherLottieAnimationsEnabled: (enabled: boolean) => void;
11
+ toggleLottieAnimations: () => void;
12
+ toggleOtherLottieAnimations: () => void;
13
+ }
14
+
15
+ const AnimationSettingsContext = createContext<AnimationSettingsContextType | undefined>(undefined);
16
+
17
+ interface AnimationSettingsProviderProps {
18
+ children: ReactNode;
19
+ }
20
+
21
+ export function AnimationSettingsProvider({ children }: AnimationSettingsProviderProps) {
22
+ const [lottieAnimationsEnabled, setLottieAnimationsEnabledState] = useState(true);
23
+ const [otherLottieAnimationsEnabled, setOtherLottieAnimationsEnabledState] = useState(true);
24
+
25
+ useEffect(() => {
26
+ const savedAnimations = localStorage.getItem('lottie-animations-enabled');
27
+ if (savedAnimations !== null) {
28
+ setLottieAnimationsEnabledState(JSON.parse(savedAnimations));
29
+ }
30
+
31
+ const savedOtherAnimations = localStorage.getItem('other-lottie-animations-enabled');
32
+ if (savedOtherAnimations !== null) {
33
+ setOtherLottieAnimationsEnabledState(JSON.parse(savedOtherAnimations));
34
+ }
35
+ }, []);
36
+
37
+ const setLottieAnimationsEnabled = (enabled: boolean) => {
38
+ setLottieAnimationsEnabledState(enabled);
39
+ localStorage.setItem('lottie-animations-enabled', JSON.stringify(enabled));
40
+
41
+ window.dispatchEvent(new CustomEvent('lottie-settings-changed', {
42
+ detail: { enabled }
43
+ }));
44
+ };
45
+
46
+ const setOtherLottieAnimationsEnabled = (enabled: boolean) => {
47
+ setOtherLottieAnimationsEnabledState(enabled);
48
+ localStorage.setItem('other-lottie-animations-enabled', JSON.stringify(enabled));
49
+
50
+ window.dispatchEvent(new CustomEvent('other-lottie-settings-changed', {
51
+ detail: { enabled }
52
+ }));
53
+ };
54
+
55
+ const toggleLottieAnimations = () => {
56
+ setLottieAnimationsEnabled(!lottieAnimationsEnabled);
57
+ };
58
+
59
+ const toggleOtherLottieAnimations = () => {
60
+ setOtherLottieAnimationsEnabled(!otherLottieAnimationsEnabled);
61
+ };
62
+
63
+ return (
64
+ <AnimationSettingsContext.Provider
65
+ value={{
66
+ lottieAnimationsEnabled,
67
+ otherLottieAnimationsEnabled,
68
+ setLottieAnimationsEnabled,
69
+ setOtherLottieAnimationsEnabled,
70
+ toggleLottieAnimations,
71
+ toggleOtherLottieAnimations,
72
+ }}
73
+ >
74
+ {children}
75
+ </AnimationSettingsContext.Provider>
76
+ );
77
+ }
78
+
79
+ export function useAnimationSettings() {
80
+ const context = useContext(AnimationSettingsContext);
81
+ if (context === undefined) {
82
+ throw new Error('useAnimationSettings must be used within an AnimationSettingsProvider');
83
+ }
84
+ return context;
85
+ }
@@ -0,0 +1,80 @@
1
+ 'use client';
2
+ import { createContext, useContext, useState, useEffect } from 'react';
3
+ import type { ReactNode } from 'react';
4
+
5
+ export interface User {
6
+ id: string;
7
+ name: string;
8
+ email: string;
9
+ [key: string]: unknown;
10
+ }
11
+
12
+ interface AuthContextType {
13
+ user: User | null;
14
+ isAuthenticated: boolean;
15
+ login: (email: string, password: string) => Promise<{ success: boolean; error?: string }>;
16
+ logout: () => Promise<void>;
17
+ initialLoading: boolean;
18
+ }
19
+
20
+ const AuthContext = createContext<AuthContextType | undefined>(undefined);
21
+
22
+ export function useAuth() {
23
+ const context = useContext(AuthContext);
24
+ if (context === undefined) {
25
+ throw new Error('useAuth must be used within an AuthProvider');
26
+ }
27
+ return context;
28
+ }
29
+
30
+ interface AuthProviderProps {
31
+ children: ReactNode;
32
+ }
33
+
34
+ export function AuthProvider({ children }: AuthProviderProps) {
35
+ const [user, setUser] = useState<User | null>(null);
36
+ const [loading, setLoading] = useState(true);
37
+
38
+ useEffect(() => {
39
+ try {
40
+ const savedUser = localStorage.getItem('auth_user');
41
+ if (savedUser) {
42
+ setUser(JSON.parse(savedUser));
43
+ }
44
+ } catch {
45
+ localStorage.removeItem('auth_user');
46
+ }
47
+ setLoading(false);
48
+ }, []);
49
+
50
+ const login = async (email: string, password: string): Promise<{ success: boolean; error?: string }> => {
51
+ try {
52
+ // Stub: accept any credentials in Storybook
53
+ const mockUser: User = { id: '1', name: 'Storybook User', email };
54
+ setUser(mockUser);
55
+ localStorage.setItem('auth_user', JSON.stringify(mockUser));
56
+ return { success: true };
57
+ } catch {
58
+ return { success: false, error: 'Erro de conexão. Tente novamente.' };
59
+ }
60
+ };
61
+
62
+ const logout = async () => {
63
+ setUser(null);
64
+ localStorage.removeItem('auth_user');
65
+ };
66
+
67
+ const value: AuthContextType = {
68
+ user,
69
+ isAuthenticated: !!user,
70
+ login,
71
+ logout,
72
+ initialLoading: loading
73
+ };
74
+
75
+ return (
76
+ <AuthContext.Provider value={value}>
77
+ {children}
78
+ </AuthContext.Provider>
79
+ );
80
+ }
@@ -0,0 +1,70 @@
1
+ import React, { createContext, useContext, useEffect, useState } from 'react';
2
+
3
+ type Theme = 'light' | 'dark';
4
+
5
+ interface ThemeContextType {
6
+ theme: Theme;
7
+ toggleTheme: () => void;
8
+ mounted: boolean;
9
+ }
10
+
11
+ const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
12
+
13
+ export function ThemeProvider({ children }: { children: React.ReactNode }) {
14
+ const [mounted, setMounted] = useState(false);
15
+ const [theme, setTheme] = useState<Theme>('light');
16
+
17
+ useEffect(() => {
18
+ setMounted(true);
19
+
20
+ const savedTheme = localStorage.getItem('theme') as Theme;
21
+ let initialTheme: Theme = 'light';
22
+
23
+ if (savedTheme && (savedTheme === 'light' || savedTheme === 'dark')) {
24
+ initialTheme = savedTheme;
25
+ } else if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
26
+ initialTheme = 'dark';
27
+ }
28
+
29
+ setTheme(initialTheme);
30
+ }, []);
31
+
32
+ useEffect(() => {
33
+ if (!mounted) return;
34
+
35
+ const root = window.document.documentElement;
36
+ root.classList.remove('light', 'dark');
37
+
38
+ root.classList.add(theme);
39
+
40
+ localStorage.setItem('theme', theme);
41
+
42
+ root.style.display = 'none';
43
+ root.offsetHeight;
44
+ root.style.display = '';
45
+ }, [theme, mounted]);
46
+
47
+ const toggleTheme = () => {
48
+ document.body.classList.add('theme-transition-disabled');
49
+
50
+ setTheme(prevTheme => prevTheme === 'light' ? 'dark' : 'light');
51
+
52
+ setTimeout(() => {
53
+ document.body.classList.remove('theme-transition-disabled');
54
+ }, 50);
55
+ };
56
+
57
+ return (
58
+ <ThemeContext.Provider value={{ theme, toggleTheme, mounted }}>
59
+ {children}
60
+ </ThemeContext.Provider>
61
+ );
62
+ }
63
+
64
+ export function useTheme() {
65
+ const context = useContext(ThemeContext);
66
+ if (context === undefined) {
67
+ throw new Error('useTheme must be used within a ThemeProvider');
68
+ }
69
+ return context;
70
+ }
@@ -0,0 +1,2 @@
1
+ // Re-export from the real context so components get the full shape
2
+ export { useAnimationSettings } from '../contexts/AnimationSettingsContext';
@@ -0,0 +1,4 @@
1
+ export const usePermissions = () => ({
2
+ hasPermission: (_permission: string) => true,
3
+ permissions: [] as string[],
4
+ });
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Utilitários para gerenciamento de filtros persistentes
3
+ * Permite salvar e carregar filtros entre sessões para diferentes módulos
4
+ */
5
+
6
+ export interface FilterConfig {
7
+ page?: number;
8
+ itemsPerPage?: number;
9
+ search?: string | null;
10
+ sortOrder?: 'asc' | 'desc';
11
+ sortField?: string;
12
+ timestamp?: number;
13
+
14
+ situacao?: string | null;
15
+ startDate?: string | null;
16
+ endDate?: string | null;
17
+ searchTerm?: string;
18
+ selectedLines?: string[];
19
+ selectedLinhas?: number[];
20
+ possuiHorarios?: string;
21
+ selectedSituacao?: "Ativo" | "Inativo" | undefined;
22
+ filtroTurno?: string;
23
+ filtroAgrupamento?: string;
24
+ filtroLinhaProducao?: string;
25
+ filtroStatus?: string;
26
+ filtroOperador?: string;
27
+
28
+ [key: string]: any;
29
+ }
30
+
31
+ export class PersistentFilters {
32
+ private static readonly SETTINGS_KEY = 'persistent-filters-enabled';
33
+
34
+ /**
35
+ * Verifica se os filtros persistentes estão habilitados
36
+ */
37
+ static isEnabled(): boolean {
38
+ const setting = localStorage.getItem(this.SETTINGS_KEY);
39
+ return setting ? JSON.parse(setting) : false;
40
+ }
41
+
42
+ /**
43
+ * Salva os filtros de um módulo específico
44
+ */
45
+ static saveFilters(module: string, filters: FilterConfig): void {
46
+ if (!this.isEnabled()) return;
47
+
48
+ const filtersWithTimestamp = {
49
+ ...filters,
50
+ timestamp: Date.now()
51
+ };
52
+
53
+ const key = `${module}-filters`;
54
+ localStorage.setItem(key, JSON.stringify(filtersWithTimestamp));
55
+ }
56
+
57
+ /**
58
+ * Carrega os filtros salvos de um módulo específico
59
+ */
60
+ static loadFilters(module: string): FilterConfig | null {
61
+ if (!this.isEnabled()) return null;
62
+
63
+ const key = `${module}-filters`;
64
+ const saved = localStorage.getItem(key);
65
+
66
+ if (!saved) return null;
67
+
68
+ try {
69
+ return JSON.parse(saved);
70
+ } catch (error) {
71
+ console.error(`Erro ao carregar filtros do módulo ${module}:`, error);
72
+ return null;
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Remove os filtros salvos de um módulo específico
78
+ */
79
+ static clearFilters(module: string): void {
80
+ const key = `${module}-filters`;
81
+ localStorage.removeItem(key);
82
+ }
83
+
84
+ /**
85
+ * Remove todos os filtros salvos de todos os módulos
86
+ */
87
+ static clearAllFilters(): void {
88
+ const keys = Object.keys(localStorage).filter(key => key.endsWith('-filters'));
89
+ keys.forEach(key => localStorage.removeItem(key));
90
+ }
91
+
92
+ /**
93
+ * Lista todos os módulos que possuem filtros salvos
94
+ */
95
+ static getModulesWithSavedFilters(): string[] {
96
+ const keys = Object.keys(localStorage).filter(key => key.endsWith('-filters'));
97
+ return keys.map(key => key.replace('-filters', ''));
98
+ }
99
+
100
+ /**
101
+ * Hook personalizado para usar filtros persistentes em componentes React
102
+ */
103
+ static useModuleFilters(module: string, defaultFilters: FilterConfig) {
104
+ return {
105
+ saveFilters: (filters: FilterConfig) => this.saveFilters(module, filters),
106
+ loadFilters: () => this.loadFilters(module) || defaultFilters,
107
+ clearFilters: () => this.clearFilters(module),
108
+ isEnabled: () => this.isEnabled()
109
+ };
110
+ }
111
+ }
112
+
113
+ /**
114
+ * Constantes para nomes de módulos
115
+ */
116
+ export const MODULES = {
117
+ // DASHBOARD: 'dashboard',
118
+ } as const;
119
+
120
+ export type ModuleName = typeof MODULES[keyof typeof MODULES];
package/lib/utils.ts ADDED
@@ -0,0 +1,2 @@
1
+ // Re-export from ui/utils for backward compatibility
2
+ export { cn } from '../components/ui/utils';
package/package.json CHANGED
@@ -1,17 +1,26 @@
1
1
  {
2
2
  "name": "@evolution-soft/ui",
3
3
  "private": false,
4
- "version": "1.0.0",
4
+ "version": "1.0.1",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
7
7
  "module": "./dist/index.js",
8
8
  "types": "./dist/index.d.ts",
9
9
  "files": [
10
- "dist"
10
+ "dist",
11
+ "cli",
12
+ "components",
13
+ "contexts",
14
+ "hooks",
15
+ "lib",
16
+ "stores"
11
17
  ],
12
18
  "sideEffects": [
13
19
  "**/*.css"
14
20
  ],
21
+ "bin": {
22
+ "evolution-ui": "./cli/index.mjs"
23
+ },
15
24
  "exports": {
16
25
  ".": {
17
26
  "types": "./dist/index.d.ts",
@@ -0,0 +1,30 @@
1
+ import { create } from 'zustand';
2
+ import { createJSONStorage, persist } from 'zustand/middleware';
3
+
4
+ export type ThemeProps = 'dark' | 'light'
5
+
6
+ interface UseThemeStoreProps {
7
+ darkMode: boolean,
8
+ setDarkMode: (e: boolean) => void;
9
+ color: "cyan" | "orange" | "violet" | "emerald"
10
+ setColor: (e: "cyan" | "orange" | "violet" | "emerald") => void;
11
+ }
12
+
13
+ const useStore = create<UseThemeStoreProps>()(persist(
14
+ (set) => ({
15
+ darkMode: false,
16
+ setDarkMode: (e) => set({
17
+ darkMode: e
18
+ }),
19
+ color: 'cyan',
20
+ setColor: (e) => set({
21
+ color: e
22
+ })
23
+ }),
24
+ {
25
+ name: 'theme-storage',
26
+ storage: createJSONStorage(() => localStorage),
27
+ }
28
+ ));
29
+
30
+ export const useThemeStore = useStore;
@@ -0,0 +1,32 @@
1
+ "use client";
2
+ import { useEffect, useState } from "react";
3
+
4
+ export function useThemeStore() {
5
+ const [theme, setTheme] = useState<'light' | 'dark'>(() => {
6
+ if (typeof window !== "undefined") {
7
+ return (localStorage.getItem("theme") as 'light' | 'dark') || "light";
8
+ }
9
+ return "light";
10
+ });
11
+
12
+ useEffect(() => {
13
+ if (typeof window !== "undefined") {
14
+ const root = document.documentElement;
15
+ root.classList.remove('light', 'dark');
16
+ root.classList.add(theme);
17
+ localStorage.setItem("theme", theme);
18
+ }
19
+ }, [theme]);
20
+
21
+ const toggleTheme = () => {
22
+ document.body.classList.add('theme-transition-disabled');
23
+
24
+ setTheme(prev => prev === "light" ? "dark" : "light");
25
+
26
+ setTimeout(() => {
27
+ document.body.classList.remove('theme-transition-disabled');
28
+ }, 50);
29
+ };
30
+
31
+ return { theme, toggleTheme };
32
+ }