mirror of
				https://github.com/CorentinTh/it-tools.git
				synced 2025-11-04 05:53:25 +00:00 
			
		
		
		
	chore(cd): added deploy on cloudflare pages
This commit is contained in:
		
							
								
								
									
										43
									
								
								.github/workflows/cd-app-prod.yaml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								.github/workflows/cd-app-prod.yaml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,43 @@
 | 
			
		||||
name: CD - Production
 | 
			
		||||
 | 
			
		||||
on:
 | 
			
		||||
  push:
 | 
			
		||||
    branches:
 | 
			
		||||
      - next
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  publish-app-prod:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    permissions:
 | 
			
		||||
      contents: read
 | 
			
		||||
      deployments: write
 | 
			
		||||
    name: Publish app to production
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
 | 
			
		||||
      - run: corepack enable
 | 
			
		||||
      - uses: actions/setup-node@v4
 | 
			
		||||
        with:
 | 
			
		||||
          node-version: 22
 | 
			
		||||
          corepack: true
 | 
			
		||||
          cache: 'pnpm'
 | 
			
		||||
 | 
			
		||||
      - name: Install dependencies
 | 
			
		||||
        run: pnpm i
 | 
			
		||||
 | 
			
		||||
      - name: Build the app
 | 
			
		||||
        run: pnpm -F @it-tools/app build
 | 
			
		||||
 | 
			
		||||
      - name: Publish to Cloudflare Pages
 | 
			
		||||
        uses: AdrianGonz97/refined-cf-pages-action@v1
 | 
			
		||||
        with:
 | 
			
		||||
          apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
 | 
			
		||||
          accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
 | 
			
		||||
          githubToken: ${{ secrets.GITHUB_TOKEN }}
 | 
			
		||||
          projectName: it-tools
 | 
			
		||||
          workingDirectory: packages/app
 | 
			
		||||
          directory: dist
 | 
			
		||||
          deploymentName: Production App
 | 
			
		||||
          branch: next
 | 
			
		||||
          wranglerVersion: '3'
 | 
			
		||||
 | 
			
		||||
  
 | 
			
		||||
							
								
								
									
										1215
									
								
								packages/app/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1215
									
								
								packages/app/package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -19,10 +19,11 @@
 | 
			
		||||
    "typecheck": "tsc --noEmit",
 | 
			
		||||
    "test": "pnpm run test:unit",
 | 
			
		||||
    "test:unit": "vitest run",
 | 
			
		||||
    "test:unit:watch": "vitest watch"
 | 
			
		||||
    "test:unit:watch": "vitest watch",
 | 
			
		||||
    "create:tool": "HYGEN_TMPLS=templates hygen tools new"
 | 
			
		||||
  },
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "@corentinth/chisels": "^1.0.4",
 | 
			
		||||
    "@corentinth/chisels": "^1.1.0",
 | 
			
		||||
    "@kobalte/core": "^0.13.6",
 | 
			
		||||
    "@solid-primitives/i18n": "^2.1.1",
 | 
			
		||||
    "@solid-primitives/storage": "^4.2.1",
 | 
			
		||||
@@ -30,20 +31,24 @@
 | 
			
		||||
    "@unocss/reset": "^0.62.4",
 | 
			
		||||
    "class-variance-authority": "^0.7.0",
 | 
			
		||||
    "clsx": "^2.1.1",
 | 
			
		||||
    "cmdk-solid": "^1.1.0",
 | 
			
		||||
    "lodash-es": "^4.17.21",
 | 
			
		||||
    "solid-js": "^1.9.1",
 | 
			
		||||
    "solid-sonner": "^0.2.8",
 | 
			
		||||
    "tailwind-merge": "^2.5.2"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@antfu/eslint-config": "^3.7.3",
 | 
			
		||||
    "@iconify-json/tabler": "^1.2.3",
 | 
			
		||||
    "@types/lodash-es": "^4.17.12",
 | 
			
		||||
    "@vitest/coverage-v8": "2.1.2",
 | 
			
		||||
    "eslint": "^9.11.1",
 | 
			
		||||
    "hygen": "^6.2.11",
 | 
			
		||||
    "typescript": "^5.6.2",
 | 
			
		||||
    "unocss": "^0.62.4",
 | 
			
		||||
    "unocss-preset-animations": "^1.1.0",
 | 
			
		||||
    "vite": "^5.4.8",
 | 
			
		||||
    "vite-plugin-solid": "^2.10.2",
 | 
			
		||||
    "vitest": "^2.1.1"
 | 
			
		||||
    "vitest": "^2.1.2"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								packages/app/public/robots.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								packages/app/public/robots.txt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,2 @@
 | 
			
		||||
User-agent: *
 | 
			
		||||
Disallow:
 | 
			
		||||
@@ -1,8 +1,7 @@
 | 
			
		||||
import type { LocaleKey } from './modules/i18n/i18n.types';
 | 
			
		||||
import { A, Navigate, type RouteDefinition, useParams } from '@solidjs/router';
 | 
			
		||||
import { map } from 'lodash-es';
 | 
			
		||||
import { localeKeys, locales } from './modules/i18n/i18n.constants';
 | 
			
		||||
import { getBrowserLocale, useI18n } from './modules/i18n/i18n.provider';
 | 
			
		||||
import { localeKeys } from './modules/i18n/i18n.constants';
 | 
			
		||||
import { useI18n } from './modules/i18n/i18n.provider';
 | 
			
		||||
import { HomePage } from './modules/pages/home.page';
 | 
			
		||||
import { ToolPage } from './modules/tools/pages/tool.page';
 | 
			
		||||
import { toolSlugs } from './modules/tools/tools.registry';
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,9 @@ import { ColorModeProvider, ColorModeScript, createLocalStorageManager } from '@
 | 
			
		||||
import { Router } from '@solidjs/router';
 | 
			
		||||
import { render, Suspense } from 'solid-js/web';
 | 
			
		||||
import { routes } from './client-routes';
 | 
			
		||||
import { CommandPaletteProvider } from './modules/command-palette/command-palette.provider';
 | 
			
		||||
import { RootI18nProvider } from './modules/i18n/i18n.provider';
 | 
			
		||||
import { Toaster } from './modules/ui/components/sonner';
 | 
			
		||||
import '@unocss/reset/tailwind.css';
 | 
			
		||||
import 'virtual:uno.css';
 | 
			
		||||
import './app.css';
 | 
			
		||||
@@ -26,7 +28,10 @@ render(
 | 
			
		||||
                initialColorMode={initialColorMode}
 | 
			
		||||
                storageManager={localStorageManager}
 | 
			
		||||
              >
 | 
			
		||||
                <div class="min-h-screen font-sans text-sm font-400">{props.children}</div>
 | 
			
		||||
                <CommandPaletteProvider>
 | 
			
		||||
                  <Toaster />
 | 
			
		||||
                  <div class="min-h-screen font-sans text-sm font-400">{props.children}</div>
 | 
			
		||||
                </CommandPaletteProvider>
 | 
			
		||||
              </ColorModeProvider>
 | 
			
		||||
            </RootI18nProvider>
 | 
			
		||||
          </Suspense>
 | 
			
		||||
 
 | 
			
		||||
@@ -16,8 +16,46 @@
 | 
			
		||||
    "support": "Support IT-Tools",
 | 
			
		||||
    "report-bug": "Report a bug"
 | 
			
		||||
  },
 | 
			
		||||
  "footer": {
 | 
			
		||||
    "resources": {
 | 
			
		||||
      "title": "Resources",
 | 
			
		||||
      "all-tools": "All the tools",
 | 
			
		||||
      "github": "GitHub repository",
 | 
			
		||||
      "support": "Support IT-Tools",
 | 
			
		||||
      "license": "License"
 | 
			
		||||
    },
 | 
			
		||||
    "support": {
 | 
			
		||||
      "title": "Support",
 | 
			
		||||
      "report-bug": "Report a bug",
 | 
			
		||||
      "request-feature": "Request a feature",
 | 
			
		||||
      "contribute": "Contribute to the project",
 | 
			
		||||
      "contact": "Contact me"
 | 
			
		||||
    },
 | 
			
		||||
    "friends": {
 | 
			
		||||
      "title": "Friends"
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  "commandPalette": {
 | 
			
		||||
    "input-placeholder": "Type to search for a tool or a command...",
 | 
			
		||||
    "go-home": "Go to home",
 | 
			
		||||
    "sections": {
 | 
			
		||||
      "tools": "Tools",
 | 
			
		||||
      "navigation": "Navigation",
 | 
			
		||||
      "language": "Language",
 | 
			
		||||
      "theme": "Theme"
 | 
			
		||||
    },
 | 
			
		||||
    "theme": {
 | 
			
		||||
      "switch-to-light": "Switch to light theme",
 | 
			
		||||
      "switch-to-dark": "Switch to dark theme",
 | 
			
		||||
      "switch-to-system": "Use to system theme"
 | 
			
		||||
    },
 | 
			
		||||
    "trigger": {
 | 
			
		||||
      "search": "Search for a tool"
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  "home": {
 | 
			
		||||
    "all-tools": "All the tools",
 | 
			
		||||
    "search-tools": "Search for a tool",
 | 
			
		||||
    "open-source": "Open Source",
 | 
			
		||||
    "free": "Free",
 | 
			
		||||
    "self-hostable": "Self-hostable"
 | 
			
		||||
@@ -26,6 +64,10 @@
 | 
			
		||||
    "token-generator": {
 | 
			
		||||
      "name": "Token Generator",
 | 
			
		||||
      "description": "Generate random string with the characters you want, uppercase, lowercase letters, numbers and/or symbols."
 | 
			
		||||
    },
 | 
			
		||||
    "random-port-generator": {
 | 
			
		||||
      "name": "Random Port Generator",
 | 
			
		||||
      "description": "Generate a random port number outside of the reserved ports range (0-1023)."
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -2,5 +2,58 @@
 | 
			
		||||
  "app": {
 | 
			
		||||
    "title": "IT-Tools",
 | 
			
		||||
    "description": "La collection open-source d'outils en ligne pour aider les devs dans leur vie quotidienne."
 | 
			
		||||
  },
 | 
			
		||||
  "navbar": {
 | 
			
		||||
    "theme": {
 | 
			
		||||
      "theme": "Thème",
 | 
			
		||||
      "light-mode": "Mode clair",
 | 
			
		||||
      "dark-mode": "Mode sombre",
 | 
			
		||||
      "system-mode": "Système"
 | 
			
		||||
    },
 | 
			
		||||
    "language": "Langue",
 | 
			
		||||
    "contribute-to-i18n": "Contribuer à l'i18n",
 | 
			
		||||
    "github": "GitHub",
 | 
			
		||||
    "support": "Soutenir IT-Tools",
 | 
			
		||||
    "report-bug": "Signaler un bug"
 | 
			
		||||
  },
 | 
			
		||||
  "footer": {
 | 
			
		||||
    "resources": {
 | 
			
		||||
      "title": "Ressources",
 | 
			
		||||
      "all-tools": "Tous les outils",
 | 
			
		||||
      "github": "Dépôt GitHub",
 | 
			
		||||
      "support": "Soutenir IT-Tools",
 | 
			
		||||
      "license": "Licence"
 | 
			
		||||
    },
 | 
			
		||||
    "support": {
 | 
			
		||||
      "title": "Support",
 | 
			
		||||
      "report-bug": "Signaler un bug",
 | 
			
		||||
      "request-feature": "Demander une fonctionnalité",
 | 
			
		||||
      "contribute": "Contribuer au projet",
 | 
			
		||||
      "contact": "Me contacter"
 | 
			
		||||
    },
 | 
			
		||||
    "friends": {
 | 
			
		||||
      "title": "Ami·e·s"
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  "commandPalette": {
 | 
			
		||||
    "input-placeholder": "Tapez pour rechercher un outil...",
 | 
			
		||||
    "go-home": "Aller à l'accueil",
 | 
			
		||||
    "sections": {
 | 
			
		||||
      "tools": "Outils",
 | 
			
		||||
      "navigation": "Navigation",
 | 
			
		||||
      "theme": "Thème"
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  "home": {
 | 
			
		||||
    "all-tools": "Tous les outils",
 | 
			
		||||
    "open-source": "Open Source",
 | 
			
		||||
    "free": "Gratuit",
 | 
			
		||||
    "self-hostable": "Self-hostable"
 | 
			
		||||
  },
 | 
			
		||||
  "tools": {
 | 
			
		||||
    "token-generator": {
 | 
			
		||||
      "name": "Générateur de token",
 | 
			
		||||
      "description": "Générer des string aléatoires, contrôlez les caractères que vous voulez, lettres majuscules, minuscules, chiffres et/ou symboles."
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,144 @@
 | 
			
		||||
import type { Accessor, ParentComponent } from 'solid-js';
 | 
			
		||||
import { useNavigate } from '@solidjs/router';
 | 
			
		||||
import { createContext, createMemo, createSignal, For, onCleanup, onMount, useContext } from 'solid-js';
 | 
			
		||||
import { locales } from '../i18n/i18n.constants';
 | 
			
		||||
import { useI18n } from '../i18n/i18n.provider';
 | 
			
		||||
import { useToolsStore } from '../tools/tools.store';
 | 
			
		||||
import { CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from '../ui/components/command';
 | 
			
		||||
import { useThemeStore } from '../ui/themes/theme.store';
 | 
			
		||||
import { cn } from '../ui/utils/cn';
 | 
			
		||||
 | 
			
		||||
const CommandPaletteContext = createContext<{
 | 
			
		||||
  getIsCommandPaletteOpen: Accessor<boolean>;
 | 
			
		||||
  openCommandPalette: () => void;
 | 
			
		||||
  closeCommandPalette: () => void;
 | 
			
		||||
}>();
 | 
			
		||||
 | 
			
		||||
export function useCommandPalette() {
 | 
			
		||||
  const context = useContext(CommandPaletteContext);
 | 
			
		||||
 | 
			
		||||
  if (!context) {
 | 
			
		||||
    throw new Error('CommandPalette context not found');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return context;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const CommandPaletteProvider: ParentComponent = (props) => {
 | 
			
		||||
  const [getIsCommandPaletteOpen, setIsCommandPaletteOpen] = createSignal(false);
 | 
			
		||||
 | 
			
		||||
  const handleKeyDown = (e: KeyboardEvent) => {
 | 
			
		||||
    if (e.key === 'k' && (e.metaKey || e.ctrlKey)) {
 | 
			
		||||
      e.preventDefault();
 | 
			
		||||
      setIsCommandPaletteOpen(true);
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  onMount(() => {
 | 
			
		||||
    document.addEventListener('keydown', handleKeyDown);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  onCleanup(() => {
 | 
			
		||||
    document.removeEventListener('keydown', handleKeyDown);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  const { getTools } = useToolsStore();
 | 
			
		||||
  const navigate = useNavigate();
 | 
			
		||||
  const { t, createLocalizedUrl, changeLocale } = useI18n();
 | 
			
		||||
  const { setColorMode } = useThemeStore();
 | 
			
		||||
 | 
			
		||||
  const getCommandData = createMemo(() => [
 | 
			
		||||
    {
 | 
			
		||||
      label: t('commandPalette.sections.tools'),
 | 
			
		||||
      options: [
 | 
			
		||||
        ...getTools().map(tool => ({
 | 
			
		||||
          label: tool.name,
 | 
			
		||||
          icon: tool.icon,
 | 
			
		||||
          action: () => navigate(createLocalizedUrl({ path: tool.slug })),
 | 
			
		||||
        })),
 | 
			
		||||
      ],
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      label: t('commandPalette.sections.navigation'),
 | 
			
		||||
      options: [
 | 
			
		||||
        {
 | 
			
		||||
          label: t('commandPalette.go-home'),
 | 
			
		||||
          icon: 'i-tabler-home',
 | 
			
		||||
          action: () => navigate(createLocalizedUrl({ path: '' })),
 | 
			
		||||
        },
 | 
			
		||||
      ],
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      label: t('commandPalette.sections.language'),
 | 
			
		||||
      options: [
 | 
			
		||||
        ...locales.map(locale => ({
 | 
			
		||||
          label: locale.switchToLabel,
 | 
			
		||||
          icon: 'i-custom-language',
 | 
			
		||||
          action: () => changeLocale(locale.key),
 | 
			
		||||
          keywords: [locale.name, locale.key],
 | 
			
		||||
        })),
 | 
			
		||||
      ],
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      label: t('commandPalette.sections.theme'),
 | 
			
		||||
      options: [
 | 
			
		||||
        {
 | 
			
		||||
          label: t('commandPalette.theme.switch-to-light'),
 | 
			
		||||
          icon: 'i-tabler-sun',
 | 
			
		||||
          action: () => setColorMode({ mode: 'light' }),
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          label: t('commandPalette.theme.switch-to-dark'),
 | 
			
		||||
          icon: 'i-tabler-moon',
 | 
			
		||||
          action: () => setColorMode({ mode: 'dark' }),
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          label: t('commandPalette.theme.switch-to-system'),
 | 
			
		||||
          icon: 'i-tabler-device-laptop',
 | 
			
		||||
          action: () => setColorMode({ mode: 'system' }),
 | 
			
		||||
        },
 | 
			
		||||
      ],
 | 
			
		||||
    },
 | 
			
		||||
  ]);
 | 
			
		||||
 | 
			
		||||
  const onCommandSelect = ({ action }: { action: () => void }) => {
 | 
			
		||||
    action();
 | 
			
		||||
    setIsCommandPaletteOpen(false);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <CommandPaletteContext.Provider value={{
 | 
			
		||||
      getIsCommandPaletteOpen,
 | 
			
		||||
      openCommandPalette: () => setIsCommandPaletteOpen(true),
 | 
			
		||||
      closeCommandPalette: () => setIsCommandPaletteOpen(false),
 | 
			
		||||
    }}
 | 
			
		||||
    >
 | 
			
		||||
      <CommandDialog
 | 
			
		||||
        class="rounded-lg border shadow-md"
 | 
			
		||||
        open={getIsCommandPaletteOpen()}
 | 
			
		||||
        onOpenChange={setIsCommandPaletteOpen}
 | 
			
		||||
      >
 | 
			
		||||
        <CommandInput placeholder={t('commandPalette.input-placeholder')} />
 | 
			
		||||
        <CommandList>
 | 
			
		||||
          <CommandEmpty>No results found.</CommandEmpty>
 | 
			
		||||
          <For each={getCommandData()}>
 | 
			
		||||
            {section => (
 | 
			
		||||
              <CommandGroup heading={section.label}>
 | 
			
		||||
                <For each={section.options}>
 | 
			
		||||
                  {item => (
 | 
			
		||||
                    <CommandItem onSelect={() => onCommandSelect(item)}>
 | 
			
		||||
                      <span class={cn('mr-2 ml-1 size-4 text-muted-foreground', item.icon)} />
 | 
			
		||||
                      <span>{item.label}</span>
 | 
			
		||||
                    </CommandItem>
 | 
			
		||||
                  )}
 | 
			
		||||
                </For>
 | 
			
		||||
              </CommandGroup>
 | 
			
		||||
            )}
 | 
			
		||||
          </For>
 | 
			
		||||
        </CommandList>
 | 
			
		||||
      </CommandDialog>
 | 
			
		||||
 | 
			
		||||
      {props.children}
 | 
			
		||||
    </CommandPaletteContext.Provider>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
@@ -5,11 +5,13 @@ export const locales = [
 | 
			
		||||
    key: 'en',
 | 
			
		||||
    file: 'en',
 | 
			
		||||
    name: 'English',
 | 
			
		||||
    switchToLabel: 'Change language to English',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    key: 'fr',
 | 
			
		||||
    file: 'fr',
 | 
			
		||||
    name: 'Français',
 | 
			
		||||
    switchToLabel: 'Changer la langue en Français',
 | 
			
		||||
  },
 | 
			
		||||
] as const;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,9 @@
 | 
			
		||||
import type { ParentComponent } from 'solid-js';
 | 
			
		||||
import type { LocaleKey } from './i18n.types';
 | 
			
		||||
import { joinUrlPaths } from '@corentinth/chisels';
 | 
			
		||||
import * as i18n from '@solid-primitives/i18n';
 | 
			
		||||
import { makePersisted } from '@solid-primitives/storage';
 | 
			
		||||
import { useNavigate } from '@solidjs/router';
 | 
			
		||||
import { merge } from 'lodash-es';
 | 
			
		||||
import { createContext, createResource, createSignal, Show, useContext } from 'solid-js';
 | 
			
		||||
import defaultDict from '../../locales/en.json';
 | 
			
		||||
@@ -23,6 +25,7 @@ const RootI18nContext = createContext<{
 | 
			
		||||
 | 
			
		||||
function useI18n() {
 | 
			
		||||
  const context = useContext(RootI18nContext);
 | 
			
		||||
  const navigate = useNavigate();
 | 
			
		||||
 | 
			
		||||
  if (!context) {
 | 
			
		||||
    throw new Error('I18n context not found');
 | 
			
		||||
@@ -35,6 +38,18 @@ function useI18n() {
 | 
			
		||||
    getLocale,
 | 
			
		||||
    setLocale,
 | 
			
		||||
    locales,
 | 
			
		||||
    createLocalizedUrl: ({ path }: { path: string }) => {
 | 
			
		||||
      const newPath = joinUrlPaths(getLocale(), path);
 | 
			
		||||
 | 
			
		||||
      return `/${newPath}`;
 | 
			
		||||
    },
 | 
			
		||||
    changeLocale: (locale: LocaleKey) => {
 | 
			
		||||
      setLocale(locale);
 | 
			
		||||
 | 
			
		||||
      const pathWithoutLocale = location.pathname.split('/').slice(2).join('/');
 | 
			
		||||
      const newPath = joinUrlPaths(locale, pathWithoutLocale);
 | 
			
		||||
      navigate(`/${newPath}`);
 | 
			
		||||
    },
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,3 @@
 | 
			
		||||
import type { locales } from './i18n.constants';
 | 
			
		||||
 | 
			
		||||
export type LocaleKey = typeof locales[number]['key'];
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,15 +1,17 @@
 | 
			
		||||
import type { Component } from 'solid-js';
 | 
			
		||||
import { A } from '@solidjs/router';
 | 
			
		||||
import { useCommandPalette } from '../command-palette/command-palette.provider';
 | 
			
		||||
import { useI18n } from '../i18n/i18n.provider';
 | 
			
		||||
import { useToolsStore } from '../tools/tools.store';
 | 
			
		||||
import { Badge } from '../ui/components/badge';
 | 
			
		||||
import { Button } from '../ui/components/button';
 | 
			
		||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '../ui/components/card';
 | 
			
		||||
import { Card, CardDescription, CardHeader, CardTitle } from '../ui/components/card';
 | 
			
		||||
import { cn } from '../ui/utils/cn';
 | 
			
		||||
 | 
			
		||||
export const HomePage: Component = () => {
 | 
			
		||||
  const { t } = useI18n();
 | 
			
		||||
  const { tools } = useToolsStore();
 | 
			
		||||
  const { getTools } = useToolsStore();
 | 
			
		||||
  const { openCommandPalette } = useCommandPalette();
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div>
 | 
			
		||||
@@ -39,11 +41,16 @@ export const HomePage: Component = () => {
 | 
			
		||||
              {t('app.description')}
 | 
			
		||||
            </p>
 | 
			
		||||
 | 
			
		||||
            <div>
 | 
			
		||||
            <div class="flex items-center gap-4">
 | 
			
		||||
              <Button variant="default" as={A} href="tools">
 | 
			
		||||
                {t('home.all-tools')}
 | 
			
		||||
                <div class="i-tabler-arrow-right ml-2 text-base"></div>
 | 
			
		||||
              </Button>
 | 
			
		||||
 | 
			
		||||
              <Button variant="outline" onClick={openCommandPalette}>
 | 
			
		||||
                <div class="i-tabler-search mr-2 text-base" />
 | 
			
		||||
                {t('home.search-tools')}
 | 
			
		||||
              </Button>
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
 | 
			
		||||
@@ -57,9 +64,9 @@ export const HomePage: Component = () => {
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <div class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3 max-w-1200px mx-auto p-6">
 | 
			
		||||
        {tools.map(tool => (
 | 
			
		||||
          <A href={tool.slug}>
 | 
			
		||||
            <Card class="hover:(shadow-md transform scale-101) transition-transform">
 | 
			
		||||
        {getTools().map(tool => (
 | 
			
		||||
          <A href={tool.slug} class="h-full">
 | 
			
		||||
            <Card class="hover:(shadow-md transform scale-101) transition-transform h-full">
 | 
			
		||||
              <CardHeader>
 | 
			
		||||
                <div class={cn(tool.icon, 'size-12 text-muted-foreground/60')} />
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										37
									
								
								packages/app/src/modules/shared/copy/copy-button.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								packages/app/src/modules/shared/copy/copy-button.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,37 @@
 | 
			
		||||
import type { Accessor, Component, ComponentProps } from 'solid-js';
 | 
			
		||||
import { Button } from '@/modules/ui/components/button';
 | 
			
		||||
import { omit } from 'lodash-es';
 | 
			
		||||
import { Show, splitProps } from 'solid-js';
 | 
			
		||||
import { useCopy } from './copy';
 | 
			
		||||
 | 
			
		||||
export const CopyButton: Component<{ textToCopy: Accessor<string | number>; toastMessage?: string } & ComponentProps<typeof Button>> = (props) => {
 | 
			
		||||
  const [localProps, buttonProps] = splitProps(props, ['textToCopy', 'toastMessage']);
 | 
			
		||||
  const { copy, getIsJustCopied } = useCopy(localProps.textToCopy, { toastMessage: localProps.toastMessage });
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <Button onClick={copy} {...omit(buttonProps, ['textToCopy', 'toastMessage'])}>
 | 
			
		||||
      <Show
 | 
			
		||||
        when={buttonProps.children}
 | 
			
		||||
        fallback={(
 | 
			
		||||
 | 
			
		||||
          getIsJustCopied()
 | 
			
		||||
            ? (
 | 
			
		||||
                <>
 | 
			
		||||
                  <div class="i-tabler-check mr-2 text-base" />
 | 
			
		||||
                  Copied!
 | 
			
		||||
                </>
 | 
			
		||||
              )
 | 
			
		||||
            : (
 | 
			
		||||
                <>
 | 
			
		||||
                  <div class="i-tabler-copy mr-2 text-base" />
 | 
			
		||||
                  Copy to clipboard
 | 
			
		||||
                </>
 | 
			
		||||
              )
 | 
			
		||||
 | 
			
		||||
        )}
 | 
			
		||||
      >
 | 
			
		||||
        {buttonProps.children}
 | 
			
		||||
      </Show>
 | 
			
		||||
    </Button>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										23
									
								
								packages/app/src/modules/shared/copy/copy.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								packages/app/src/modules/shared/copy/copy.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,23 @@
 | 
			
		||||
import type { Accessor } from 'solid-js';
 | 
			
		||||
import { createSignal } from 'solid-js';
 | 
			
		||||
import { toast } from '../../ui/components/sonner';
 | 
			
		||||
 | 
			
		||||
export { useCopy, writeTextToClipboard };
 | 
			
		||||
 | 
			
		||||
function writeTextToClipboard({ text }: { text: string }) {
 | 
			
		||||
  return navigator.clipboard.writeText(text);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function useCopy(getText: Accessor<string | number>, { toastMessage = 'Copied to clipboard' }: { toastMessage?: string } = {}) {
 | 
			
		||||
  const [getIsJustCopied, setIsJustCopied] = createSignal(false);
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    getIsJustCopied,
 | 
			
		||||
    copy: () => {
 | 
			
		||||
      writeTextToClipboard({ text: String(getText()) });
 | 
			
		||||
      setIsJustCopied(true);
 | 
			
		||||
      setTimeout(() => setIsJustCopied(false), 2000);
 | 
			
		||||
      toast(toastMessage);
 | 
			
		||||
    },
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										33
									
								
								packages/app/src/modules/shared/signals.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								packages/app/src/modules/shared/signals.test.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,33 @@
 | 
			
		||||
import { describe, expect, test } from 'vitest';
 | 
			
		||||
import { createRefreshableSignal } from './signals';
 | 
			
		||||
 | 
			
		||||
describe('signals', () => {
 | 
			
		||||
  describe('createRefreshableSignal', () => {
 | 
			
		||||
    test('the state initially has the value returned by the getter', () => {
 | 
			
		||||
      const [getState] = createRefreshableSignal(() => 42);
 | 
			
		||||
      expect(getState()).to.eql(42);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    test('calling the refresh function updates the state', () => {
 | 
			
		||||
      let value = 0;
 | 
			
		||||
      const [getState, refresh] = createRefreshableSignal(() => value++);
 | 
			
		||||
 | 
			
		||||
      expect(getState()).to.eql(0);
 | 
			
		||||
 | 
			
		||||
      refresh();
 | 
			
		||||
 | 
			
		||||
      expect(getState()).to.eql(1);
 | 
			
		||||
      expect(getState()).to.eql(1);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    test('the state can be muted using the setState function', () => {
 | 
			
		||||
      const [getState, , { setState }] = createRefreshableSignal(() => 0);
 | 
			
		||||
 | 
			
		||||
      expect(getState()).to.eql(0);
 | 
			
		||||
 | 
			
		||||
      setState(42);
 | 
			
		||||
 | 
			
		||||
      expect(getState()).to.eql(42);
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										13
									
								
								packages/app/src/modules/shared/signals.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								packages/app/src/modules/shared/signals.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
			
		||||
import { createSignal } from 'solid-js';
 | 
			
		||||
 | 
			
		||||
export { createRefreshableSignal };
 | 
			
		||||
 | 
			
		||||
function createRefreshableSignal<T>(getValue: () => T) {
 | 
			
		||||
  const [getState, setState] = createSignal<T>(getValue());
 | 
			
		||||
 | 
			
		||||
  return [
 | 
			
		||||
    getState,
 | 
			
		||||
    () => setState(() => getValue()),
 | 
			
		||||
    { setState },
 | 
			
		||||
  ] as const;
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "Random Port Generator",
 | 
			
		||||
  "description": "Generate a random port number outside of the reserved ports range (0-1023).",
 | 
			
		||||
  "refresh": "Refresh port",
 | 
			
		||||
  "copy-toast": "Port copied to clipboard"
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,31 @@
 | 
			
		||||
import type { Component } from 'solid-js';
 | 
			
		||||
import { CopyButton } from '@/modules/shared/copy/copy-button';
 | 
			
		||||
import { createRefreshableSignal } from '@/modules/shared/signals';
 | 
			
		||||
import { Button } from '@/modules/ui/components/button';
 | 
			
		||||
import { useCurrentTool } from '../../tools.provider';
 | 
			
		||||
import defaultDictionary from './locales/en.json';
 | 
			
		||||
import { generateRandomPort } from './random-port-generator.services';
 | 
			
		||||
 | 
			
		||||
const RandomPortGenerator: Component = () => {
 | 
			
		||||
  const [getPort, refreshPort] = createRefreshableSignal(generateRandomPort);
 | 
			
		||||
  const { t } = useCurrentTool({ defaultDictionary });
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div class="mx-auto max-w-1200px p-6">
 | 
			
		||||
      <div>
 | 
			
		||||
        {getPort()}
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <div class="flex gap-4 mt-4">
 | 
			
		||||
        <Button onClick={refreshPort} variant="outline">
 | 
			
		||||
          <div class="i-tabler-refresh mr-2 text-base text-muted-foreground" />
 | 
			
		||||
          {t('refresh')}
 | 
			
		||||
        </Button>
 | 
			
		||||
 | 
			
		||||
        <CopyButton textToCopy={getPort} toastMessage={t('copy-toast')} />
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default RandomPortGenerator;
 | 
			
		||||
@@ -0,0 +1,5 @@
 | 
			
		||||
import { random } from 'lodash-es';
 | 
			
		||||
 | 
			
		||||
export function generateRandomPort() {
 | 
			
		||||
  return random(1024, 65535);
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,9 @@
 | 
			
		||||
import { defineTool } from '../../tools.models';
 | 
			
		||||
 | 
			
		||||
export const randomPortGeneratorTool = defineTool({
 | 
			
		||||
  slug: 'random-port-generator',
 | 
			
		||||
  entryFile: () => import('./random-port-generator.page'),
 | 
			
		||||
  icon: 'i-tabler-server',
 | 
			
		||||
  createdAt: new Date('2024-10-03'),
 | 
			
		||||
  dirName: 'random-port-generator',
 | 
			
		||||
});
 | 
			
		||||
@@ -1,4 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "Token Generator",
 | 
			
		||||
  "description": "Generate random string with the characters you want, uppercase, lowercase letters, numbers and/or symbols.",
 | 
			
		||||
  "uppercase": "Uppercase letters (A-Z)",
 | 
			
		||||
  "lowercase": "Lowercase letters (a-z)",
 | 
			
		||||
  "numbers": "Numbers (0-9)",
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "Générateur de token",
 | 
			
		||||
  "description": "Génère une chaîne de caractères aléatoire, contrôlez les caractères que vous voulez, lettres majuscules, minuscules, chiffres et/ou symboles.",
 | 
			
		||||
  "uppercase": "Lettres majuscules (A-Z)",
 | 
			
		||||
  "lowercase": "Lettres minuscules (a-z)",
 | 
			
		||||
  "numbers": "Chiffres (0-9)",
 | 
			
		||||
 
 | 
			
		||||
@@ -5,5 +5,5 @@ export const tokenGeneratorTool = defineTool({
 | 
			
		||||
  entryFile: () => import('./token-generator.page'),
 | 
			
		||||
  icon: 'i-tabler-key',
 | 
			
		||||
  createdAt: new Date('2024-02-13'),
 | 
			
		||||
  currentDirUrl: import.meta.url,
 | 
			
		||||
  dirName: 'token-generator',
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -1,12 +1,7 @@
 | 
			
		||||
import type { LocaleKey } from '@/modules/i18n/i18n.types';
 | 
			
		||||
import type { Flatten } from '@solid-primitives/i18n';
 | 
			
		||||
import type { ToolI18nFactory } from '../tools.types';
 | 
			
		||||
import { useI18n } from '@/modules/i18n/i18n.provider';
 | 
			
		||||
import { safely } from '@corentinth/chisels';
 | 
			
		||||
import { flatten, translator } from '@solid-primitives/i18n';
 | 
			
		||||
import { useParams } from '@solidjs/router';
 | 
			
		||||
import { merge } from 'lodash-es';
 | 
			
		||||
import { type Component, createContext, createResource, lazy, Show } from 'solid-js';
 | 
			
		||||
import { type Component, createResource, lazy, Show } from 'solid-js';
 | 
			
		||||
import { CurrentToolProvider } from '../tools.provider';
 | 
			
		||||
import { getToolDefinitionBySlug } from '../tools.registry';
 | 
			
		||||
 | 
			
		||||
@@ -18,9 +13,13 @@ export const ToolPage: Component = () => {
 | 
			
		||||
  const ToolComponent = lazy(toolDefinition.entryFile);
 | 
			
		||||
 | 
			
		||||
  const [toolDict] = createResource(getLocale, async (locale) => {
 | 
			
		||||
    const [dict = { default: {} }] = await safely(import(`../definitions/${toolDefinition.dirName}/locales/${locale}.json`));
 | 
			
		||||
    const [dict, error] = await safely(import(`../definitions/${toolDefinition.dirName}/locales/${locale}.json`));
 | 
			
		||||
 | 
			
		||||
    return dict;
 | 
			
		||||
    if (error) {
 | 
			
		||||
      console.error(error);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return dict ?? { default: {} };
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
 
 | 
			
		||||
@@ -5,13 +5,12 @@ export { defineTool };
 | 
			
		||||
function defineTool(toolDefinition: {
 | 
			
		||||
  slug: string;
 | 
			
		||||
  entryFile: () => Promise<{ default: Component }>;
 | 
			
		||||
  currentDirUrl: string;
 | 
			
		||||
  dirName: string;
 | 
			
		||||
  icon: string;
 | 
			
		||||
  createdAt: Date;
 | 
			
		||||
}) {
 | 
			
		||||
  return {
 | 
			
		||||
    ...toolDefinition,
 | 
			
		||||
    key: toolDefinition.slug,
 | 
			
		||||
    dirName: toolDefinition.currentDirUrl.split('/').slice(-2)[0],
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,5 @@
 | 
			
		||||
import type { Accessor, ParentComponent } from 'solid-js';
 | 
			
		||||
import type { ToolI18nFactory } from './tools.types';
 | 
			
		||||
import { flatten, type Flatten, translator, type Translator } from '@solid-primitives/i18n';
 | 
			
		||||
import { flatten, translator } from '@solid-primitives/i18n';
 | 
			
		||||
import { merge } from 'lodash-es';
 | 
			
		||||
import { createContext, useContext } from 'solid-js';
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,10 @@
 | 
			
		||||
import { keyBy, map } from 'lodash-es';
 | 
			
		||||
import { randomPortGeneratorTool } from './definitions/random-port-generator/random-port-generator.tool';
 | 
			
		||||
import { tokenGeneratorTool } from './definitions/token-generator/token-generator.tool';
 | 
			
		||||
 | 
			
		||||
export const toolDefinitions = [
 | 
			
		||||
  tokenGeneratorTool,
 | 
			
		||||
  randomPortGeneratorTool,
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
export const toolSlugs = map(toolDefinitions, 'slug');
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,4 @@
 | 
			
		||||
import { createMemo } from 'solid-js';
 | 
			
		||||
import { useI18n } from '../i18n/i18n.provider';
 | 
			
		||||
import { toolDefinitions } from './tools.registry';
 | 
			
		||||
 | 
			
		||||
@@ -6,13 +7,13 @@ export { useToolsStore };
 | 
			
		||||
function useToolsStore() {
 | 
			
		||||
  const { t } = useI18n();
 | 
			
		||||
 | 
			
		||||
  const tools = toolDefinitions.map((tool) => {
 | 
			
		||||
  const getTools = createMemo(() => toolDefinitions.map((tool) => {
 | 
			
		||||
    return {
 | 
			
		||||
      ...tool,
 | 
			
		||||
      name: t(`tools.${tool.slug}.name` as any) ?? tool.slug,
 | 
			
		||||
      description: t(`tools.${tool.slug}.description` as any) ?? tool.slug,
 | 
			
		||||
    };
 | 
			
		||||
  });
 | 
			
		||||
  }));
 | 
			
		||||
 | 
			
		||||
  return { tools };
 | 
			
		||||
  return { getTools };
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,42 +1,37 @@
 | 
			
		||||
import { cn } from "@/modules/ui/utils/cn";
 | 
			
		||||
import type { VariantProps } from "class-variance-authority";
 | 
			
		||||
import { cva } from "class-variance-authority";
 | 
			
		||||
import { type ComponentProps, splitProps } from "solid-js";
 | 
			
		||||
import type { VariantProps } from 'class-variance-authority';
 | 
			
		||||
import { cn } from '@/modules/ui/utils/cn';
 | 
			
		||||
import { cva } from 'class-variance-authority';
 | 
			
		||||
import { type ComponentProps, splitProps } from 'solid-js';
 | 
			
		||||
 | 
			
		||||
export const badgeVariants = cva(
 | 
			
		||||
	"inline-flex items-center rounded-md px-2.5 py-0.5 text-xs font-semibold transition-shadow focus-visible:(outline-none ring-1.5 ring-ring)",
 | 
			
		||||
	{
 | 
			
		||||
		variants: {
 | 
			
		||||
			variant: {
 | 
			
		||||
				default:
 | 
			
		||||
					"bg-primary text-primary-foreground shadow hover:bg-primary/80",
 | 
			
		||||
				secondary:
 | 
			
		||||
					"bg-secondary text-secondary-foreground hover:bg-secondary/80",
 | 
			
		||||
				destructive:
 | 
			
		||||
					"bg-destructive text-destructive-foreground shadow hover:bg-destructive/80",
 | 
			
		||||
				outline: "border text-foreground",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		defaultVariants: {
 | 
			
		||||
			variant: "default",
 | 
			
		||||
		},
 | 
			
		||||
	},
 | 
			
		||||
  'inline-flex items-center rounded-md px-2.5 py-0.5 text-xs font-semibold transition-shadow focus-visible:(outline-none ring-1.5 ring-ring)',
 | 
			
		||||
  {
 | 
			
		||||
    variants: {
 | 
			
		||||
      variant: {
 | 
			
		||||
        default: 'bg-primary text-primary-foreground shadow hover:bg-primary/80',
 | 
			
		||||
        secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
 | 
			
		||||
        destructive: 'bg-destructive text-destructive-foreground shadow hover:bg-destructive/80',
 | 
			
		||||
        outline: 'border text-foreground',
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    defaultVariants: {
 | 
			
		||||
      variant: 'default',
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
export const Badge = (
 | 
			
		||||
	props: ComponentProps<"div"> & VariantProps<typeof badgeVariants>,
 | 
			
		||||
) => {
 | 
			
		||||
	const [local, rest] = splitProps(props, ["class", "variant"]);
 | 
			
		||||
export function Badge(props: ComponentProps<'div'> & VariantProps<typeof badgeVariants>) {
 | 
			
		||||
  const [local, rest] = splitProps(props, ['class', 'variant']);
 | 
			
		||||
 | 
			
		||||
	return (
 | 
			
		||||
		<div
 | 
			
		||||
			class={cn(
 | 
			
		||||
				badgeVariants({
 | 
			
		||||
					variant: local.variant,
 | 
			
		||||
				}),
 | 
			
		||||
				local.class,
 | 
			
		||||
			)}
 | 
			
		||||
			{...rest}
 | 
			
		||||
		/>
 | 
			
		||||
	);
 | 
			
		||||
};
 | 
			
		||||
  return (
 | 
			
		||||
    <div
 | 
			
		||||
      class={cn(
 | 
			
		||||
        badgeVariants({
 | 
			
		||||
          variant: local.variant,
 | 
			
		||||
        }),
 | 
			
		||||
        local.class,
 | 
			
		||||
      )}
 | 
			
		||||
      {...rest}
 | 
			
		||||
    />
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,60 +1,60 @@
 | 
			
		||||
import { cn } from "@/modules/ui/utils/cn";
 | 
			
		||||
import type { ComponentProps, ParentComponent } from "solid-js";
 | 
			
		||||
import { splitProps } from "solid-js";
 | 
			
		||||
import type { ComponentProps, ParentComponent } from 'solid-js';
 | 
			
		||||
import { cn } from '@/modules/ui/utils/cn';
 | 
			
		||||
import { splitProps } from 'solid-js';
 | 
			
		||||
 | 
			
		||||
export const Card = (props: ComponentProps<"div">) => {
 | 
			
		||||
	const [local, rest] = splitProps(props, ["class"]);
 | 
			
		||||
export function Card(props: ComponentProps<'div'>) {
 | 
			
		||||
  const [local, rest] = splitProps(props, ['class']);
 | 
			
		||||
 | 
			
		||||
	return (
 | 
			
		||||
		<div
 | 
			
		||||
			class={cn(
 | 
			
		||||
				"rounded-xl border bg-card text-card-foreground shadow",
 | 
			
		||||
				local.class,
 | 
			
		||||
			)}
 | 
			
		||||
			{...rest}
 | 
			
		||||
		/>
 | 
			
		||||
	);
 | 
			
		||||
  return (
 | 
			
		||||
    <div
 | 
			
		||||
      class={cn(
 | 
			
		||||
        'rounded-xl border bg-card text-card-foreground shadow',
 | 
			
		||||
        local.class,
 | 
			
		||||
      )}
 | 
			
		||||
      {...rest}
 | 
			
		||||
    />
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function CardHeader(props: ComponentProps<'div'>) {
 | 
			
		||||
  const [local, rest] = splitProps(props, ['class']);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div class={cn('flex flex-col space-y-1.5 p-6', local.class)} {...rest} />
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const CardTitle: ParentComponent<ComponentProps<'h1'>> = (props) => {
 | 
			
		||||
  const [local, rest] = splitProps(props, ['class']);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <h1
 | 
			
		||||
      class={cn('font-semibold leading-none tracking-tight', local.class)}
 | 
			
		||||
      {...rest}
 | 
			
		||||
    />
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const CardHeader = (props: ComponentProps<"div">) => {
 | 
			
		||||
	const [local, rest] = splitProps(props, ["class"]);
 | 
			
		||||
 | 
			
		||||
	return (
 | 
			
		||||
		<div class={cn("flex flex-col space-y-1.5 p-6", local.class)} {...rest} />
 | 
			
		||||
	);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const CardTitle: ParentComponent<ComponentProps<"h1">> = (props) => {
 | 
			
		||||
	const [local, rest] = splitProps(props, ["class"]);
 | 
			
		||||
 | 
			
		||||
	return (
 | 
			
		||||
		<h1
 | 
			
		||||
			class={cn("font-semibold leading-none tracking-tight", local.class)}
 | 
			
		||||
			{...rest}
 | 
			
		||||
		/>
 | 
			
		||||
	);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const CardDescription: ParentComponent<ComponentProps<"h3">> = (
 | 
			
		||||
	props,
 | 
			
		||||
export const CardDescription: ParentComponent<ComponentProps<'h3'>> = (
 | 
			
		||||
  props,
 | 
			
		||||
) => {
 | 
			
		||||
	const [local, rest] = splitProps(props, ["class"]);
 | 
			
		||||
  const [local, rest] = splitProps(props, ['class']);
 | 
			
		||||
 | 
			
		||||
	return (
 | 
			
		||||
		<h3 class={cn("text-sm text-muted-foreground", local.class)} {...rest} />
 | 
			
		||||
	);
 | 
			
		||||
  return (
 | 
			
		||||
    <h3 class={cn('text-sm text-muted-foreground', local.class)} {...rest} />
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const CardContent = (props: ComponentProps<"div">) => {
 | 
			
		||||
	const [local, rest] = splitProps(props, ["class"]);
 | 
			
		||||
export function CardContent(props: ComponentProps<'div'>) {
 | 
			
		||||
  const [local, rest] = splitProps(props, ['class']);
 | 
			
		||||
 | 
			
		||||
	return <div class={cn("p-6 pt-0", local.class)} {...rest} />;
 | 
			
		||||
};
 | 
			
		||||
  return <div class={cn('p-6 pt-0', local.class)} {...rest} />;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const CardFooter = (props: ComponentProps<"div">) => {
 | 
			
		||||
	const [local, rest] = splitProps(props, ["class"]);
 | 
			
		||||
export function CardFooter(props: ComponentProps<'div'>) {
 | 
			
		||||
  const [local, rest] = splitProps(props, ['class']);
 | 
			
		||||
 | 
			
		||||
	return (
 | 
			
		||||
		<div class={cn("flex items-center p-6 pt-0", local.class)} {...rest} />
 | 
			
		||||
	);
 | 
			
		||||
};
 | 
			
		||||
  return (
 | 
			
		||||
    <div class={cn('flex items-center p-6 pt-0', local.class)} {...rest} />
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										151
									
								
								packages/app/src/modules/ui/components/command.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										151
									
								
								packages/app/src/modules/ui/components/command.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,151 @@
 | 
			
		||||
import type {
 | 
			
		||||
  CommandDialogProps,
 | 
			
		||||
  CommandEmptyProps,
 | 
			
		||||
  CommandGroupProps,
 | 
			
		||||
  CommandInputProps,
 | 
			
		||||
  CommandItemProps,
 | 
			
		||||
  CommandListProps,
 | 
			
		||||
  CommandRootProps,
 | 
			
		||||
} from 'cmdk-solid';
 | 
			
		||||
import type { ComponentProps, VoidProps } from 'solid-js';
 | 
			
		||||
import { cn } from '@/modules/ui/utils/cn';
 | 
			
		||||
import { Command as CommandPrimitive } from 'cmdk-solid';
 | 
			
		||||
import { splitProps } from 'solid-js';
 | 
			
		||||
import { Dialog, DialogContent } from './dialog';
 | 
			
		||||
 | 
			
		||||
export function Command(props: CommandRootProps) {
 | 
			
		||||
  const [local, rest] = splitProps(props, ['class']);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <CommandPrimitive
 | 
			
		||||
      class={cn(
 | 
			
		||||
        'flex size-full flex-col overflow-hidden bg-popover text-popover-foreground',
 | 
			
		||||
        local.class,
 | 
			
		||||
      )}
 | 
			
		||||
      {...rest}
 | 
			
		||||
    />
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function CommandList(props: CommandListProps) {
 | 
			
		||||
  const [local, rest] = splitProps(props, ['class']);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <CommandPrimitive.List
 | 
			
		||||
      class={cn(
 | 
			
		||||
        'max-h-[300px] overflow-y-auto overflow-x-hidden p-1',
 | 
			
		||||
        local.class,
 | 
			
		||||
      )}
 | 
			
		||||
      {...rest}
 | 
			
		||||
    />
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function CommandInput(props: VoidProps<CommandInputProps>) {
 | 
			
		||||
  const [local, rest] = splitProps(props, ['class']);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div class="flex items-center border-b px-3" cmdk-input-wrapper="">
 | 
			
		||||
      <svg
 | 
			
		||||
        xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
        viewBox="0 0 24 24"
 | 
			
		||||
        class="mr-2 h-4 w-4 shrink-0 opacity-50"
 | 
			
		||||
      >
 | 
			
		||||
        <path
 | 
			
		||||
          fill="none"
 | 
			
		||||
          stroke="currentColor"
 | 
			
		||||
          stroke-linecap="round"
 | 
			
		||||
          stroke-linejoin="round"
 | 
			
		||||
          stroke-width="2"
 | 
			
		||||
          d="M3 10a7 7 0 1 0 14 0a7 7 0 1 0-14 0m18 11l-6-6"
 | 
			
		||||
        />
 | 
			
		||||
        <title>Search</title>
 | 
			
		||||
      </svg>
 | 
			
		||||
      <CommandPrimitive.Input
 | 
			
		||||
        class={cn(
 | 
			
		||||
          'flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:(cursor-not-allowed opacity-50)',
 | 
			
		||||
          local.class,
 | 
			
		||||
        )}
 | 
			
		||||
        {...rest}
 | 
			
		||||
      />
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function CommandItem(props: CommandItemProps) {
 | 
			
		||||
  const [local, rest] = splitProps(props, ['class']);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <CommandPrimitive.Item
 | 
			
		||||
      class={cn(
 | 
			
		||||
        'relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5! text-sm outline-none aria-selected:(bg-accent text-accent-foreground) aria-disabled:(pointer-events-none opacity-50)',
 | 
			
		||||
        local.class,
 | 
			
		||||
      )}
 | 
			
		||||
      {...rest}
 | 
			
		||||
    />
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function CommandShortcut(props: ComponentProps<'span'>) {
 | 
			
		||||
  const [local, rest] = splitProps(props, ['class']);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <span
 | 
			
		||||
      class={cn(
 | 
			
		||||
        'ml-auto text-xs tracking-widest text-muted-foreground',
 | 
			
		||||
        local.class,
 | 
			
		||||
      )}
 | 
			
		||||
      {...rest}
 | 
			
		||||
    />
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function CommandDialog(props: CommandDialogProps) {
 | 
			
		||||
  const [local, rest] = splitProps(props, ['children']);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <Dialog {...rest}>
 | 
			
		||||
      <DialogContent class="overflow-hidden p-0">
 | 
			
		||||
        <Command class="[&_[cmdk-group-heading]]:(px-2 font-medium text-muted-foreground) [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:size-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:(px-2 py-3) [&_[cmdk-item]_svg]:size-5">
 | 
			
		||||
          {local.children}
 | 
			
		||||
        </Command>
 | 
			
		||||
      </DialogContent>
 | 
			
		||||
    </Dialog>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function CommandEmpty(props: CommandEmptyProps) {
 | 
			
		||||
  const [local, rest] = splitProps(props, ['class']);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <CommandPrimitive.Empty
 | 
			
		||||
      class={cn('py-6 text-center text-sm', local.class)}
 | 
			
		||||
      {...rest}
 | 
			
		||||
    />
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function CommandGroup(props: CommandGroupProps) {
 | 
			
		||||
  const [local, rest] = splitProps(props, ['class']);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <CommandPrimitive.Group
 | 
			
		||||
      class={cn(
 | 
			
		||||
        'overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:(px-2 py-1.5 text-xs font-medium text-muted-foreground)',
 | 
			
		||||
        local.class,
 | 
			
		||||
      )}
 | 
			
		||||
      {...rest}
 | 
			
		||||
    />
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function CommandSeparator(props: CommandEmptyProps) {
 | 
			
		||||
  const [local, rest] = splitProps(props, ['class']);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <CommandPrimitive.Separator
 | 
			
		||||
      class={cn('-mx-1 h-px bg-border', local.class)}
 | 
			
		||||
      {...rest}
 | 
			
		||||
    />
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										122
									
								
								packages/app/src/modules/ui/components/dialog.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										122
									
								
								packages/app/src/modules/ui/components/dialog.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,122 @@
 | 
			
		||||
import type {
 | 
			
		||||
  DialogContentProps,
 | 
			
		||||
  DialogDescriptionProps,
 | 
			
		||||
  DialogTitleProps,
 | 
			
		||||
} from '@kobalte/core/dialog';
 | 
			
		||||
import type { PolymorphicProps } from '@kobalte/core/polymorphic';
 | 
			
		||||
import type { ComponentProps, ParentProps, ValidComponent } from 'solid-js';
 | 
			
		||||
import { cn } from '@/modules/ui/utils/cn';
 | 
			
		||||
import { Dialog as DialogPrimitive } from '@kobalte/core/dialog';
 | 
			
		||||
import { splitProps } from 'solid-js';
 | 
			
		||||
 | 
			
		||||
export const Dialog = DialogPrimitive;
 | 
			
		||||
export const DialogTrigger = DialogPrimitive.Trigger;
 | 
			
		||||
 | 
			
		||||
type dialogContentProps<T extends ValidComponent = 'div'> = ParentProps<
 | 
			
		||||
  DialogContentProps<T> & {
 | 
			
		||||
    class?: string;
 | 
			
		||||
  }
 | 
			
		||||
>;
 | 
			
		||||
 | 
			
		||||
export function DialogContent<T extends ValidComponent = 'div'>(props: PolymorphicProps<T, dialogContentProps<T>>) {
 | 
			
		||||
  const [local, rest] = splitProps(props as dialogContentProps, [
 | 
			
		||||
    'class',
 | 
			
		||||
    'children',
 | 
			
		||||
  ]);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <DialogPrimitive.Portal>
 | 
			
		||||
      <DialogPrimitive.Overlay
 | 
			
		||||
        class={cn(
 | 
			
		||||
          'fixed inset-0 z-50 bg-background/80 data-[expanded]:(animate-in fade-in-0) data-[closed]:(animate-out fade-out-0)',
 | 
			
		||||
        )}
 | 
			
		||||
        {...rest}
 | 
			
		||||
      />
 | 
			
		||||
      <DialogPrimitive.Content
 | 
			
		||||
        class={cn(
 | 
			
		||||
          'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[expanded]:(animate-in fade-in-0 zoom-in-95 slide-in-from-left-1/2 slide-in-from-top-48% duration-200) data-[closed]:(animate-out fade-out-0 zoom-out-95 slide-out-to-left-1/2 slide-out-to-top-48% duration-200) md:w-full sm:rounded-lg',
 | 
			
		||||
          local.class,
 | 
			
		||||
        )}
 | 
			
		||||
        {...rest}
 | 
			
		||||
      >
 | 
			
		||||
        {local.children}
 | 
			
		||||
        <DialogPrimitive.CloseButton class="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:(outline-none ring-1.5 ring-ring ring-offset-2) disabled:pointer-events-none bg-inherit transition-property-[opacity,box-shadow]">
 | 
			
		||||
          <svg
 | 
			
		||||
            xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
            viewBox="0 0 24 24"
 | 
			
		||||
            class="h-4 w-4"
 | 
			
		||||
          >
 | 
			
		||||
            <path
 | 
			
		||||
              fill="none"
 | 
			
		||||
              stroke="currentColor"
 | 
			
		||||
              stroke-linecap="round"
 | 
			
		||||
              stroke-linejoin="round"
 | 
			
		||||
              stroke-width="2"
 | 
			
		||||
              d="M18 6L6 18M6 6l12 12"
 | 
			
		||||
            />
 | 
			
		||||
            <title>Close</title>
 | 
			
		||||
          </svg>
 | 
			
		||||
        </DialogPrimitive.CloseButton>
 | 
			
		||||
      </DialogPrimitive.Content>
 | 
			
		||||
    </DialogPrimitive.Portal>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type dialogTitleProps<T extends ValidComponent = 'h2'> = DialogTitleProps<T> & {
 | 
			
		||||
  class?: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export function DialogTitle<T extends ValidComponent = 'h2'>(props: PolymorphicProps<T, dialogTitleProps<T>>) {
 | 
			
		||||
  const [local, rest] = splitProps(props as dialogTitleProps, ['class']);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <DialogPrimitive.Title
 | 
			
		||||
      class={cn('text-lg font-semibold text-foreground', local.class)}
 | 
			
		||||
      {...rest}
 | 
			
		||||
    />
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type dialogDescriptionProps<T extends ValidComponent = 'p'> =
 | 
			
		||||
  DialogDescriptionProps<T> & {
 | 
			
		||||
    class?: string;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
export function DialogDescription<T extends ValidComponent = 'p'>(props: PolymorphicProps<T, dialogDescriptionProps<T>>) {
 | 
			
		||||
  const [local, rest] = splitProps(props as dialogDescriptionProps, ['class']);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <DialogPrimitive.Description
 | 
			
		||||
      class={cn('text-sm text-muted-foreground', local.class)}
 | 
			
		||||
      {...rest}
 | 
			
		||||
    />
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function DialogHeader(props: ComponentProps<'div'>) {
 | 
			
		||||
  const [local, rest] = splitProps(props, ['class']);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div
 | 
			
		||||
      class={cn(
 | 
			
		||||
        'flex flex-col space-y-2 text-center sm:text-left',
 | 
			
		||||
        local.class,
 | 
			
		||||
      )}
 | 
			
		||||
      {...rest}
 | 
			
		||||
    />
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function DialogFooter(props: ComponentProps<'div'>) {
 | 
			
		||||
  const [local, rest] = splitProps(props, ['class']);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div
 | 
			
		||||
      class={cn(
 | 
			
		||||
        'flex flex-col-reverse sm:(flex-row justify-end space-x-2)',
 | 
			
		||||
        local.class,
 | 
			
		||||
      )}
 | 
			
		||||
      {...rest}
 | 
			
		||||
    />
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
@@ -1,314 +1,286 @@
 | 
			
		||||
import { cn } from "@/modules/ui/utils/cn";
 | 
			
		||||
import type {
 | 
			
		||||
	DropdownMenuCheckboxItemProps,
 | 
			
		||||
	DropdownMenuContentProps,
 | 
			
		||||
	DropdownMenuGroupLabelProps,
 | 
			
		||||
	DropdownMenuItemLabelProps,
 | 
			
		||||
	DropdownMenuItemProps,
 | 
			
		||||
	DropdownMenuRadioItemProps,
 | 
			
		||||
	DropdownMenuRootProps,
 | 
			
		||||
	DropdownMenuSeparatorProps,
 | 
			
		||||
	DropdownMenuSubTriggerProps,
 | 
			
		||||
} from "@kobalte/core/dropdown-menu";
 | 
			
		||||
import { DropdownMenu as DropdownMenuPrimitive } from "@kobalte/core/dropdown-menu";
 | 
			
		||||
import type { PolymorphicProps } from "@kobalte/core/polymorphic";
 | 
			
		||||
import type { ComponentProps, ParentProps, ValidComponent } from "solid-js";
 | 
			
		||||
import { mergeProps, splitProps } from "solid-js";
 | 
			
		||||
  DropdownMenuCheckboxItemProps,
 | 
			
		||||
  DropdownMenuContentProps,
 | 
			
		||||
  DropdownMenuGroupLabelProps,
 | 
			
		||||
  DropdownMenuItemLabelProps,
 | 
			
		||||
  DropdownMenuItemProps,
 | 
			
		||||
  DropdownMenuRadioItemProps,
 | 
			
		||||
  DropdownMenuRootProps,
 | 
			
		||||
  DropdownMenuSeparatorProps,
 | 
			
		||||
  DropdownMenuSubTriggerProps,
 | 
			
		||||
} from '@kobalte/core/dropdown-menu';
 | 
			
		||||
import type { PolymorphicProps } from '@kobalte/core/polymorphic';
 | 
			
		||||
import type { ComponentProps, ParentProps, ValidComponent } from 'solid-js';
 | 
			
		||||
import { cn } from '@/modules/ui/utils/cn';
 | 
			
		||||
import { DropdownMenu as DropdownMenuPrimitive } from '@kobalte/core/dropdown-menu';
 | 
			
		||||
import { mergeProps, splitProps } from 'solid-js';
 | 
			
		||||
 | 
			
		||||
export const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
 | 
			
		||||
export const DropdownMenuGroup = DropdownMenuPrimitive.Group;
 | 
			
		||||
export const DropdownMenuSub = DropdownMenuPrimitive.Sub;
 | 
			
		||||
export const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
 | 
			
		||||
 | 
			
		||||
export const DropdownMenu = (props: DropdownMenuRootProps) => {
 | 
			
		||||
	const merge = mergeProps<DropdownMenuRootProps[]>({ gutter: 4 }, props);
 | 
			
		||||
export function DropdownMenu(props: DropdownMenuRootProps) {
 | 
			
		||||
  const merge = mergeProps<DropdownMenuRootProps[]>({ gutter: 4 }, props);
 | 
			
		||||
 | 
			
		||||
	return <DropdownMenuPrimitive {...merge} />;
 | 
			
		||||
};
 | 
			
		||||
  return <DropdownMenuPrimitive {...merge} />;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type dropdownMenuContentProps<T extends ValidComponent = "div"> =
 | 
			
		||||
	DropdownMenuContentProps<T> & {
 | 
			
		||||
		class?: string;
 | 
			
		||||
	};
 | 
			
		||||
type dropdownMenuContentProps<T extends ValidComponent = 'div'> =
 | 
			
		||||
  DropdownMenuContentProps<T> & {
 | 
			
		||||
    class?: string;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
export const DropdownMenuContent = <T extends ValidComponent = "div">(
 | 
			
		||||
	props: PolymorphicProps<T, dropdownMenuContentProps<T>>,
 | 
			
		||||
) => {
 | 
			
		||||
	const [local, rest] = splitProps(props as dropdownMenuContentProps, [
 | 
			
		||||
		"class",
 | 
			
		||||
	]);
 | 
			
		||||
export function DropdownMenuContent<T extends ValidComponent = 'div'>(props: PolymorphicProps<T, dropdownMenuContentProps<T>>) {
 | 
			
		||||
  const [local, rest] = splitProps(props as dropdownMenuContentProps, [
 | 
			
		||||
    'class',
 | 
			
		||||
  ]);
 | 
			
		||||
 | 
			
		||||
	return (
 | 
			
		||||
		<DropdownMenuPrimitive.Portal>
 | 
			
		||||
			<DropdownMenuPrimitive.Content
 | 
			
		||||
				class={cn(
 | 
			
		||||
					"min-w-8rem z-50 overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[expanded]:(animate-in fade-in-0 zoom-in-95) data-[closed]:(animate-out fade-out-0 zoom-out-95) focus-visible:(outline-none ring-1.5 ring-ring) transition-shadow",
 | 
			
		||||
					local.class,
 | 
			
		||||
				)}
 | 
			
		||||
				{...rest}
 | 
			
		||||
			/>
 | 
			
		||||
		</DropdownMenuPrimitive.Portal>
 | 
			
		||||
	);
 | 
			
		||||
};
 | 
			
		||||
  return (
 | 
			
		||||
    <DropdownMenuPrimitive.Portal>
 | 
			
		||||
      <DropdownMenuPrimitive.Content
 | 
			
		||||
        class={cn(
 | 
			
		||||
          'min-w-8rem z-50 overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[expanded]:(animate-in fade-in-0 zoom-in-95) data-[closed]:(animate-out fade-out-0 zoom-out-95) focus-visible:(outline-none ring-1.5 ring-ring) transition-shadow',
 | 
			
		||||
          local.class,
 | 
			
		||||
        )}
 | 
			
		||||
        {...rest}
 | 
			
		||||
      />
 | 
			
		||||
    </DropdownMenuPrimitive.Portal>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type dropdownMenuItemProps<T extends ValidComponent = "div"> =
 | 
			
		||||
	DropdownMenuItemProps<T> & {
 | 
			
		||||
		class?: string;
 | 
			
		||||
		inset?: boolean;
 | 
			
		||||
	};
 | 
			
		||||
type dropdownMenuItemProps<T extends ValidComponent = 'div'> =
 | 
			
		||||
  DropdownMenuItemProps<T> & {
 | 
			
		||||
    class?: string;
 | 
			
		||||
    inset?: boolean;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
export const DropdownMenuItem = <T extends ValidComponent = "div">(
 | 
			
		||||
	props: PolymorphicProps<T, dropdownMenuItemProps<T>>,
 | 
			
		||||
) => {
 | 
			
		||||
	const [local, rest] = splitProps(props as dropdownMenuItemProps, [
 | 
			
		||||
		"class",
 | 
			
		||||
		"inset",
 | 
			
		||||
	]);
 | 
			
		||||
export function DropdownMenuItem<T extends ValidComponent = 'div'>(props: PolymorphicProps<T, dropdownMenuItemProps<T>>) {
 | 
			
		||||
  const [local, rest] = splitProps(props as dropdownMenuItemProps, [
 | 
			
		||||
    'class',
 | 
			
		||||
    'inset',
 | 
			
		||||
  ]);
 | 
			
		||||
 | 
			
		||||
	return (
 | 
			
		||||
		<DropdownMenuPrimitive.Item
 | 
			
		||||
			class={cn(
 | 
			
		||||
				"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:(bg-accent text-accent-foreground) data-[disabled]:(pointer-events-none opacity-50)",
 | 
			
		||||
				local.inset && "pl-8",
 | 
			
		||||
				local.class,
 | 
			
		||||
			)}
 | 
			
		||||
			{...rest}
 | 
			
		||||
		/>
 | 
			
		||||
	);
 | 
			
		||||
};
 | 
			
		||||
  return (
 | 
			
		||||
    <DropdownMenuPrimitive.Item
 | 
			
		||||
      class={cn(
 | 
			
		||||
        'relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:(bg-accent text-accent-foreground) data-[disabled]:(pointer-events-none opacity-50)',
 | 
			
		||||
        local.inset && 'pl-8',
 | 
			
		||||
        local.class,
 | 
			
		||||
      )}
 | 
			
		||||
      {...rest}
 | 
			
		||||
    />
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type dropdownMenuGroupLabelProps<T extends ValidComponent = "span"> =
 | 
			
		||||
	DropdownMenuGroupLabelProps<T> & {
 | 
			
		||||
		class?: string;
 | 
			
		||||
	};
 | 
			
		||||
type dropdownMenuGroupLabelProps<T extends ValidComponent = 'span'> =
 | 
			
		||||
  DropdownMenuGroupLabelProps<T> & {
 | 
			
		||||
    class?: string;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
export const DropdownMenuGroupLabel = <T extends ValidComponent = "span">(
 | 
			
		||||
	props: PolymorphicProps<T, dropdownMenuGroupLabelProps<T>>,
 | 
			
		||||
) => {
 | 
			
		||||
	const [local, rest] = splitProps(props as dropdownMenuGroupLabelProps, [
 | 
			
		||||
		"class",
 | 
			
		||||
	]);
 | 
			
		||||
export function DropdownMenuGroupLabel<T extends ValidComponent = 'span'>(props: PolymorphicProps<T, dropdownMenuGroupLabelProps<T>>) {
 | 
			
		||||
  const [local, rest] = splitProps(props as dropdownMenuGroupLabelProps, [
 | 
			
		||||
    'class',
 | 
			
		||||
  ]);
 | 
			
		||||
 | 
			
		||||
	return (
 | 
			
		||||
		<DropdownMenuPrimitive.GroupLabel
 | 
			
		||||
			as="div"
 | 
			
		||||
			class={cn("px-2 py-1.5 text-sm font-semibold", local.class)}
 | 
			
		||||
			{...rest}
 | 
			
		||||
		/>
 | 
			
		||||
	);
 | 
			
		||||
};
 | 
			
		||||
  return (
 | 
			
		||||
    <DropdownMenuPrimitive.GroupLabel
 | 
			
		||||
      as="div"
 | 
			
		||||
      class={cn('px-2 py-1.5 text-sm font-semibold', local.class)}
 | 
			
		||||
      {...rest}
 | 
			
		||||
    />
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type dropdownMenuItemLabelProps<T extends ValidComponent = "div"> =
 | 
			
		||||
	DropdownMenuItemLabelProps<T> & {
 | 
			
		||||
		class?: string;
 | 
			
		||||
	};
 | 
			
		||||
type dropdownMenuItemLabelProps<T extends ValidComponent = 'div'> =
 | 
			
		||||
  DropdownMenuItemLabelProps<T> & {
 | 
			
		||||
    class?: string;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
export const DropdownMenuItemLabel = <T extends ValidComponent = "div">(
 | 
			
		||||
	props: PolymorphicProps<T, dropdownMenuItemLabelProps<T>>,
 | 
			
		||||
) => {
 | 
			
		||||
	const [local, rest] = splitProps(props as dropdownMenuItemLabelProps, [
 | 
			
		||||
		"class",
 | 
			
		||||
	]);
 | 
			
		||||
export function DropdownMenuItemLabel<T extends ValidComponent = 'div'>(props: PolymorphicProps<T, dropdownMenuItemLabelProps<T>>) {
 | 
			
		||||
  const [local, rest] = splitProps(props as dropdownMenuItemLabelProps, [
 | 
			
		||||
    'class',
 | 
			
		||||
  ]);
 | 
			
		||||
 | 
			
		||||
	return (
 | 
			
		||||
		<DropdownMenuPrimitive.ItemLabel
 | 
			
		||||
			as="div"
 | 
			
		||||
			class={cn("px-2 py-1.5 text-sm font-semibold", local.class)}
 | 
			
		||||
			{...rest}
 | 
			
		||||
		/>
 | 
			
		||||
	);
 | 
			
		||||
};
 | 
			
		||||
  return (
 | 
			
		||||
    <DropdownMenuPrimitive.ItemLabel
 | 
			
		||||
      as="div"
 | 
			
		||||
      class={cn('px-2 py-1.5 text-sm font-semibold', local.class)}
 | 
			
		||||
      {...rest}
 | 
			
		||||
    />
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type dropdownMenuSeparatorProps<T extends ValidComponent = "hr"> =
 | 
			
		||||
	DropdownMenuSeparatorProps<T> & {
 | 
			
		||||
		class?: string;
 | 
			
		||||
	};
 | 
			
		||||
type dropdownMenuSeparatorProps<T extends ValidComponent = 'hr'> =
 | 
			
		||||
  DropdownMenuSeparatorProps<T> & {
 | 
			
		||||
    class?: string;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
export const DropdownMenuSeparator = <T extends ValidComponent = "hr">(
 | 
			
		||||
	props: PolymorphicProps<T, dropdownMenuSeparatorProps<T>>,
 | 
			
		||||
) => {
 | 
			
		||||
	const [local, rest] = splitProps(props as dropdownMenuSeparatorProps, [
 | 
			
		||||
		"class",
 | 
			
		||||
	]);
 | 
			
		||||
export function DropdownMenuSeparator<T extends ValidComponent = 'hr'>(props: PolymorphicProps<T, dropdownMenuSeparatorProps<T>>) {
 | 
			
		||||
  const [local, rest] = splitProps(props as dropdownMenuSeparatorProps, [
 | 
			
		||||
    'class',
 | 
			
		||||
  ]);
 | 
			
		||||
 | 
			
		||||
	return (
 | 
			
		||||
		<DropdownMenuPrimitive.Separator
 | 
			
		||||
			class={cn("-mx-1 my-1 h-px bg-muted", local.class)}
 | 
			
		||||
			{...rest}
 | 
			
		||||
		/>
 | 
			
		||||
	);
 | 
			
		||||
};
 | 
			
		||||
  return (
 | 
			
		||||
    <DropdownMenuPrimitive.Separator
 | 
			
		||||
      class={cn('-mx-1 my-1 h-px bg-muted', local.class)}
 | 
			
		||||
      {...rest}
 | 
			
		||||
    />
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const DropdownMenuShortcut = (props: ComponentProps<"span">) => {
 | 
			
		||||
	const [local, rest] = splitProps(props, ["class"]);
 | 
			
		||||
export function DropdownMenuShortcut(props: ComponentProps<'span'>) {
 | 
			
		||||
  const [local, rest] = splitProps(props, ['class']);
 | 
			
		||||
 | 
			
		||||
	return (
 | 
			
		||||
		<span
 | 
			
		||||
			class={cn("ml-auto text-xs tracking-widest opacity-60", local.class)}
 | 
			
		||||
			{...rest}
 | 
			
		||||
		/>
 | 
			
		||||
	);
 | 
			
		||||
};
 | 
			
		||||
  return (
 | 
			
		||||
    <span
 | 
			
		||||
      class={cn('ml-auto text-xs tracking-widest opacity-60', local.class)}
 | 
			
		||||
      {...rest}
 | 
			
		||||
    />
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type dropdownMenuSubTriggerProps<T extends ValidComponent = "div"> =
 | 
			
		||||
	ParentProps<
 | 
			
		||||
		DropdownMenuSubTriggerProps<T> & {
 | 
			
		||||
			class?: string;
 | 
			
		||||
		}
 | 
			
		||||
	>;
 | 
			
		||||
type dropdownMenuSubTriggerProps<T extends ValidComponent = 'div'> = ParentProps<DropdownMenuSubTriggerProps<T> & { class?: string }>;
 | 
			
		||||
 | 
			
		||||
export const DropdownMenuSubTrigger = <T extends ValidComponent = "div">(
 | 
			
		||||
	props: PolymorphicProps<T, dropdownMenuSubTriggerProps<T>>,
 | 
			
		||||
) => {
 | 
			
		||||
	const [local, rest] = splitProps(props as dropdownMenuSubTriggerProps, [
 | 
			
		||||
		"class",
 | 
			
		||||
		"children",
 | 
			
		||||
	]);
 | 
			
		||||
export function DropdownMenuSubTrigger<T extends ValidComponent = 'div'>(props: PolymorphicProps<T, dropdownMenuSubTriggerProps<T>>) {
 | 
			
		||||
  const [local, rest] = splitProps(props as dropdownMenuSubTriggerProps, [
 | 
			
		||||
    'class',
 | 
			
		||||
    'children',
 | 
			
		||||
  ]);
 | 
			
		||||
 | 
			
		||||
	return (
 | 
			
		||||
		<DropdownMenuPrimitive.SubTrigger
 | 
			
		||||
			class={cn(
 | 
			
		||||
				"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[expanded]:bg-accent",
 | 
			
		||||
				local.class,
 | 
			
		||||
			)}
 | 
			
		||||
			{...rest}
 | 
			
		||||
		>
 | 
			
		||||
			{local.children}
 | 
			
		||||
			<svg
 | 
			
		||||
				xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
				width="1em"
 | 
			
		||||
				height="1em"
 | 
			
		||||
				viewBox="0 0 24 24"
 | 
			
		||||
				class="ml-auto h-4 w-4"
 | 
			
		||||
			>
 | 
			
		||||
				<path
 | 
			
		||||
					fill="none"
 | 
			
		||||
					stroke="currentColor"
 | 
			
		||||
					stroke-linecap="round"
 | 
			
		||||
					stroke-linejoin="round"
 | 
			
		||||
					stroke-width="2"
 | 
			
		||||
					d="m9 6l6 6l-6 6"
 | 
			
		||||
				/>
 | 
			
		||||
				<title>Arrow</title>
 | 
			
		||||
			</svg>
 | 
			
		||||
		</DropdownMenuPrimitive.SubTrigger>
 | 
			
		||||
	);
 | 
			
		||||
};
 | 
			
		||||
  return (
 | 
			
		||||
    <DropdownMenuPrimitive.SubTrigger
 | 
			
		||||
      class={cn(
 | 
			
		||||
        'flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[expanded]:bg-accent',
 | 
			
		||||
        local.class,
 | 
			
		||||
      )}
 | 
			
		||||
      {...rest}
 | 
			
		||||
    >
 | 
			
		||||
      {local.children}
 | 
			
		||||
      <svg
 | 
			
		||||
        xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
        width="1em"
 | 
			
		||||
        height="1em"
 | 
			
		||||
        viewBox="0 0 24 24"
 | 
			
		||||
        class="ml-auto h-4 w-4"
 | 
			
		||||
      >
 | 
			
		||||
        <path
 | 
			
		||||
          fill="none"
 | 
			
		||||
          stroke="currentColor"
 | 
			
		||||
          stroke-linecap="round"
 | 
			
		||||
          stroke-linejoin="round"
 | 
			
		||||
          stroke-width="2"
 | 
			
		||||
          d="m9 6l6 6l-6 6"
 | 
			
		||||
        />
 | 
			
		||||
        <title>Arrow</title>
 | 
			
		||||
      </svg>
 | 
			
		||||
    </DropdownMenuPrimitive.SubTrigger>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type dropdownMenuSubContentProps<T extends ValidComponent = "div"> =
 | 
			
		||||
	DropdownMenuSubTriggerProps<T> & {
 | 
			
		||||
		class?: string;
 | 
			
		||||
	};
 | 
			
		||||
type dropdownMenuSubContentProps<T extends ValidComponent = 'div'> =
 | 
			
		||||
  DropdownMenuSubTriggerProps<T> & {
 | 
			
		||||
    class?: string;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
export const DropdownMenuSubContent = <T extends ValidComponent = "div">(
 | 
			
		||||
	props: PolymorphicProps<T, dropdownMenuSubContentProps<T>>,
 | 
			
		||||
) => {
 | 
			
		||||
	const [local, rest] = splitProps(props as dropdownMenuSubContentProps, [
 | 
			
		||||
		"class",
 | 
			
		||||
	]);
 | 
			
		||||
export function DropdownMenuSubContent<T extends ValidComponent = 'div'>(props: PolymorphicProps<T, dropdownMenuSubContentProps<T>>) {
 | 
			
		||||
  const [local, rest] = splitProps(props as dropdownMenuSubContentProps, [
 | 
			
		||||
    'class',
 | 
			
		||||
  ]);
 | 
			
		||||
 | 
			
		||||
	return (
 | 
			
		||||
		<DropdownMenuPrimitive.Portal>
 | 
			
		||||
			<DropdownMenuPrimitive.SubContent
 | 
			
		||||
				class={cn(
 | 
			
		||||
					"min-w-8rem z-50 overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[expanded]:(animate-in fade-in-0 zoom-in-95) data-[closed]:(animate-out fade-out-0 zoom-out-95)",
 | 
			
		||||
					local.class,
 | 
			
		||||
				)}
 | 
			
		||||
				{...rest}
 | 
			
		||||
			/>
 | 
			
		||||
		</DropdownMenuPrimitive.Portal>
 | 
			
		||||
	);
 | 
			
		||||
};
 | 
			
		||||
  return (
 | 
			
		||||
    <DropdownMenuPrimitive.Portal>
 | 
			
		||||
      <DropdownMenuPrimitive.SubContent
 | 
			
		||||
        class={cn(
 | 
			
		||||
          'min-w-8rem z-50 overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[expanded]:(animate-in fade-in-0 zoom-in-95) data-[closed]:(animate-out fade-out-0 zoom-out-95)',
 | 
			
		||||
          local.class,
 | 
			
		||||
        )}
 | 
			
		||||
        {...rest}
 | 
			
		||||
      />
 | 
			
		||||
    </DropdownMenuPrimitive.Portal>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type dropdownMenuCheckboxItemProps<T extends ValidComponent = "div"> =
 | 
			
		||||
	ParentProps<
 | 
			
		||||
		DropdownMenuCheckboxItemProps<T> & {
 | 
			
		||||
			class?: string;
 | 
			
		||||
		}
 | 
			
		||||
	>;
 | 
			
		||||
type dropdownMenuCheckboxItemProps<T extends ValidComponent = 'div'> = ParentProps<DropdownMenuCheckboxItemProps<T> & { class?: string }>;
 | 
			
		||||
 | 
			
		||||
export const DropdownMenuCheckboxItem = <T extends ValidComponent = "div">(
 | 
			
		||||
	props: PolymorphicProps<T, dropdownMenuCheckboxItemProps<T>>,
 | 
			
		||||
) => {
 | 
			
		||||
	const [local, rest] = splitProps(props as dropdownMenuCheckboxItemProps, [
 | 
			
		||||
		"class",
 | 
			
		||||
		"children",
 | 
			
		||||
	]);
 | 
			
		||||
export function DropdownMenuCheckboxItem<T extends ValidComponent = 'div'>(props: PolymorphicProps<T, dropdownMenuCheckboxItemProps<T>>) {
 | 
			
		||||
  const [local, rest] = splitProps(props as dropdownMenuCheckboxItemProps, [
 | 
			
		||||
    'class',
 | 
			
		||||
    'children',
 | 
			
		||||
  ]);
 | 
			
		||||
 | 
			
		||||
	return (
 | 
			
		||||
		<DropdownMenuPrimitive.CheckboxItem
 | 
			
		||||
			class={cn(
 | 
			
		||||
				"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:(bg-accent text-accent-foreground) data-[disabled]:(pointer-events-none opacity-50)",
 | 
			
		||||
				local.class,
 | 
			
		||||
			)}
 | 
			
		||||
			{...rest}
 | 
			
		||||
		>
 | 
			
		||||
			<DropdownMenuPrimitive.ItemIndicator class="absolute left-2 inline-flex h-4 w-4 items-center justify-center">
 | 
			
		||||
				<svg
 | 
			
		||||
					xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
					viewBox="0 0 24 24"
 | 
			
		||||
					class="h-4 w-4"
 | 
			
		||||
				>
 | 
			
		||||
					<path
 | 
			
		||||
						fill="none"
 | 
			
		||||
						stroke="currentColor"
 | 
			
		||||
						stroke-linecap="round"
 | 
			
		||||
						stroke-linejoin="round"
 | 
			
		||||
						stroke-width="2"
 | 
			
		||||
						d="m5 12l5 5L20 7"
 | 
			
		||||
					/>
 | 
			
		||||
					<title>Checkbox</title>
 | 
			
		||||
				</svg>
 | 
			
		||||
			</DropdownMenuPrimitive.ItemIndicator>
 | 
			
		||||
			{props.children}
 | 
			
		||||
		</DropdownMenuPrimitive.CheckboxItem>
 | 
			
		||||
	);
 | 
			
		||||
};
 | 
			
		||||
  return (
 | 
			
		||||
    <DropdownMenuPrimitive.CheckboxItem
 | 
			
		||||
      class={cn(
 | 
			
		||||
        'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:(bg-accent text-accent-foreground) data-[disabled]:(pointer-events-none opacity-50)',
 | 
			
		||||
        local.class,
 | 
			
		||||
      )}
 | 
			
		||||
      {...rest}
 | 
			
		||||
    >
 | 
			
		||||
      <DropdownMenuPrimitive.ItemIndicator class="absolute left-2 inline-flex h-4 w-4 items-center justify-center">
 | 
			
		||||
        <svg
 | 
			
		||||
          xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
          viewBox="0 0 24 24"
 | 
			
		||||
          class="h-4 w-4"
 | 
			
		||||
        >
 | 
			
		||||
          <path
 | 
			
		||||
            fill="none"
 | 
			
		||||
            stroke="currentColor"
 | 
			
		||||
            stroke-linecap="round"
 | 
			
		||||
            stroke-linejoin="round"
 | 
			
		||||
            stroke-width="2"
 | 
			
		||||
            d="m5 12l5 5L20 7"
 | 
			
		||||
          />
 | 
			
		||||
          <title>Checkbox</title>
 | 
			
		||||
        </svg>
 | 
			
		||||
      </DropdownMenuPrimitive.ItemIndicator>
 | 
			
		||||
      {props.children}
 | 
			
		||||
    </DropdownMenuPrimitive.CheckboxItem>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type dropdownMenuRadioItemProps<T extends ValidComponent = "div"> = ParentProps<
 | 
			
		||||
	DropdownMenuRadioItemProps<T> & {
 | 
			
		||||
		class?: string;
 | 
			
		||||
	}
 | 
			
		||||
type dropdownMenuRadioItemProps<T extends ValidComponent = 'div'> = ParentProps<
 | 
			
		||||
  DropdownMenuRadioItemProps<T> & {
 | 
			
		||||
    class?: string;
 | 
			
		||||
  }
 | 
			
		||||
>;
 | 
			
		||||
 | 
			
		||||
export const DropdownMenuRadioItem = <T extends ValidComponent = "div">(
 | 
			
		||||
	props: PolymorphicProps<T, dropdownMenuRadioItemProps<T>>,
 | 
			
		||||
) => {
 | 
			
		||||
	const [local, rest] = splitProps(props as dropdownMenuRadioItemProps, [
 | 
			
		||||
		"class",
 | 
			
		||||
		"children",
 | 
			
		||||
	]);
 | 
			
		||||
export function DropdownMenuRadioItem<T extends ValidComponent = 'div'>(props: PolymorphicProps<T, dropdownMenuRadioItemProps<T>>) {
 | 
			
		||||
  const [local, rest] = splitProps(props as dropdownMenuRadioItemProps, [
 | 
			
		||||
    'class',
 | 
			
		||||
    'children',
 | 
			
		||||
  ]);
 | 
			
		||||
 | 
			
		||||
	return (
 | 
			
		||||
		<DropdownMenuPrimitive.RadioItem
 | 
			
		||||
			class={cn(
 | 
			
		||||
				"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:(bg-accent text-accent-foreground) data-[disabled]:(pointer-events-none opacity-50)",
 | 
			
		||||
				local.class,
 | 
			
		||||
			)}
 | 
			
		||||
			{...rest}
 | 
			
		||||
		>
 | 
			
		||||
			<DropdownMenuPrimitive.ItemIndicator class="absolute left-2 inline-flex h-4 w-4 items-center justify-center">
 | 
			
		||||
				<svg
 | 
			
		||||
					xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
					viewBox="0 0 24 24"
 | 
			
		||||
					class="h-2 w-2"
 | 
			
		||||
				>
 | 
			
		||||
					<g
 | 
			
		||||
						fill="none"
 | 
			
		||||
						stroke-linecap="round"
 | 
			
		||||
						stroke-linejoin="round"
 | 
			
		||||
						stroke-width="2"
 | 
			
		||||
					>
 | 
			
		||||
						<path d="M0 0h24v24H0z" />
 | 
			
		||||
						<path
 | 
			
		||||
							fill="currentColor"
 | 
			
		||||
							d="M7 3.34a10 10 0 1 1-4.995 8.984L2 12l.005-.324A10 10 0 0 1 7 3.34"
 | 
			
		||||
						/>
 | 
			
		||||
					</g>
 | 
			
		||||
					<title>Radio</title>
 | 
			
		||||
				</svg>
 | 
			
		||||
			</DropdownMenuPrimitive.ItemIndicator>
 | 
			
		||||
			{props.children}
 | 
			
		||||
		</DropdownMenuPrimitive.RadioItem>
 | 
			
		||||
	);
 | 
			
		||||
};
 | 
			
		||||
  return (
 | 
			
		||||
    <DropdownMenuPrimitive.RadioItem
 | 
			
		||||
      class={cn(
 | 
			
		||||
        'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:(bg-accent text-accent-foreground) data-[disabled]:(pointer-events-none opacity-50)',
 | 
			
		||||
        local.class,
 | 
			
		||||
      )}
 | 
			
		||||
      {...rest}
 | 
			
		||||
    >
 | 
			
		||||
      <DropdownMenuPrimitive.ItemIndicator class="absolute left-2 inline-flex h-4 w-4 items-center justify-center">
 | 
			
		||||
        <svg
 | 
			
		||||
          xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
          viewBox="0 0 24 24"
 | 
			
		||||
          class="h-2 w-2"
 | 
			
		||||
        >
 | 
			
		||||
          <g
 | 
			
		||||
            fill="none"
 | 
			
		||||
            stroke-linecap="round"
 | 
			
		||||
            stroke-linejoin="round"
 | 
			
		||||
            stroke-width="2"
 | 
			
		||||
          >
 | 
			
		||||
            <path d="M0 0h24v24H0z" />
 | 
			
		||||
            <path
 | 
			
		||||
              fill="currentColor"
 | 
			
		||||
              d="M7 3.34a10 10 0 1 1-4.995 8.984L2 12l.005-.324A10 10 0 0 1 7 3.34"
 | 
			
		||||
            />
 | 
			
		||||
          </g>
 | 
			
		||||
          <title>Radio</title>
 | 
			
		||||
        </svg>
 | 
			
		||||
      </DropdownMenuPrimitive.ItemIndicator>
 | 
			
		||||
      {props.children}
 | 
			
		||||
    </DropdownMenuPrimitive.RadioItem>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										20
									
								
								packages/app/src/modules/ui/components/sonner.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								packages/app/src/modules/ui/components/sonner.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,20 @@
 | 
			
		||||
import { Toaster as Sonner, toast } from 'solid-sonner';
 | 
			
		||||
 | 
			
		||||
export { toast };
 | 
			
		||||
 | 
			
		||||
export function Toaster(props: Parameters<typeof Sonner>[0]) {
 | 
			
		||||
  return (
 | 
			
		||||
    <Sonner
 | 
			
		||||
      class="toaster group"
 | 
			
		||||
      toastOptions={{
 | 
			
		||||
        classes: {
 | 
			
		||||
          toast: 'group toast group-[.toaster]:(bg-background text-foreground border-border shadow-lg)',
 | 
			
		||||
          description: 'group-[.toast]:text-muted-foreground',
 | 
			
		||||
          actionButton: 'group-[.toast]:(bg-primary text-primary-foreground)',
 | 
			
		||||
          cancelButton: 'group-[.toast]:(bg-muted text-muted-foreground)',
 | 
			
		||||
        },
 | 
			
		||||
      }}
 | 
			
		||||
      {...props}
 | 
			
		||||
    />
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
@@ -1,62 +1,54 @@
 | 
			
		||||
import { cn } from "@/modules/ui/utils/cn";
 | 
			
		||||
import type { PolymorphicProps } from "@kobalte/core/polymorphic";
 | 
			
		||||
import type { PolymorphicProps } from '@kobalte/core/polymorphic';
 | 
			
		||||
import type {
 | 
			
		||||
	SwitchControlProps,
 | 
			
		||||
	SwitchThumbProps,
 | 
			
		||||
} from "@kobalte/core/switch";
 | 
			
		||||
import { Switch as SwitchPrimitive } from "@kobalte/core/switch";
 | 
			
		||||
import type { ParentProps, ValidComponent, VoidProps } from "solid-js";
 | 
			
		||||
import { splitProps } from "solid-js";
 | 
			
		||||
  SwitchControlProps,
 | 
			
		||||
  SwitchThumbProps,
 | 
			
		||||
} from '@kobalte/core/switch';
 | 
			
		||||
import type { ParentProps, ValidComponent, VoidProps } from 'solid-js';
 | 
			
		||||
import { cn } from '@/modules/ui/utils/cn';
 | 
			
		||||
import { Switch as SwitchPrimitive } from '@kobalte/core/switch';
 | 
			
		||||
import { splitProps } from 'solid-js';
 | 
			
		||||
 | 
			
		||||
export const SwitchLabel = SwitchPrimitive.Label;
 | 
			
		||||
export const Switch = SwitchPrimitive;
 | 
			
		||||
export const SwitchErrorMessage = SwitchPrimitive.ErrorMessage;
 | 
			
		||||
export const SwitchDescription = SwitchPrimitive.Description;
 | 
			
		||||
 | 
			
		||||
type switchControlProps<T extends ValidComponent = "input"> = ParentProps<
 | 
			
		||||
	SwitchControlProps<T> & { class?: string }
 | 
			
		||||
>;
 | 
			
		||||
type switchControlProps<T extends ValidComponent = 'input'> = ParentProps<SwitchControlProps<T> & { class?: string }>;
 | 
			
		||||
 | 
			
		||||
export const SwitchControl = <T extends ValidComponent = "input">(
 | 
			
		||||
	props: PolymorphicProps<T, switchControlProps<T>>,
 | 
			
		||||
) => {
 | 
			
		||||
	const [local, rest] = splitProps(props as switchControlProps, [
 | 
			
		||||
		"class",
 | 
			
		||||
		"children",
 | 
			
		||||
	]);
 | 
			
		||||
export function SwitchControl<T extends ValidComponent = 'input'>(props: PolymorphicProps<T, switchControlProps<T>>) {
 | 
			
		||||
  const [local, rest] = splitProps(props as switchControlProps, [
 | 
			
		||||
    'class',
 | 
			
		||||
    'children',
 | 
			
		||||
  ]);
 | 
			
		||||
 | 
			
		||||
	return (
 | 
			
		||||
		<>
 | 
			
		||||
			<SwitchPrimitive.Input class="[&:focus-visible+div]:(outline-none ring-1.5 ring-ring ring-offset-2 ring-offset-background)" />
 | 
			
		||||
			<SwitchPrimitive.Control
 | 
			
		||||
				class={cn(
 | 
			
		||||
					"inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent bg-input shadow-sm transition-shadow data-[disabled]:(cursor-not-allowed opacity-50) data-[checked]:bg-primary transition-property-[box-shadow,color,background-color]",
 | 
			
		||||
					local.class,
 | 
			
		||||
				)}
 | 
			
		||||
				{...rest}
 | 
			
		||||
			>
 | 
			
		||||
				{local.children}
 | 
			
		||||
			</SwitchPrimitive.Control>
 | 
			
		||||
		</>
 | 
			
		||||
	);
 | 
			
		||||
};
 | 
			
		||||
  return (
 | 
			
		||||
    <>
 | 
			
		||||
      <SwitchPrimitive.Input class="[&:focus-visible+div]:(outline-none ring-1.5 ring-ring ring-offset-2 ring-offset-background)" />
 | 
			
		||||
      <SwitchPrimitive.Control
 | 
			
		||||
        class={cn(
 | 
			
		||||
          'inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent bg-input shadow-sm transition-shadow data-[disabled]:(cursor-not-allowed opacity-50) data-[checked]:bg-primary transition-property-[box-shadow,color,background-color]',
 | 
			
		||||
          local.class,
 | 
			
		||||
        )}
 | 
			
		||||
        {...rest}
 | 
			
		||||
      >
 | 
			
		||||
        {local.children}
 | 
			
		||||
      </SwitchPrimitive.Control>
 | 
			
		||||
    </>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type switchThumbProps<T extends ValidComponent = "div"> = VoidProps<
 | 
			
		||||
	SwitchThumbProps<T> & { class?: string }
 | 
			
		||||
>;
 | 
			
		||||
type switchThumbProps<T extends ValidComponent = 'div'> = VoidProps<SwitchThumbProps<T> & { class?: string }>;
 | 
			
		||||
 | 
			
		||||
export const SwitchThumb = <T extends ValidComponent = "div">(
 | 
			
		||||
	props: PolymorphicProps<T, switchThumbProps<T>>,
 | 
			
		||||
) => {
 | 
			
		||||
	const [local, rest] = splitProps(props as switchThumbProps, ["class"]);
 | 
			
		||||
export function SwitchThumb<T extends ValidComponent = 'div'>(props: PolymorphicProps<T, switchThumbProps<T>>) {
 | 
			
		||||
  const [local, rest] = splitProps(props as switchThumbProps, ['class']);
 | 
			
		||||
 | 
			
		||||
	return (
 | 
			
		||||
		<SwitchPrimitive.Thumb
 | 
			
		||||
			class={cn(
 | 
			
		||||
				"pointer-events-none block h-4 w-4 translate-x-0 rounded-full bg-background shadow-lg transition-transform data-[checked]:translate-x-4",
 | 
			
		||||
				local.class,
 | 
			
		||||
			)}
 | 
			
		||||
			{...rest}
 | 
			
		||||
		/>
 | 
			
		||||
	);
 | 
			
		||||
};
 | 
			
		||||
  return (
 | 
			
		||||
    <SwitchPrimitive.Thumb
 | 
			
		||||
      class={cn(
 | 
			
		||||
        'pointer-events-none block h-4 w-4 translate-x-0 rounded-full bg-background shadow-lg transition-transform data-[checked]:translate-x-4',
 | 
			
		||||
        local.class,
 | 
			
		||||
      )}
 | 
			
		||||
      {...rest}
 | 
			
		||||
    />
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,28 +1,26 @@
 | 
			
		||||
import { cn } from "@/modules/ui/utils/cn";
 | 
			
		||||
import type { PolymorphicProps } from "@kobalte/core/polymorphic";
 | 
			
		||||
import type { TextFieldTextAreaProps } from "@kobalte/core/text-field";
 | 
			
		||||
import { TextArea as TextFieldPrimitive } from "@kobalte/core/text-field";
 | 
			
		||||
import type { ValidComponent, VoidProps } from "solid-js";
 | 
			
		||||
import { splitProps } from "solid-js";
 | 
			
		||||
import type { PolymorphicProps } from '@kobalte/core/polymorphic';
 | 
			
		||||
import type { TextFieldTextAreaProps } from '@kobalte/core/text-field';
 | 
			
		||||
import type { ValidComponent, VoidProps } from 'solid-js';
 | 
			
		||||
import { cn } from '@/modules/ui/utils/cn';
 | 
			
		||||
import { TextArea as TextFieldPrimitive } from '@kobalte/core/text-field';
 | 
			
		||||
import { splitProps } from 'solid-js';
 | 
			
		||||
 | 
			
		||||
type textAreaProps<T extends ValidComponent = "textarea"> = VoidProps<
 | 
			
		||||
	TextFieldTextAreaProps<T> & {
 | 
			
		||||
		class?: string;
 | 
			
		||||
	}
 | 
			
		||||
type textAreaProps<T extends ValidComponent = 'textarea'> = VoidProps<
 | 
			
		||||
  TextFieldTextAreaProps<T> & {
 | 
			
		||||
    class?: string;
 | 
			
		||||
  }
 | 
			
		||||
>;
 | 
			
		||||
 | 
			
		||||
export const TextArea = <T extends ValidComponent = "textarea">(
 | 
			
		||||
	props: PolymorphicProps<T, textAreaProps<T>>,
 | 
			
		||||
) => {
 | 
			
		||||
	const [local, rest] = splitProps(props as textAreaProps, ["class"]);
 | 
			
		||||
export function TextArea<T extends ValidComponent = 'textarea'>(props: PolymorphicProps<T, textAreaProps<T>>) {
 | 
			
		||||
  const [local, rest] = splitProps(props as textAreaProps, ['class']);
 | 
			
		||||
 | 
			
		||||
	return (
 | 
			
		||||
		<TextFieldPrimitive
 | 
			
		||||
			class={cn(
 | 
			
		||||
				"flex min-h-[60px] w-full rounded-md border border-input bg-inherit px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground focus-visible:(outline-none ring-1.5 ring-ring) disabled:(cursor-not-allowed opacity-50) transition-shadow",
 | 
			
		||||
				local.class,
 | 
			
		||||
			)}
 | 
			
		||||
			{...rest}
 | 
			
		||||
		/>
 | 
			
		||||
	);
 | 
			
		||||
};
 | 
			
		||||
  return (
 | 
			
		||||
    <TextFieldPrimitive
 | 
			
		||||
      class={cn(
 | 
			
		||||
        'flex min-h-[60px] w-full rounded-md border border-input bg-inherit px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground focus-visible:(outline-none ring-1.5 ring-ring) disabled:(cursor-not-allowed opacity-50) transition-shadow',
 | 
			
		||||
        local.class,
 | 
			
		||||
      )}
 | 
			
		||||
      {...rest}
 | 
			
		||||
    />
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,126 +1,116 @@
 | 
			
		||||
import { cn } from "@/modules/ui/utils/cn";
 | 
			
		||||
import type { PolymorphicProps } from "@kobalte/core/polymorphic";
 | 
			
		||||
import type { PolymorphicProps } from '@kobalte/core/polymorphic';
 | 
			
		||||
import type {
 | 
			
		||||
	TextFieldDescriptionProps,
 | 
			
		||||
	TextFieldErrorMessageProps,
 | 
			
		||||
	TextFieldInputProps,
 | 
			
		||||
	TextFieldLabelProps,
 | 
			
		||||
	TextFieldRootProps,
 | 
			
		||||
} from "@kobalte/core/text-field";
 | 
			
		||||
import { TextField as TextFieldPrimitive } from "@kobalte/core/text-field";
 | 
			
		||||
import { cva } from "class-variance-authority";
 | 
			
		||||
import type { ValidComponent, VoidProps } from "solid-js";
 | 
			
		||||
import { splitProps } from "solid-js";
 | 
			
		||||
  TextFieldDescriptionProps,
 | 
			
		||||
  TextFieldErrorMessageProps,
 | 
			
		||||
  TextFieldInputProps,
 | 
			
		||||
  TextFieldLabelProps,
 | 
			
		||||
  TextFieldRootProps,
 | 
			
		||||
} from '@kobalte/core/text-field';
 | 
			
		||||
import type { ValidComponent, VoidProps } from 'solid-js';
 | 
			
		||||
import { cn } from '@/modules/ui/utils/cn';
 | 
			
		||||
import { TextField as TextFieldPrimitive } from '@kobalte/core/text-field';
 | 
			
		||||
import { cva } from 'class-variance-authority';
 | 
			
		||||
import { splitProps } from 'solid-js';
 | 
			
		||||
 | 
			
		||||
type textFieldProps<T extends ValidComponent = "div"> =
 | 
			
		||||
	TextFieldRootProps<T> & {
 | 
			
		||||
		class?: string;
 | 
			
		||||
	};
 | 
			
		||||
type textFieldProps<T extends ValidComponent = 'div'> =
 | 
			
		||||
  TextFieldRootProps<T> & {
 | 
			
		||||
    class?: string;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
export const TextFieldRoot = <T extends ValidComponent = "div">(
 | 
			
		||||
	props: PolymorphicProps<T, textFieldProps<T>>,
 | 
			
		||||
) => {
 | 
			
		||||
	const [local, rest] = splitProps(props as textFieldProps, ["class"]);
 | 
			
		||||
export function TextFieldRoot<T extends ValidComponent = 'div'>(props: PolymorphicProps<T, textFieldProps<T>>) {
 | 
			
		||||
  const [local, rest] = splitProps(props as textFieldProps, ['class']);
 | 
			
		||||
 | 
			
		||||
	return <TextFieldPrimitive class={cn("space-y-1", local.class)} {...rest} />;
 | 
			
		||||
};
 | 
			
		||||
  return <TextFieldPrimitive class={cn('space-y-1', local.class)} {...rest} />;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const textfieldLabel = cva(
 | 
			
		||||
	"text-sm data-[disabled]:(cursor-not-allowed opacity-70) font-medium",
 | 
			
		||||
	{
 | 
			
		||||
		variants: {
 | 
			
		||||
			label: {
 | 
			
		||||
				true: "data-[invalid]:text-destructive",
 | 
			
		||||
			},
 | 
			
		||||
			error: {
 | 
			
		||||
				true: "text-destructive text-xs",
 | 
			
		||||
			},
 | 
			
		||||
			description: {
 | 
			
		||||
				true: "font-normal text-muted-foreground",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		defaultVariants: {
 | 
			
		||||
			label: true,
 | 
			
		||||
		},
 | 
			
		||||
	},
 | 
			
		||||
  'text-sm data-[disabled]:(cursor-not-allowed opacity-70) font-medium',
 | 
			
		||||
  {
 | 
			
		||||
    variants: {
 | 
			
		||||
      label: {
 | 
			
		||||
        true: 'data-[invalid]:text-destructive',
 | 
			
		||||
      },
 | 
			
		||||
      error: {
 | 
			
		||||
        true: 'text-destructive text-xs',
 | 
			
		||||
      },
 | 
			
		||||
      description: {
 | 
			
		||||
        true: 'font-normal text-muted-foreground',
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    defaultVariants: {
 | 
			
		||||
      label: true,
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
type textFieldLabelProps<T extends ValidComponent = "label"> =
 | 
			
		||||
	TextFieldLabelProps<T> & {
 | 
			
		||||
		class?: string;
 | 
			
		||||
	};
 | 
			
		||||
type textFieldLabelProps<T extends ValidComponent = 'label'> =
 | 
			
		||||
  TextFieldLabelProps<T> & {
 | 
			
		||||
    class?: string;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
export const TextFieldLabel = <T extends ValidComponent = "label">(
 | 
			
		||||
	props: PolymorphicProps<T, textFieldLabelProps<T>>,
 | 
			
		||||
) => {
 | 
			
		||||
	const [local, rest] = splitProps(props as textFieldLabelProps, ["class"]);
 | 
			
		||||
export function TextFieldLabel<T extends ValidComponent = 'label'>(props: PolymorphicProps<T, textFieldLabelProps<T>>) {
 | 
			
		||||
  const [local, rest] = splitProps(props as textFieldLabelProps, ['class']);
 | 
			
		||||
 | 
			
		||||
	return (
 | 
			
		||||
		<TextFieldPrimitive.Label
 | 
			
		||||
			class={cn(textfieldLabel(), local.class)}
 | 
			
		||||
			{...rest}
 | 
			
		||||
		/>
 | 
			
		||||
	);
 | 
			
		||||
};
 | 
			
		||||
  return (
 | 
			
		||||
    <TextFieldPrimitive.Label
 | 
			
		||||
      class={cn(textfieldLabel(), local.class)}
 | 
			
		||||
      {...rest}
 | 
			
		||||
    />
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type textFieldErrorMessageProps<T extends ValidComponent = "div"> =
 | 
			
		||||
	TextFieldErrorMessageProps<T> & {
 | 
			
		||||
		class?: string;
 | 
			
		||||
	};
 | 
			
		||||
type textFieldErrorMessageProps<T extends ValidComponent = 'div'> =
 | 
			
		||||
  TextFieldErrorMessageProps<T> & {
 | 
			
		||||
    class?: string;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
export const TextFieldErrorMessage = <T extends ValidComponent = "div">(
 | 
			
		||||
	props: PolymorphicProps<T, textFieldErrorMessageProps<T>>,
 | 
			
		||||
) => {
 | 
			
		||||
	const [local, rest] = splitProps(props as textFieldErrorMessageProps, [
 | 
			
		||||
		"class",
 | 
			
		||||
	]);
 | 
			
		||||
export function TextFieldErrorMessage<T extends ValidComponent = 'div'>(props: PolymorphicProps<T, textFieldErrorMessageProps<T>>) {
 | 
			
		||||
  const [local, rest] = splitProps(props as textFieldErrorMessageProps, [
 | 
			
		||||
    'class',
 | 
			
		||||
  ]);
 | 
			
		||||
 | 
			
		||||
	return (
 | 
			
		||||
		<TextFieldPrimitive.ErrorMessage
 | 
			
		||||
			class={cn(textfieldLabel({ error: true }), local.class)}
 | 
			
		||||
			{...rest}
 | 
			
		||||
		/>
 | 
			
		||||
	);
 | 
			
		||||
};
 | 
			
		||||
  return (
 | 
			
		||||
    <TextFieldPrimitive.ErrorMessage
 | 
			
		||||
      class={cn(textfieldLabel({ error: true }), local.class)}
 | 
			
		||||
      {...rest}
 | 
			
		||||
    />
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type textFieldDescriptionProps<T extends ValidComponent = "div"> =
 | 
			
		||||
	TextFieldDescriptionProps<T> & {
 | 
			
		||||
		class?: string;
 | 
			
		||||
	};
 | 
			
		||||
type textFieldDescriptionProps<T extends ValidComponent = 'div'> =
 | 
			
		||||
  TextFieldDescriptionProps<T> & {
 | 
			
		||||
    class?: string;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
export const TextFieldDescription = <T extends ValidComponent = "div">(
 | 
			
		||||
	props: PolymorphicProps<T, textFieldDescriptionProps<T>>,
 | 
			
		||||
) => {
 | 
			
		||||
	const [local, rest] = splitProps(props as textFieldDescriptionProps, [
 | 
			
		||||
		"class",
 | 
			
		||||
	]);
 | 
			
		||||
export function TextFieldDescription<T extends ValidComponent = 'div'>(props: PolymorphicProps<T, textFieldDescriptionProps<T>>) {
 | 
			
		||||
  const [local, rest] = splitProps(props as textFieldDescriptionProps, [
 | 
			
		||||
    'class',
 | 
			
		||||
  ]);
 | 
			
		||||
 | 
			
		||||
	return (
 | 
			
		||||
		<TextFieldPrimitive.Description
 | 
			
		||||
			class={cn(textfieldLabel({ description: true }), local.class)}
 | 
			
		||||
			{...rest}
 | 
			
		||||
		/>
 | 
			
		||||
	);
 | 
			
		||||
};
 | 
			
		||||
  return (
 | 
			
		||||
    <TextFieldPrimitive.Description
 | 
			
		||||
      class={cn(textfieldLabel({ description: true }), local.class)}
 | 
			
		||||
      {...rest}
 | 
			
		||||
    />
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type textFieldInputProps<T extends ValidComponent = "input"> = VoidProps<
 | 
			
		||||
	TextFieldInputProps<T> & {
 | 
			
		||||
		class?: string;
 | 
			
		||||
	}
 | 
			
		||||
type textFieldInputProps<T extends ValidComponent = 'input'> = VoidProps<
 | 
			
		||||
  TextFieldInputProps<T> & {
 | 
			
		||||
    class?: string;
 | 
			
		||||
  }
 | 
			
		||||
>;
 | 
			
		||||
 | 
			
		||||
export const TextField = <T extends ValidComponent = "input">(
 | 
			
		||||
	props: PolymorphicProps<T, textFieldInputProps<T>>,
 | 
			
		||||
) => {
 | 
			
		||||
	const [local, rest] = splitProps(props as textFieldInputProps, ["class"]);
 | 
			
		||||
export function TextField<T extends ValidComponent = 'input'>(props: PolymorphicProps<T, textFieldInputProps<T>>) {
 | 
			
		||||
  const [local, rest] = splitProps(props as textFieldInputProps, ['class']);
 | 
			
		||||
 | 
			
		||||
	return (
 | 
			
		||||
		<TextFieldPrimitive.Input
 | 
			
		||||
			class={cn(
 | 
			
		||||
				"flex h-9 w-full rounded-md border border-input bg-inherit px-3 py-1 text-sm shadow-sm file:(border-0 bg-transparent text-sm font-medium) placeholder:text-muted-foreground focus-visible:(outline-none ring-1.5 ring-ring) disabled:(cursor-not-allowed opacity-50) transition-shadow",
 | 
			
		||||
				local.class,
 | 
			
		||||
			)}
 | 
			
		||||
			{...rest}
 | 
			
		||||
		/>
 | 
			
		||||
	);
 | 
			
		||||
};
 | 
			
		||||
  return (
 | 
			
		||||
    <TextFieldPrimitive.Input
 | 
			
		||||
      class={cn(
 | 
			
		||||
        'flex h-9 w-full rounded-md border border-input bg-inherit px-3 py-1 text-sm shadow-sm file:(border-0 bg-transparent text-sm font-medium) placeholder:text-muted-foreground focus-visible:(outline-none ring-1.5 ring-ring) disabled:(cursor-not-allowed opacity-50) transition-shadow',
 | 
			
		||||
        local.class,
 | 
			
		||||
      )}
 | 
			
		||||
      {...rest}
 | 
			
		||||
    />
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,11 +1,13 @@
 | 
			
		||||
import type { LocaleKey } from '@/modules/i18n/i18n.types';
 | 
			
		||||
import type { Component, ParentComponent } from 'solid-js';
 | 
			
		||||
import { useCommandPalette } from '@/modules/command-palette/command-palette.provider';
 | 
			
		||||
import { useI18n } from '@/modules/i18n/i18n.provider';
 | 
			
		||||
import { Button } from '@/modules/ui/components/button';
 | 
			
		||||
import { A, useLocation, useNavigate } from '@solidjs/router';
 | 
			
		||||
import { A } from '@solidjs/router';
 | 
			
		||||
import { Badge } from '../components/badge';
 | 
			
		||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger } from '../components/dropdown-menu';
 | 
			
		||||
import { useThemeStore } from '../themes/theme.store';
 | 
			
		||||
import { cn } from '../utils/cn';
 | 
			
		||||
import { socialLinks } from './app.layouts.constants';
 | 
			
		||||
 | 
			
		||||
const ThemeSwitcher: Component = () => {
 | 
			
		||||
  const themeStore = useThemeStore();
 | 
			
		||||
@@ -30,17 +32,7 @@ const ThemeSwitcher: Component = () => {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const LanguageSwitcher: Component = () => {
 | 
			
		||||
  const { t, getLocale, setLocale, locales } = useI18n();
 | 
			
		||||
  const navigate = useNavigate();
 | 
			
		||||
  const location = useLocation();
 | 
			
		||||
 | 
			
		||||
  const changeLocale = (locale: LocaleKey) => {
 | 
			
		||||
    setLocale(locale);
 | 
			
		||||
 | 
			
		||||
    const pathWithoutLocale = location.pathname.split('/').slice(2).join('/');
 | 
			
		||||
    const newPath = [locale, pathWithoutLocale].filter(Boolean).join('/');
 | 
			
		||||
    navigate(`/${newPath}`);
 | 
			
		||||
  };
 | 
			
		||||
  const { t, getLocale, changeLocale, locales } = useI18n();
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <>
 | 
			
		||||
@@ -62,14 +54,24 @@ const LanguageSwitcher: Component = () => {
 | 
			
		||||
export const Navbar: Component = () => {
 | 
			
		||||
  const themeStore = useThemeStore();
 | 
			
		||||
  const { t } = useI18n();
 | 
			
		||||
  const { openCommandPalette } = useCommandPalette();
 | 
			
		||||
  const getIsMacOs = () => navigator?.userAgent?.match(/Macintosh;/);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div class="border-b border-border bg-surface">
 | 
			
		||||
      <div class="flex items-center justify-between px-6 py-3 mx-auto max-w-1200px">
 | 
			
		||||
        <div class="flex items-baseline gap-4">
 | 
			
		||||
        <div class="flex items-center gap-4">
 | 
			
		||||
          <Button variant="link" class="text-xl font-semibold border-b border-transparent hover:no-underline h-auto py-0 px-1 ml--1 rounded-none !transition-border-color-250" as={A} href="/" aria-label="Home">
 | 
			
		||||
            <span class="font-bold text-foreground">IT</span>
 | 
			
		||||
            <span class="text-80% font-extrabold border border-2px leading-none border-current rounded-md px-1 py-0.5 ml-1 text-lime">TOOLS</span>
 | 
			
		||||
            <span class="text-80% font-extrabold border border-2px leading-none border-current rounded-md px-1 py-0.5 ml-1 text-primary">TOOLS</span>
 | 
			
		||||
          </Button>
 | 
			
		||||
 | 
			
		||||
          <Button size="sm" variant="outline" class="bg-card transition flex items-center gap-2 text-muted-foreground" onClick={openCommandPalette}>
 | 
			
		||||
            <div class="i-tabler-search text-base"></div>
 | 
			
		||||
            {t('commandPalette.trigger.search')}
 | 
			
		||||
            <Badge variant="secondary" class="text-muted-foreground text-10px!">
 | 
			
		||||
              {getIsMacOs() ? '⌘ + K' : 'Ctrl + K'}
 | 
			
		||||
            </Badge>
 | 
			
		||||
          </Button>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
@@ -155,6 +157,114 @@ export const Navbar: Component = () => {
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const Footer: Component = () => {
 | 
			
		||||
  const { t, createLocalizedUrl } = useI18n();
 | 
			
		||||
 | 
			
		||||
  const getFooterSections = () => [
 | 
			
		||||
    {
 | 
			
		||||
      title: t('footer.resources.title'),
 | 
			
		||||
      items: [
 | 
			
		||||
        { label: t('footer.resources.all-tools'), to: createLocalizedUrl({ path: '/tools' }) },
 | 
			
		||||
        { label: t('footer.resources.github'), href: 'https://github.com/CorentinTh/it-tools' },
 | 
			
		||||
        { label: t('footer.resources.support'), href: 'https://buymeacoffee.com/cthmsst' },
 | 
			
		||||
        { label: 'Humans.txt', href: '/humans.txt' },
 | 
			
		||||
        { label: t('footer.resources.license'), href: 'https://github.com/CorentinTh/it-tools/blob/main/LICENSE' },
 | 
			
		||||
      ],
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      title: t('footer.support.title'),
 | 
			
		||||
      items: [
 | 
			
		||||
        { label: t('footer.support.report-bug'), href: 'https://github.com/CorentinTh/it-tools/issues/new/choose' },
 | 
			
		||||
        { label: t('footer.support.request-feature'), href: 'https://github.com/CorentinTh/it-tools/issues/new/choose' },
 | 
			
		||||
        { label: t('footer.support.contribute'), href: 'https://github.com/CorentinTh/it-tools/blob/main/CONTRIBUTING.md' },
 | 
			
		||||
        { label: t('footer.support.contact'), href: 'https://github.com/CorentinTh/it-tools/issues/new/choose' },
 | 
			
		||||
      ],
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      title: t('footer.friends.title'),
 | 
			
		||||
      items: [
 | 
			
		||||
        { label: 'Jugly.io', href: 'https://jugly.io' },
 | 
			
		||||
        { label: 'Enclosed.cc', href: 'https://enclosed.cc' },
 | 
			
		||||
      ],
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <footer class="bg-card border-t border-border">
 | 
			
		||||
      <div class="py-12 px-6 mx-auto max-w-1200px">
 | 
			
		||||
 | 
			
		||||
        <div class="flex items-start justify-between flex-col md:flex-row gap-12">
 | 
			
		||||
          <div>
 | 
			
		||||
            <div class="flex items-center gap-2">
 | 
			
		||||
              <A href="/" class="text-2xl font-semibold border-b border-transparent hover:no-underline h-auto py-0 px-1 ml--1 rounded-none !transition-border-color-250 group text-muted-foreground flex items-center gap-1">
 | 
			
		||||
                <span class="font-bold group-hover:text-foreground transition">IT</span>
 | 
			
		||||
                <span class="text-80% font-extrabold border border-2px leading-none border-current rounded-md px-1 py-0.5 ml-1 group-hover:text-primary transition">TOOLS</span>
 | 
			
		||||
              </A>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <div class="flex items-center gap-2 mt-4">
 | 
			
		||||
              {socialLinks.map(({ icon, href, label }) => (
 | 
			
		||||
                <a href={href} target="_blank" rel="noopener noreferrer" class="text-2xl text-muted-foreground hover:text-primary transition" aria-label={label}>
 | 
			
		||||
                  <div class={icon}></div>
 | 
			
		||||
                </a>
 | 
			
		||||
              ))}
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <div class="text-muted-foreground mt-2">
 | 
			
		||||
              Crafted with
 | 
			
		||||
              {' '}
 | 
			
		||||
              <span class="i-tabler-heart inline-block text-base mb--0.5"></span>
 | 
			
		||||
              {' '}
 | 
			
		||||
              by
 | 
			
		||||
              {' '}
 | 
			
		||||
              <a href="https://corentin.tech" target="_blank" rel="noopener" class="hover:text-primary transition">
 | 
			
		||||
                Corentin Thomasset
 | 
			
		||||
              </a>
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
 | 
			
		||||
          <div class="grid grid-cols-1 sm:grid-cols-3 gap-12">
 | 
			
		||||
            {getFooterSections().map(({ title, items }) => (
 | 
			
		||||
              <div>
 | 
			
		||||
                <h4 class="font-semibold text-foreground">{title}</h4>
 | 
			
		||||
                <ul class="mt-4">
 | 
			
		||||
                  {items.map(({ label, to, href }) => (
 | 
			
		||||
                    <li class="mt-1">
 | 
			
		||||
                      {to
 | 
			
		||||
                        ? (
 | 
			
		||||
                            <A href={to} class="text-muted-foreground hover:text-primary transition">
 | 
			
		||||
                              {label}
 | 
			
		||||
                            </A>
 | 
			
		||||
                          )
 | 
			
		||||
                        : (
 | 
			
		||||
                            <a href={href} target="_blank" rel="noopener" class="text-muted-foreground hover:text-primary transition">
 | 
			
		||||
                              {label}
 | 
			
		||||
                            </a>
 | 
			
		||||
                          )}
 | 
			
		||||
                    </li>
 | 
			
		||||
                  ))}
 | 
			
		||||
                </ul>
 | 
			
		||||
              </div>
 | 
			
		||||
            ))}
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div class="text-xs text-muted-foreground border-t border-border pt-4 mt-12">
 | 
			
		||||
          <span>
 | 
			
		||||
            ©
 | 
			
		||||
            {new Date().getFullYear()}
 | 
			
		||||
            {' '}
 | 
			
		||||
            Corentin Thomasset
 | 
			
		||||
          </span>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="text-xs text-foreground opacity-80%">
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </footer>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const AppLayout: ParentComponent = (props) => {
 | 
			
		||||
  return (
 | 
			
		||||
    <div class="flex flex-col h-screen min-h-0">
 | 
			
		||||
@@ -163,6 +273,7 @@ export const AppLayout: ParentComponent = (props) => {
 | 
			
		||||
 | 
			
		||||
      <div class="flex-1 pb-20 ">{props.children}</div>
 | 
			
		||||
 | 
			
		||||
      <Footer />
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										17
									
								
								packages/app/src/modules/ui/layouts/app.layouts.constants.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								packages/app/src/modules/ui/layouts/app.layouts.constants.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
			
		||||
export const socialLinks = [
 | 
			
		||||
  {
 | 
			
		||||
    icon: 'i-tabler-brand-github',
 | 
			
		||||
    href: 'https://github.com/CorentinTh/it-tools',
 | 
			
		||||
    label: 'GitHub',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    icon: 'i-tabler-brand-x',
 | 
			
		||||
    href: 'https://x.com/ittoolsdottech',
 | 
			
		||||
    label: 'X',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    icon: 'i-tabler-coffee',
 | 
			
		||||
    href: 'https://buymeacoffee.com/cthmsst',
 | 
			
		||||
    label: 'Support the project',
 | 
			
		||||
  },
 | 
			
		||||
];
 | 
			
		||||
							
								
								
									
										12
									
								
								packages/app/templates/tools/new/tool.definition.ejs.t
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								packages/app/templates/tools/new/tool.definition.ejs.t
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
			
		||||
---
 | 
			
		||||
to: src/modules/tools/definitions/<%= h.changeCase.param(name) %>/<%= h.changeCase.param(name) %>.tool.ts
 | 
			
		||||
---
 | 
			
		||||
import { defineTool } from '../../tools.models'
 | 
			
		||||
 | 
			
		||||
export const <%= h.changeCase.camel(name) %>Tool = defineTool({
 | 
			
		||||
  slug: '<%= h.changeCase.param(name) %>',
 | 
			
		||||
  entryFile: () => import('./<%= h.changeCase.param(name) %>.page'),
 | 
			
		||||
  icon: 'i-tabler-question-mark',
 | 
			
		||||
  createdAt: new Date('<%= new Date().toISOString().split('T')[0] %>'),
 | 
			
		||||
  dirName: '<%= h.changeCase.param(name) %>',
 | 
			
		||||
})
 | 
			
		||||
							
								
								
									
										4
									
								
								packages/app/templates/tools/new/tool.en.locale.ejs.t
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								packages/app/templates/tools/new/tool.en.locale.ejs.t
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,4 @@
 | 
			
		||||
---
 | 
			
		||||
to: src/modules/tools/definitions/<%= h.changeCase.param(name) %>/locales/en.json
 | 
			
		||||
---
 | 
			
		||||
{}
 | 
			
		||||
@@ -0,0 +1,6 @@
 | 
			
		||||
---
 | 
			
		||||
inject: true
 | 
			
		||||
to: src/modules/tools/tools.registry.ts
 | 
			
		||||
at_line: 0
 | 
			
		||||
---
 | 
			
		||||
import { <%= h.changeCase.camel(name) %>Tool } from './definitions/<%= h.changeCase.param(name) %>/<%= h.changeCase.param(name) %>.tool';
 | 
			
		||||
@@ -0,0 +1,6 @@
 | 
			
		||||
---
 | 
			
		||||
inject: true
 | 
			
		||||
to: src/modules/tools/tools.registry.ts
 | 
			
		||||
before: "^]"
 | 
			
		||||
---
 | 
			
		||||
  <%= h.changeCase.camel(name) %>Tool,
 | 
			
		||||
							
								
								
									
										14
									
								
								packages/app/templates/tools/new/tool.page.ejs.t
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								packages/app/templates/tools/new/tool.page.ejs.t
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
			
		||||
---
 | 
			
		||||
to: src/modules/tools/definitions/<%= h.changeCase.param(name) %>/<%= h.changeCase.param(name) %>.page.tsx
 | 
			
		||||
---
 | 
			
		||||
import type { Component } from 'solid-js';
 | 
			
		||||
 | 
			
		||||
const <%= h.changeCase.pascal(name) %>: Component = () => {
 | 
			
		||||
  return (
 | 
			
		||||
    <div class="mx-auto max-w-1200px p-6">
 | 
			
		||||
      <h1><%= h.changeCase.title(name) %></h1>
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default <%= h.changeCase.pascal(name) %>;
 | 
			
		||||
@@ -8,6 +8,7 @@ import {
 | 
			
		||||
} from 'unocss';
 | 
			
		||||
import presetAnimations from 'unocss-preset-animations';
 | 
			
		||||
import { toolDefinitions } from './src/modules/tools/tools.registry';
 | 
			
		||||
import { socialLinks } from './src/modules/ui/layouts/app.layouts.constants';
 | 
			
		||||
 | 
			
		||||
export default defineConfig({
 | 
			
		||||
  presets: [
 | 
			
		||||
@@ -111,5 +112,9 @@ export default defineConfig({
 | 
			
		||||
  },
 | 
			
		||||
  safelist: [
 | 
			
		||||
    ...toolDefinitions.map(tool => tool.icon),
 | 
			
		||||
    ...socialLinks.map(({ icon }) => icon),
 | 
			
		||||
  ],
 | 
			
		||||
  shortcuts: {
 | 
			
		||||
    'i-logo': 'i-tabler-terminal',
 | 
			
		||||
  },
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1596
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1596
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
		Reference in New Issue
	
	Block a user