diff --git a/.eslintignore b/.eslintignore
deleted file mode 100644
index 34eeb9f..0000000
--- a/.eslintignore
+++ /dev/null
@@ -1,8 +0,0 @@
-doc
-mock-shop/**
-public
-nginx
-node_modules
-tests/mock-server/public
-tests/e2e/dist
-tests/e2e
\ No newline at end of file
diff --git a/.eslintrc.js b/.eslintrc.js
deleted file mode 100644
index 852e02a..0000000
--- a/.eslintrc.js
+++ /dev/null
@@ -1,127 +0,0 @@
-/* eslint-disable */
-module.exports = {
- root: true,
- parser: 'vue-eslint-parser',
- parserOptions: {
- ecmaVersion: 2022, // Allows for the parsing of modern ECMAScript features
- sourceType: 'module', // Allows for the use of imports
- parser: '@typescript-eslint/parser',
- ecmaFeatures: {
- jsx: true // Allows for the parsing of JSX
- },
- },
- extends: [
- 'materya',
- 'materya/typescript',
- "plugin:vue/vue3-recommended",
- "eslint:recommended",
- "@vue/typescript/recommended"
- ],
- env: {
- node: true,
- },
- settings: {
- 'import/resolver': {
- node: {
- extensions: [".js", ".jsx", ".ts", ".tsx", '.vue'],
- paths: ['@types'],
- },
- },
- },
- rules: {
- '@typescript-eslint/naming-convention': 'off',
- 'max-len': ['error', {
- code: 79,
- ignoreComments: true,
- ignoreTemplateLiterals: true,
- ignoreStrings: true,
- ignoreUrls: true,
- }],
- semi: ['error', 'never'],
- 'import/extensions': 'off',
- 'import/no-cycle': 'off',
- 'import/named': 'off', // Does not work well with TS
- camelcase: ['off', { ignoreDestructuring: true }],
- 'no-unused-expressions': ['off', { allowShortCircuit: true }],
- 'arrow-parens': ['error', 'as-needed', { requireForBlockBody: false }],
- 'space-before-function-paren': ['off'],
- '@typescript-eslint/no-unused-vars': ['error', {
- varsIgnorePattern: '^\\\$',
- }],
- 'object-curly-newline': ['error', {
- ObjectExpression: { consistent: true },
- }],
- 'no-plusplus': ['error', { allowForLoopAfterthoughts: true }],
- 'template-tag-spacing': ['error', 'always'],
- 'no-console': process.env.NODE_ENV === 'dev' ? 'off' : 'error',
- 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
- 'comma-dangle': [2, 'always-multiline'],
- 'import/no-unresolved': ['error', {
- ignore: ['^@/@types/cresh-ui$', '^@', 'config'],
- }],
- 'lines-between-class-members': ['warn', 'always', { exceptAfterSingleLine: true }],
- '@typescript-eslint/member-delimiter-style': ['error', {
- "multiline": {
- 'delimiter': 'none',
- 'requireLast': false
- },
- 'singleline': {
- 'delimiter': 'semi',
- 'requireLast': false
- }
- }],
- 'vue/component-name-in-template-casing': ['error', 'PascalCase', {
- 'registeredComponentsOnly': true,
- 'ignores': [],
- }],
- 'vue/html-indent': 'off',
- 'vue/attributes-order': 'off',
- 'vue/no-boolean-default': ['error', 'default-false'],
- 'vue/no-reserved-component-names': ['error'],
- 'vue/padding-line-between-blocks': ['error', 'always'],
- 'vue/require-name-property': ['error'],
- 'vue/one-component-per-file': ['error'],
- 'vue/custom-event-name-casing': ['error'],
- 'vue/no-arrow-functions-in-watch': ['error'],
- 'vue/no-custom-modifiers-on-v-model': ['error'],
- 'vue/no-dupe-v-else-if': ['error'],
- 'vue/no-multiple-template-root': ['off'],
- 'no-undef': 'off', // @typescript-eslint/no-undef
- 'no-param-reassign': ['error', { "props": false }],
- 'no-shadow': 'off', // @typescript-eslint/no-shadow
- 'vue/custom-event-name-casing': 'off',
- 'no-nested-ternary': ['warn'],
- 'vue/max-len': ['off'], // max-len exists
- 'import/no-extraneous-dependencies':[
- 'error',
- {
- 'devDependencies':[
- '**/*.test.{js,ts,tsx}',
- '**/*.spec.{js,ts,tsx}',
- '**/cypress.config.{js,ts}',
- '**/cypress/**',
- '**/tests/**',
- '**/vite.config.{js,ts}',
- '**/vitest.config.{js,ts}',
- '**/*.cy.{js,ts,tsx}',
- ]
- }
- ]
- },
- overrides: [
- {
- files: [
- '**/__tests__/*.{j,t}s?(x)',
- '**/tests/unit/**/*.spec.{j,t}s?(x)',
- '**/tests/unit/**/*.test.{j,t}s?(x)'
- ],
- env: {
- mocha: true
- }
- }
- ],
- ignorePatterns: [
- 'src/pages/*', // pages for examples purposes only
- 'cypress.config.ts',
- ]
-}
diff --git a/.gitignore b/.gitignore
index 5c0d32c..a5e58b2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -50,3 +50,12 @@
# Artifacts
.DS_Store
+
+!.storybook
+
+!API_CONVENTIONS.md
+!CONTRIBUTING.md
+!CODE_OF_CONDUCT.md
+!SUPPORT.md
+!SECURITY.md
+!FUNDING.yml
\ No newline at end of file
diff --git a/.storybook/locales/cn.json b/.storybook/locales/cn.json
new file mode 100644
index 0000000..a655b57
--- /dev/null
+++ b/.storybook/locales/cn.json
@@ -0,0 +1,28 @@
+{
+ "docs": {
+ "home": {
+ "title": "Cresh UI",
+ "subtitle": "现代 Vue 3 组件库"
+ },
+ "getting_started": {
+ "title": "开始使用",
+ "description": "了解如何在您的项目中安装和使用 Cresh UI"
+ },
+ "components": {
+ "title": "组件",
+ "description": "探索所有可用组件"
+ },
+ "theme": {
+ "title": "设计系统",
+ "description": "颜色、排版和设计令牌"
+ },
+ "language_setup": {
+ "title": "国际化 (i18n)",
+ "description": "多语言支持和安装指南"
+ },
+ "i18n_stories": {
+ "title": "i18n 展示",
+ "description": "语言切换器的交互式演示"
+ }
+ }
+}
diff --git a/.storybook/locales/de.json b/.storybook/locales/de.json
new file mode 100644
index 0000000..06075ed
--- /dev/null
+++ b/.storybook/locales/de.json
@@ -0,0 +1,28 @@
+{
+ "docs": {
+ "home": {
+ "title": "Cresh UI",
+ "subtitle": "Moderne Vue 3 Komponentenbibliothek"
+ },
+ "getting_started": {
+ "title": "Erste Schritte",
+ "description": "Erfahren Sie, wie Sie Cresh UI in Ihrem Projekt installieren und verwenden"
+ },
+ "components": {
+ "title": "Komponenten",
+ "description": "Erkunden Sie alle verfügbaren Komponenten"
+ },
+ "theme": {
+ "title": "Designsystem",
+ "description": "Farben, Typographie und Design-Tokens"
+ },
+ "language_setup": {
+ "title": "Internationalisierung (i18n)",
+ "description": "Mehrsprachiger Support und Installationsanleitung"
+ },
+ "i18n_stories": {
+ "title": "i18n Präsentation",
+ "description": "Interaktive Demonstration des Sprachumschalters"
+ }
+ }
+}
diff --git a/.storybook/locales/en.json b/.storybook/locales/en.json
new file mode 100644
index 0000000..5c333c6
--- /dev/null
+++ b/.storybook/locales/en.json
@@ -0,0 +1,28 @@
+{
+ "docs": {
+ "home": {
+ "title": "Cresh UI",
+ "subtitle": "Modern Vue 3 Component Library"
+ },
+ "getting_started": {
+ "title": "Getting Started",
+ "description": "Learn how to install and use Cresh UI in your project"
+ },
+ "components": {
+ "title": "Components",
+ "description": "Explore all available components"
+ },
+ "theme": {
+ "title": "Design System",
+ "description": "Colors, typography, and design tokens"
+ },
+ "language_setup": {
+ "title": "Internationalization (i18n)",
+ "description": "Multi-language support and setup guide"
+ },
+ "i18n_stories": {
+ "title": "i18n Showcase",
+ "description": "Interactive language switcher demonstration"
+ }
+ }
+}
diff --git a/.storybook/locales/es.json b/.storybook/locales/es.json
new file mode 100644
index 0000000..6afaf1b
--- /dev/null
+++ b/.storybook/locales/es.json
@@ -0,0 +1,28 @@
+{
+ "docs": {
+ "home": {
+ "title": "Cresh UI",
+ "subtitle": "Biblioteca moderna de componentes Vue 3"
+ },
+ "getting_started": {
+ "title": "Primeros pasos",
+ "description": "Aprenda a instalar y usar Cresh UI en su proyecto"
+ },
+ "components": {
+ "title": "Componentes",
+ "description": "Explore todos los componentes disponibles"
+ },
+ "theme": {
+ "title": "Sistema de diseño",
+ "description": "Colores, tipografía y tokens de diseño"
+ },
+ "language_setup": {
+ "title": "Internacionalización (i18n)",
+ "description": "Soporte multilingüe y guía de instalación"
+ },
+ "i18n_stories": {
+ "title": "Demostración i18n",
+ "description": "Demostración interactiva del selector de idiomas"
+ }
+ }
+}
diff --git a/.storybook/locales/fr.json b/.storybook/locales/fr.json
new file mode 100644
index 0000000..93c48fe
--- /dev/null
+++ b/.storybook/locales/fr.json
@@ -0,0 +1,28 @@
+{
+ "docs": {
+ "home": {
+ "title": "Cresh UI",
+ "subtitle": "Bibliothèque de composants Vue 3 moderne"
+ },
+ "getting_started": {
+ "title": "Commencer",
+ "description": "Apprenez à installer et utiliser Cresh UI dans votre projet"
+ },
+ "components": {
+ "title": "Composants",
+ "description": "Découvrez tous les composants disponibles"
+ },
+ "theme": {
+ "title": "Système de design",
+ "description": "Couleurs, typographie et tokens de design"
+ },
+ "language_setup": {
+ "title": "Internationalisation (i18n)",
+ "description": "Guide d'installation et support multilingue"
+ },
+ "i18n_stories": {
+ "title": "Démonstration i18n",
+ "description": "Démonstration interactive du sélecteur de langue"
+ }
+ }
+}
diff --git a/.storybook/locales/it.json b/.storybook/locales/it.json
new file mode 100644
index 0000000..2eedfc1
--- /dev/null
+++ b/.storybook/locales/it.json
@@ -0,0 +1,28 @@
+{
+ "docs": {
+ "home": {
+ "title": "Cresh UI",
+ "subtitle": "Moderna libreria di componenti Vue 3"
+ },
+ "getting_started": {
+ "title": "Iniziare",
+ "description": "Scopri come installare e utilizzare Cresh UI nel tuo progetto"
+ },
+ "components": {
+ "title": "Componenti",
+ "description": "Esplora tutti i componenti disponibili"
+ },
+ "theme": {
+ "title": "Sistema di design",
+ "description": "Colori, tipografia e token di design"
+ },
+ "language_setup": {
+ "title": "Internazionalizzazione (i18n)",
+ "description": "Supporto multilingue e guida all'installazione"
+ },
+ "i18n_stories": {
+ "title": "Dimostrazioni i18n",
+ "description": "Dimostrazione interattiva del selettore di lingua"
+ }
+ }
+}
diff --git a/.storybook/main.ts b/.storybook/main.ts
new file mode 100644
index 0000000..8392cd1
--- /dev/null
+++ b/.storybook/main.ts
@@ -0,0 +1,43 @@
+import type { StorybookConfig } from '@storybook/vue3-vite'
+import path from 'node:path'
+import { fileURLToPath } from 'node:url'
+
+const __dirname = path.dirname(fileURLToPath(import.meta.url))
+
+const config: StorybookConfig = {
+ stories: ['../src/**/*.stories.@(ts|tsx|js|jsx)'],
+ addons: ['@storybook/addon-links', '@storybook/addon-docs'],
+
+ framework: {
+ name: '@storybook/vue3-vite',
+ options: {
+ vueDocgenOptions: {
+ alias: {
+ '@': new URL('../src', import.meta.url).pathname,
+ },
+ },
+ },
+ },
+
+ viteFinal: async config => ({
+ ...config,
+ css: {
+ preprocessorOptions: {
+ scss: {
+ loadPaths: [path.resolve(__dirname, '../src/styles/preprocessor')],
+ additionalData: `
+ @import "fonts";
+ @import "break-points";
+ @import "computeds";
+ @import "utils";
+ @import "default-colors";
+ `,
+ quietDeps: true,
+ silenceDeprecations: ['legacy-js-api', 'import'],
+ },
+ },
+ },
+ }),
+}
+
+export default config
diff --git a/.storybook/manager-head.html b/.storybook/manager-head.html
new file mode 100644
index 0000000..8aa9184
--- /dev/null
+++ b/.storybook/manager-head.html
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.storybook/manager.ts b/.storybook/manager.ts
new file mode 100644
index 0000000..d613602
--- /dev/null
+++ b/.storybook/manager.ts
@@ -0,0 +1,6 @@
+import { addons } from 'storybook/manager-api'
+
+// Simple Storybook manager configuration
+addons.setConfig({
+ panelPosition: 'bottom',
+})
diff --git a/.storybook/preview-head.html b/.storybook/preview-head.html
new file mode 100644
index 0000000..32daa73
--- /dev/null
+++ b/.storybook/preview-head.html
@@ -0,0 +1,100 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.storybook/preview-localization.ts b/.storybook/preview-localization.ts
new file mode 100644
index 0000000..b8bdbc0
--- /dev/null
+++ b/.storybook/preview-localization.ts
@@ -0,0 +1,479 @@
+type StorybookLocale = 'en' | 'fr' | 'de' | 'es' | 'it' | 'cn'
+
+type TranslationDictionary = Record, Record>
+
+const translations: TranslationDictionary = {
+ de: {
+ Name: 'Name',
+ Description: 'Beschreibung',
+ Default: 'Standard',
+ Control: 'Steuerung',
+ Props: 'Props',
+ Events: 'Ereignisse',
+ Slots: 'Slots',
+ 'Set string': 'Text setzen',
+ 'Set boolean': 'Booleschen Wert setzen',
+ 'Set number': 'Zahl setzen',
+ 'Set object': 'Objekt setzen',
+ 'Set other': 'Wert setzen',
+ 'Show code': 'Code anzeigen',
+ 'Checked state (v-model binding)': 'Ausgewählter Zustand (v-model-Bindung)',
+ 'Label text': 'Beschriftungstext',
+ 'Input name attribute': 'Name-Attribut des Eingabefelds',
+ 'Disable the checkbox': 'Kontrollkästchen deaktivieren',
+ 'The input value (v-model binding)': 'Eingabewert (v-model-Bindung)',
+ 'Label for the input field': 'Beschriftung für das Eingabefeld',
+ 'Placeholder text': 'Platzhaltertext',
+ 'Input type': 'Eingabetyp',
+ 'Disable the input': 'Eingabe deaktivieren',
+ 'Selected value (v-model binding)': 'Ausgewählter Wert (v-model-Bindung)',
+ 'Label for the select': 'Beschriftung für die Auswahl',
+ 'Disable the select': 'Auswahl deaktivieren',
+ 'Disable the radio button': 'Optionsfeld deaktivieren',
+ 'The value of this radio option': 'Wert dieser Radio-Option',
+ 'Disable the switch': 'Schalter deaktivieren',
+ 'Switch state (v-model binding)': 'Schalterzustand (v-model-Bindung)',
+ 'Switch label': 'Schalterbeschriftung',
+ 'Button variant/style': 'Button-Variante/Stil',
+ 'Button size': 'Button-Größe',
+ 'Disable the button': 'Button deaktivieren',
+ 'Show loading state': 'Ladezustand anzeigen',
+ 'Progress value (0-100)': 'Fortschrittswert (0-100)',
+ 'Progress bar color': 'Farbe der Fortschrittsleiste',
+ 'Indeterminate loading state': 'Unbestimmter Ladezustand',
+ 'Image source URL': 'Bildquell-URL',
+ 'Alternative text for accessibility': 'Alternativtext für Barrierefreiheit',
+ 'Tooltip title on hover': 'Tooltip-Titel beim Hover',
+ 'Fallback image URL for errors': 'Fallback-Bild-URL bei Fehlern',
+ 'Error message text': 'Fehlermeldungstext',
+ 'Show error message text': 'Fehlermeldung anzeigen',
+ 'Image width in pixels': 'Bildbreite in Pixeln',
+ 'Image height in pixels': 'Bildhöhe in Pixeln',
+ 'Aspect ratio preset': 'Seitenverhältnis-Voreinstellung',
+ 'Enable lazy loading': 'Lazy Loading aktivieren',
+ 'Responsive image srcset': 'Responsives Bild-Set',
+ 'Array of items with key and value': 'Array von Einträgen mit Schlüssel und Wert',
+ 'Dropdown open/closed state': 'Dropdown geöffnet/geschlossen',
+ 'Show chevron icon': 'Chevron-Symbol anzeigen',
+ 'Pre-selected item': 'Vorab ausgewählter Eintrag',
+ 'Alert type': 'Alarmtyp',
+ 'Alert title': 'Alarmtitel',
+ 'Show close button': 'Schließen-Schaltfläche anzeigen',
+ 'Error message (string, array, or object with message property)': 'Fehlermeldung (String, Array oder Objekt mit Nachricht)',
+ 'Hide the error icon': 'Fehlersymbol ausblenden',
+ 'Empty state message': 'Leerzustandsnachricht',
+ 'Icon name from icon library': 'Icon-Name aus der Bibliothek',
+ 'Card title': 'Kartentitel',
+ 'Show card border': 'Kartenrahmen anzeigen',
+ 'Show card shadow': 'Kartenschatten anzeigen',
+ 'Show/hide modal': 'Modal anzeigen/ausblenden',
+ 'Modal title': 'Modaltitel',
+ 'Prevent closing on background click': 'Schließen bei Hintergrundklick verhindern',
+ 'Modal width in pixels': 'Modalbreite in Pixeln',
+ 'Hide footer with action buttons': 'Footer mit Aktionsbuttons ausblenden',
+ 'Document label': 'Dokumentbezeichnung',
+ 'Document link/URL': 'Dokumentlink/URL',
+ 'Approval status (undefined, true, or false)': 'Freigabestatus (undefined, true oder false)',
+ 'Available actions: upload, validate, download, read': 'Verfügbare Aktionen: hochladen, validieren, herunterladen, lesen',
+ 'Show/hide the document viewer modal': 'Dokumentenanzeige-Modal anzeigen/ausblenden',
+ 'Document to display (label, link, extension)': 'Anzuzeigendes Dokument (Bezeichnung, Link, Erweiterung)',
+ 'Hide navigation arrows': 'Navigationspfeile ausblenden',
+ 'Hide left arrow': 'Linken Pfeil ausblenden',
+ 'Hide right arrow': 'Rechten Pfeil ausblenden',
+ 'Maximum file size in KB (default: 2048)': 'Maximale Dateigröße in KB (Standard: 2048)',
+ 'Array of valid MIME types': 'Array gültiger MIME-Typen',
+ 'Show uploaded filename': 'Hochgeladenen Dateinamen anzeigen',
+ 'Drawer placement position': 'Position des Drawers',
+ 'Drawer width in pixels (for left/right placement)': 'Drawer-Breite in Pixeln (für links/rechts)',
+ 'Drawer height in pixels (for top/bottom placement)': 'Drawer-Höhe in Pixeln (für oben/unten)',
+ 'Mobile breakpoint width to auto-close drawer': 'Mobiler Breakpoint zum automatischen Schließen des Drawers',
+ 'Whether the drawer is open': 'Ob der Drawer geöffnet ist',
+ 'Array of step items': 'Array von Schritten',
+ 'Allow clicking on steps to navigate': 'Klicken auf Schritte zur Navigation erlauben',
+ 'Currently selected step index': 'Aktuell ausgewählter Schrittindex',
+ 'Array of step items with state': 'Array von Schritten mit Status',
+ 'Default date filter': 'Standard-Datumsfilter',
+ 'Date range array [startDate, endDate]': 'Datumsbereich-Array [Startdatum, Enddatum]',
+ 'Which input initiated the selection': 'Welches Feld die Auswahl gestartet hat',
+ 'Allow navigation between steps': 'Navigation zwischen Schritten erlauben',
+ 'Menu title': 'Menütitel',
+ 'Currently selected menu item': 'Aktuell ausgewählter Menüpunkt',
+ 'Divider direction': 'Ausrichtung des Trenners',
+ 'Optional text to display': 'Optional anzuzeigender Text',
+ 'Tag color/type': 'Tag-Farbe/-Typ',
+ 'Spinner size': 'Spinner-Größe',
+ 'Spinner color': 'Spinner-Farbe',
+ 'Make tabs read-only': 'Tabs schreibgeschützt machen',
+ 'Accept terms and conditions': 'Allgemeine Geschäftsbedingungen akzeptieren',
+ 'Checked:': 'Aktiviert:',
+ 'This option is disabled': 'Diese Option ist deaktiviert',
+ 'Disabled and checked': 'Deaktiviert und aktiviert',
+ 'Full Name': 'Vollständiger Name',
+ 'Enter your full name': 'Geben Sie Ihren vollständigen Namen ein',
+ 'Value:': 'Wert:',
+ Email: 'E-Mail',
+ 'Enter your email': 'Geben Sie Ihre E-Mail ein',
+ Password: 'Passwort',
+ 'Enter your password': 'Geben Sie Ihr Passwort ein',
+ 'Disabled Input': 'Deaktivierte Eingabe',
+ 'Input with Error': 'Eingabe mit Fehler',
+ 'Primary Button': 'Primärer Button',
+ 'Secondary Button': 'Sekundärer Button',
+ Delete: 'Löschen',
+ 'Disabled Button': 'Deaktivierter Button',
+ 'Loading...': 'Lädt...',
+ Small: 'Klein',
+ 'Large Button': 'Großer Button',
+ 'Project A - 30%': 'Projekt A - 30 %',
+ 'Project B - 60%': 'Projekt B - 60 %',
+ 'Project C - 100%': 'Projekt C - 100 %',
+ 'Loading - Indeterminate': 'Ladevorgang - Unbestimmt',
+ },
+ fr: {
+ Description: 'Description',
+ Default: 'Defaut',
+ Control: 'Controle',
+ Props: 'Props',
+ Events: 'Evenements',
+ Slots: 'Slots',
+ 'Set string': 'Definir une chaine',
+ 'Set boolean': 'Definir un booleen',
+ 'Set number': 'Definir un nombre',
+ 'Set object': 'Definir un objet',
+ 'Set other': 'Definir une valeur',
+ 'Show code': 'Afficher le code',
+ 'Checked state (v-model binding)': 'Etat coche (liaison v-model)',
+ 'Label text': 'Texte du libelle',
+ 'Input name attribute': 'Attribut name du champ',
+ 'Disable the checkbox': 'Desactiver la case a cocher',
+ 'The input value (v-model binding)': 'Valeur du champ (liaison v-model)',
+ 'Label for the input field': 'Libelle du champ de saisie',
+ 'Placeholder text': 'Texte du placeholder',
+ 'Input type': 'Type de champ',
+ 'Disable the input': 'Desactiver le champ',
+ 'Selected value (v-model binding)': 'Valeur selectionnee (liaison v-model)',
+ 'Label for the select': 'Libelle de la liste',
+ 'Disable the select': 'Desactiver la liste',
+ 'Disable the radio button': 'Desactiver le bouton radio',
+ 'Button variant/style': 'Variante/style du bouton',
+ 'Button size': 'Taille du bouton',
+ 'Disable the button': 'Desactiver le bouton',
+ 'Show loading state': 'Afficher l etat de chargement',
+ 'Progress value (0-100)': 'Valeur de progression (0-100)',
+ 'Progress bar color': 'Couleur de la barre de progression',
+ 'Indeterminate loading state': 'Etat de chargement indetermine',
+ 'Alert type': 'Type d alerte',
+ 'Alert title': 'Titre de l alerte',
+ 'Show close button': 'Afficher le bouton de fermeture',
+ 'Accept terms and conditions': 'Accepter les termes et conditions',
+ 'Checked:': 'Coche :',
+ 'This option is disabled': 'Cette option est desactivee',
+ 'Disabled and checked': 'Desactivee et cochee',
+ 'Full Name': 'Nom complet',
+ 'Enter your full name': 'Saisissez votre nom complet',
+ 'Value:': 'Valeur :',
+ Email: 'E-mail',
+ 'Enter your email': 'Saisissez votre e-mail',
+ Password: 'Mot de passe',
+ 'Enter your password': 'Saisissez votre mot de passe',
+ 'Disabled Input': 'Champ desactive',
+ 'Input with Error': 'Champ avec erreur',
+ 'Primary Button': 'Bouton principal',
+ 'Secondary Button': 'Bouton secondaire',
+ Delete: 'Supprimer',
+ 'Disabled Button': 'Bouton desactive',
+ 'Loading...': 'Chargement...',
+ Small: 'Petit',
+ 'Large Button': 'Grand bouton',
+ 'Project A - 30%': 'Projet A - 30 %',
+ 'Project B - 60%': 'Projet B - 60 %',
+ 'Project C - 100%': 'Projet C - 100 %',
+ 'Loading - Indeterminate': 'Chargement - Indetermine',
+ },
+ es: {
+ Description: 'Descripcion',
+ Default: 'Predeterminado',
+ Control: 'Control',
+ Props: 'Props',
+ Events: 'Eventos',
+ Slots: 'Slots',
+ 'Set string': 'Definir texto',
+ 'Set boolean': 'Definir booleano',
+ 'Set number': 'Definir numero',
+ 'Set object': 'Definir objeto',
+ 'Set other': 'Definir valor',
+ 'Show code': 'Mostrar codigo',
+ 'Checked state (v-model binding)': 'Estado marcado (v-model)',
+ 'Label text': 'Texto de la etiqueta',
+ 'Input name attribute': 'Atributo name del campo',
+ 'Disable the checkbox': 'Desactivar la casilla',
+ 'The input value (v-model binding)': 'Valor del campo (v-model)',
+ 'Label for the input field': 'Etiqueta del campo',
+ 'Placeholder text': 'Texto de placeholder',
+ 'Input type': 'Tipo de entrada',
+ 'Disable the input': 'Desactivar la entrada',
+ 'Selected value (v-model binding)': 'Valor seleccionado (v-model)',
+ 'Label for the select': 'Etiqueta del selector',
+ 'Disable the select': 'Desactivar el selector',
+ 'Disable the radio button': 'Desactivar el boton de opcion',
+ 'Button variant/style': 'Variante/estilo del boton',
+ 'Button size': 'Tamano del boton',
+ 'Disable the button': 'Desactivar el boton',
+ 'Show loading state': 'Mostrar estado de carga',
+ 'Progress value (0-100)': 'Valor de progreso (0-100)',
+ 'Progress bar color': 'Color de la barra de progreso',
+ 'Indeterminate loading state': 'Estado de carga indeterminado',
+ 'Accept terms and conditions': 'Aceptar terminos y condiciones',
+ 'Checked:': 'Marcado:',
+ 'This option is disabled': 'Esta opcion esta desactivada',
+ 'Disabled and checked': 'Desactivado y marcado',
+ 'Full Name': 'Nombre completo',
+ 'Enter your full name': 'Introduzca su nombre completo',
+ 'Value:': 'Valor:',
+ Email: 'Correo electronico',
+ 'Enter your email': 'Introduzca su correo electronico',
+ Password: 'Contrasena',
+ 'Enter your password': 'Introduzca su contrasena',
+ 'Disabled Input': 'Entrada desactivada',
+ 'Input with Error': 'Entrada con error',
+ 'Primary Button': 'Boton principal',
+ 'Secondary Button': 'Boton secundario',
+ Delete: 'Eliminar',
+ 'Disabled Button': 'Boton desactivado',
+ 'Loading...': 'Cargando...',
+ Small: 'Pequeno',
+ 'Large Button': 'Boton grande',
+ },
+ it: {
+ Description: 'Descrizione',
+ Default: 'Predefinito',
+ Control: 'Controllo',
+ Props: 'Props',
+ Events: 'Eventi',
+ Slots: 'Slots',
+ 'Set string': 'Imposta testo',
+ 'Set boolean': 'Imposta booleano',
+ 'Set number': 'Imposta numero',
+ 'Set object': 'Imposta oggetto',
+ 'Set other': 'Imposta valore',
+ 'Show code': 'Mostra codice',
+ 'Checked state (v-model binding)': 'Stato selezionato (binding v-model)',
+ 'Label text': 'Testo etichetta',
+ 'Input name attribute': 'Attributo name del campo',
+ 'Disable the checkbox': 'Disabilita la checkbox',
+ 'The input value (v-model binding)': 'Valore input (binding v-model)',
+ 'Label for the input field': 'Etichetta del campo',
+ 'Placeholder text': 'Testo segnaposto',
+ 'Input type': 'Tipo di input',
+ 'Disable the input': 'Disabilita l input',
+ 'Selected value (v-model binding)': 'Valore selezionato (binding v-model)',
+ 'Label for the select': 'Etichetta della select',
+ 'Disable the select': 'Disabilita la select',
+ 'Disable the radio button': 'Disabilita il pulsante radio',
+ 'Button variant/style': 'Variante/stile del pulsante',
+ 'Button size': 'Dimensione del pulsante',
+ 'Disable the button': 'Disabilita il pulsante',
+ 'Show loading state': 'Mostra stato di caricamento',
+ 'Progress value (0-100)': 'Valore di avanzamento (0-100)',
+ 'Progress bar color': 'Colore della barra di avanzamento',
+ 'Indeterminate loading state': 'Stato di caricamento indeterminato',
+ 'Accept terms and conditions': 'Accetta termini e condizioni',
+ 'Checked:': 'Selezionato:',
+ 'This option is disabled': 'Questa opzione e disabilitata',
+ 'Disabled and checked': 'Disabilitato e selezionato',
+ 'Full Name': 'Nome completo',
+ 'Enter your full name': 'Inserisci il tuo nome completo',
+ 'Value:': 'Valore:',
+ Email: 'Email',
+ 'Enter your email': 'Inserisci la tua email',
+ Password: 'Password',
+ 'Enter your password': 'Inserisci la tua password',
+ 'Disabled Input': 'Input disabilitato',
+ 'Input with Error': 'Input con errore',
+ 'Primary Button': 'Pulsante primario',
+ 'Secondary Button': 'Pulsante secondario',
+ Delete: 'Elimina',
+ 'Disabled Button': 'Pulsante disabilitato',
+ 'Loading...': 'Caricamento...',
+ Small: 'Piccolo',
+ 'Large Button': 'Pulsante grande',
+ },
+ cn: {
+ Description: '描述',
+ Default: '默认值',
+ Control: '控件',
+ Props: '属性',
+ Events: '事件',
+ Slots: '插槽',
+ 'Set string': '设置文本',
+ 'Set boolean': '设置布尔值',
+ 'Set number': '设置数字',
+ 'Set object': '设置对象',
+ 'Set other': '设置值',
+ 'Show code': '显示代码',
+ 'Checked state (v-model binding)': '选中状态(v-model 绑定)',
+ 'Label text': '标签文本',
+ 'Input name attribute': '输入框 name 属性',
+ 'Disable the checkbox': '禁用复选框',
+ 'The input value (v-model binding)': '输入值(v-model 绑定)',
+ 'Label for the input field': '输入框标签',
+ 'Placeholder text': '占位文本',
+ 'Input type': '输入类型',
+ 'Disable the input': '禁用输入框',
+ 'Selected value (v-model binding)': '选中值(v-model 绑定)',
+ 'Label for the select': '选择框标签',
+ 'Disable the select': '禁用选择框',
+ 'Disable the radio button': '禁用单选按钮',
+ 'Button variant/style': '按钮变体/样式',
+ 'Button size': '按钮尺寸',
+ 'Disable the button': '禁用按钮',
+ 'Show loading state': '显示加载状态',
+ 'Progress value (0-100)': '进度值(0-100)',
+ 'Progress bar color': '进度条颜色',
+ 'Indeterminate loading state': '不确定加载状态',
+ 'Accept terms and conditions': '接受条款和条件',
+ 'Checked:': '选中:',
+ 'This option is disabled': '该选项已禁用',
+ 'Disabled and checked': '已禁用且已选中',
+ 'Full Name': '姓名',
+ 'Enter your full name': '请输入您的姓名',
+ 'Value:': '值:',
+ Email: '邮箱',
+ 'Enter your email': '请输入您的邮箱',
+ Password: '密码',
+ 'Enter your password': '请输入您的密码',
+ 'Disabled Input': '禁用输入框',
+ 'Input with Error': '带错误的输入框',
+ 'Primary Button': '主按钮',
+ 'Secondary Button': '次按钮',
+ Delete: '删除',
+ 'Disabled Button': '禁用按钮',
+ 'Loading...': '加载中...',
+ Small: '小',
+ 'Large Button': '大按钮',
+ },
+}
+
+const ignoredTags = new Set(['CODE', 'PRE', 'SCRIPT', 'STYLE'])
+const textSources = new WeakMap()
+const attributeSources = new WeakMap>()
+
+let observer: MutationObserver | null = null
+let currentLocale: StorybookLocale = 'en'
+let isApplying = false
+
+function translateValue(input: string, locale: StorybookLocale) {
+ if (locale === 'en') {
+ return input
+ }
+
+ const dictionary = translations[locale]
+
+ return Object.entries(dictionary)
+ .sort((left, right) => right[0].length - left[0].length)
+ .reduce((value, [source, target]) => value.split(source).join(target), input)
+}
+
+function shouldIgnoreTextNode(node: Text) {
+ const parent = node.parentElement
+
+ if (!parent) {
+ return true
+ }
+
+ return ignoredTags.has(parent.tagName) || Boolean(parent.closest('code, pre, .docblock-source'))
+}
+
+function localizeTextNode(node: Text) {
+ if (shouldIgnoreTextNode(node)) {
+ return
+ }
+
+ if (!textSources.has(node)) {
+ textSources.set(node, node.textContent || '')
+ }
+
+ const source = textSources.get(node) || ''
+ const translated = translateValue(source, currentLocale)
+
+ if (node.textContent !== translated) {
+ node.textContent = translated
+ }
+}
+
+function localizeAttribute(element: Element, attribute: string) {
+ const value = element.getAttribute(attribute)
+
+ if (!value) {
+ return
+ }
+
+ const attributes = attributeSources.get(element) || {}
+
+ if (!(attribute in attributes)) {
+ attributes[attribute] = value
+ attributeSources.set(element, attributes)
+ }
+
+ const translated = translateValue(attributes[attribute], currentLocale)
+
+ if (element.getAttribute(attribute) !== translated) {
+ element.setAttribute(attribute, translated)
+ }
+}
+
+function walkAndLocalize(root: ParentNode) {
+ const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT)
+ let currentNode = walker.nextNode()
+
+ while (currentNode) {
+ localizeTextNode(currentNode as Text)
+ currentNode = walker.nextNode()
+ }
+
+ root.querySelectorAll?.('[placeholder], [title], [aria-label]').forEach(element => {
+ localizeAttribute(element, 'placeholder')
+ localizeAttribute(element, 'title')
+ localizeAttribute(element, 'aria-label')
+ })
+}
+
+function applyLocalization() {
+ if (typeof document === 'undefined' || !document.body || isApplying) {
+ return
+ }
+
+ isApplying = true
+
+ try {
+ walkAndLocalize(document.body)
+ } finally {
+ isApplying = false
+ }
+}
+
+export function installStorybookPreviewLocalization(locale: StorybookLocale) {
+ currentLocale = locale
+
+ if (typeof window === 'undefined' || typeof document === 'undefined') {
+ return
+ }
+
+ if (!observer) {
+ observer = new MutationObserver(() => {
+ applyLocalization()
+ })
+
+ observer.observe(document.body, {
+ childList: true,
+ subtree: true,
+ characterData: true,
+ attributes: true,
+ attributeFilter: ['placeholder', 'title', 'aria-label'],
+ })
+ }
+
+ window.requestAnimationFrame(() => {
+ applyLocalization()
+ })
+}
diff --git a/.storybook/preview.ts b/.storybook/preview.ts
new file mode 100644
index 0000000..d25d689
--- /dev/null
+++ b/.storybook/preview.ts
@@ -0,0 +1,82 @@
+import { setup, type Preview } from '@storybook/vue3'
+import i18n from '../src/plugins/i18n.plugin'
+import { installStorybookPreviewLocalization } from './preview-localization'
+
+// Import global styles
+import '../src/styles/colors.css'
+
+setup(app => {
+ app.use(i18n)
+})
+
+const preview: Preview = {
+ parameters: {
+ actions: { argTypesRegex: '^on[A-Z].*' },
+ controls: {
+ matchers: {
+ color: /(background|color)$/i,
+ date: /Date$/i,
+ },
+ },
+ layout: 'padded',
+ },
+ globalTypes: {
+ locale: {
+ name: 'Locale',
+ description: 'Internationalization locale',
+ defaultValue: i18n.global.locale.value,
+ toolbar: {
+ icon: 'globe',
+ items: [
+ { value: 'en', title: 'English' },
+ { value: 'fr', title: 'Francais' },
+ { value: 'de', title: 'Deutsch' },
+ { value: 'es', title: 'Espanol' },
+ { value: 'it', title: 'Italiano' },
+ { value: 'cn', title: '中文' },
+ ],
+ showName: true,
+ },
+ },
+ },
+ decorators: [
+ (story, context) => {
+ const locale = context.globals.locale || i18n.global.locale.value
+
+ i18n.global.locale.value = locale
+ installStorybookPreviewLocalization(locale as 'en' | 'fr' | 'de' | 'es' | 'it' | 'cn')
+
+ if (typeof document !== 'undefined') {
+ document.documentElement.lang = locale
+ document.documentElement.dir = 'ltr'
+
+ // Update page metadata based on story
+ const storyTitle = context.title || 'Cresh UI'
+ const storyName = context.name || 'Story'
+ const fullTitle = `${storyTitle} - ${storyName} | Cresh UI`
+
+ // Update document title
+ document.title = fullTitle
+
+ // Update Open Graph title
+ const ogTitle = document.querySelector('meta[property="og:title"]')
+ if (ogTitle) {
+ ogTitle.setAttribute('content', fullTitle)
+ }
+
+ // Update canonical URL if in a story view
+ const canonical = document.querySelector('link[rel="canonical"]')
+ if (canonical && context.id) {
+ canonical.setAttribute('href', `https://storybook.cresh-ui.dev/?path=/story/${context.id}`)
+ }
+ }
+
+ return {
+ components: { story },
+ template: '',
+ }
+ },
+ ],
+}
+
+export default preview
diff --git a/API_CONVENTIONS.md b/API_CONVENTIONS.md
new file mode 100644
index 0000000..4520625
--- /dev/null
+++ b/API_CONVENTIONS.md
@@ -0,0 +1,235 @@
+# Cresh UI - API Conventions Charter
+
+This document defines consistent API conventions for all Cresh UI components to reduce learning curve and ensure predictable behavior across the library.
+
+## 1. Form Input Components
+
+Components that accept user input should follow Vue 3's v-model pattern.
+
+### Pattern:
+- **Prop**: `modelValue` (the current value)
+- **Event**: `@update:modelValue` (emitted when value changes)
+- **Usage**: `v-model="variable"`
+
+### Components:
+- CInput
+- CTextarea
+- CSelect
+- CSwitch
+- CSlider
+- CCheckbox
+- CDatetimeInput
+- CFileInput
+
+### Example:
+```vue
+
+
+```
+
+---
+
+## 2. Overlay & Dialog Components
+
+Components that control visibility should combine v-model for visibility with specific action events.
+
+### Pattern:
+- **Prop**: `modelValue` (boolean) - controls visibility
+- **Event**: `@update:modelValue` - emitted when visibility changes
+- **Additional Events**: Specific action events (`ok`, `cancel`, `prev`, `next`, etc.)
+
+### Components:
+- CModal
+- CDocumentViewer
+
+### Example:
+```vue
+
+ Modal content
+
+```
+
+---
+
+## 3. File Upload Components
+
+File upload components emit file data directly without v-model binding for the file itself.
+
+### Pattern:
+- **Event**: `@uploaded` - emitted with the uploaded File object
+- **No v-model**: Files are not stored in a "value" model, only emitted as events
+
+### Components:
+- CFileUploadZone
+- CFileInput
+
+### Example:
+```vue
+
+```
+
+---
+
+## 4. Navigation & Action Components
+
+Components that trigger navigation or selection actions should use consistent, descriptive event names.
+
+### Pattern:
+- **Events**: Use simple, present-tense verbs (`select`, `prev`, `next`, `close`, etc.)
+- **Prefix**: Navigation events use directional names (`prev`, `next`)
+- **Selection events**: Use `select` or specific action names
+
+### Components:
+- CDocumentViewer: `prev`, `next`
+- CVerticalStepper: `select`
+- CDropdown: `select`
+- CAccordion: `accordion-change`
+
+### Example:
+```vue
+
+
+
+```
+
+---
+
+## 5. Composite & Layout Components
+
+Components that compose other components should follow appropriate sub-pattern conventions.
+
+### Pattern:
+- If contains form inputs: Use v-model for relevant properties
+- If contains actions: Use specific event names
+- If contains slots: Document slot scope and properties
+
+### Components:
+- CHeader
+- CTable
+- CCarousel
+
+---
+
+## 6. Event Naming Best Practices
+
+### ✅ DO:
+- Use lowercase, kebab-case event names: `@update:modelValue`, `@uploaded`, `@prev`
+- Use past participle for actions that complete: `uploaded`, `selected`, `changed`
+- Use present tense for navigation: `prev`, `next`
+- Use specific names for dialog actions: `ok`, `cancel` (not generic `submit`/`close`)
+
+### ❌ DON'T:
+- Mix conventions: Don't use `switchState` when `update:modelValue` is available
+- Use generic names: Avoid `@action` or `@change` alone
+- Use camelCase in event names: Use `update:modelValue` not `updateModelValue`
+- Use custom event names for v-model candidates
+
+---
+
+## 7. Implementation Checklist
+
+When creating or updating a component, verify:
+
+- [ ] **Form Input?** → Use `modelValue` + `@update:modelValue`
+- [ ] **Overlay/Dialog?** → Use `modelValue` for visibility + specific action events
+- [ ] **File Upload?** → Emit `@uploaded` with File object
+- [ ] **Navigation?** → Use `@prev`, `@next`, or `@select`
+- [ ] **Custom Events?** → Use past participle (`loaded`, `changed`) or present tense (`select`, `next`)
+- [ ] **Documentation?** → Document props, events, and slots in Storybook argTypes
+- [ ] **Tests?** → Test event emissions match the expected convention
+
+---
+
+## 8. Migration Guide
+
+If updating existing components to follow conventions:
+
+### ❌ Before (Inconsistent):
+```typescript
+// CSwitch - custom event
+emit('switchState', checked)
+
+// CFileUploadZone - no v-model
+emit('uploaded', file)
+
+// CAccordion - kebab-case custom event
+emit('accordion-change', index)
+```
+
+### ✅ After (Consistent):
+```typescript
+// CSwitch - v-model support
+emit('update:modelValue', checked)
+
+// CFileUploadZone - consistent event naming
+emit('uploaded', file)
+
+// CAccordion - consistent action event naming (consider: emit('select', index))
+emit('accordion-change', index)
+```
+
+---
+
+## 9. Examples by Component Type
+
+### Form Input (with v-model)
+```vue
+
+
+
+
+
+```
+
+### Overlay (v-model + actions)
+```vue
+
+
+
+
+
+
+
+
+```
+
+### File Upload (event-driven)
+```vue
+
+
+
+ handleFileSelect(e.target.files[0])" />
+
+```
+
+---
+
+## 10. Notes
+
+- **Vue 3 Compliance**: All conventions follow Vue 3 composition API best practices
+- **Accessibility**: Event conventions should align with semantic HTML and ARIA patterns
+- **Type Safety**: Use TypeScript generics with `defineEmits<{ (e: 'event-name', payload): void }>()`
+- **Backward Compatibility**: Mark breaking changes clearly when updating event names
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c6c5ddc..168ab0a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,132 @@
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
+## [6.0.0](https://github.com/creshpay/cresh-ui/compare/v5.18.0...v6.0.0) (2026-04-30)
+
+### ⚠ BREAKING CHANGES
+
+- **peerDependencies bumped to majors:**
+ - `vue-router` `^4.2.2` → `^5.0.6`
+ - `vue-i18n` `^9.14.5` → `^11.4.0`
+ - `vue` peer floor raised to `^3.5.33` (still 3.x)
+- **`@vuepic/vue-datepicker` 8 → 12** — the package no longer ships a default
+ export. Internal usage was migrated to
+ `import { VueDatePicker as Datepicker } from '@vuepic/vue-datepicker'`.
+ The `locale` prop now expects a `date-fns` `Locale` object instead of a string.
+- **Node.js `>=22`** is now required (`engines.node`).
+- **ESLint flat config** (`eslint.config.js`) replaces `.eslintrc.js`. Consumers
+ extending the legacy file path will need to migrate.
+
+### Features
+
+#### A11y & UX
+- Added unified `error` slot on every form-control (`CInput`, `CSelect`,
+ `CTextarea`, `CRadio`, `CDatetimeInput`, `CCheckbox`). Legacy `errors` slot
+ still works but logs a deprecation warning in development.
+- `CCheckbox` rebuilt with a visually-hidden native `` inside its label
+ (proper screen-reader support + visible focus ring).
+- ARIA wiring on every form-control: `aria-invalid`, `aria-describedby`,
+ `aria-required`, error region with stable `id`.
+- `CDropdown` now exposes a full ARIA listbox/option pattern with keyboard
+ navigation (↑ ↓ Home End Enter Space Esc Tab) and `aria-activedescendant`.
+- `CSwitch` exposes `role="switch"` and an `aria-label` prop.
+- `CModal` listens for `Escape` at the document level when open and now uses
+ `` (configurable via the new `teleport` and `teleportTarget` props).
+- All hardcoded English `aria-label`s (`CTag`, `CDocumentViewer`,
+ `CFileUploadZone`) are translated through `vue-i18n`. Added
+ `common.aria.{close, previous, next, upload_zone}` to all 6 locales (en, fr,
+ de, es, it, cn).
+- `CFileUploadZone` file `` is now `tabindex="-1" aria-hidden="true"`;
+ the visible button is the real keyboard trigger.
+
+#### API
+- New composable `useFormControl(props, fallbackName)` centralising
+ `inputId` / `errorId` / `hasError` / `hasErrorSlot` / `hasValueOrPlaceholder`.
+- `CButton` rewritten with `
diff --git a/src/components/form-controls/CCreditCardInput.stories.ts b/src/components/form-controls/CCreditCardInput.stories.ts
new file mode 100644
index 0000000..b60fb26
--- /dev/null
+++ b/src/components/form-controls/CCreditCardInput.stories.ts
@@ -0,0 +1,111 @@
+import type { Meta, StoryObj } from '@storybook/vue3'
+import { ref } from 'vue'
+import { useI18n } from 'vue-i18n'
+import CCreditCardInput from './CCreditCardInput.vue'
+
+const meta = {
+ title: 'Form Controls/CCreditCardInput',
+ component: CCreditCardInput,
+ parameters: {
+ layout: 'centered',
+ },
+ tags: ['autodocs'],
+} satisfies Meta
+
+export default meta
+type Story = StoryObj
+
+export const Default: Story = {
+ render: () => ({
+ components: { CCreditCardInput },
+ setup() {
+ const { t } = useI18n()
+ const cardNumber = ref('')
+ return { cardNumber, t }
+ },
+ template: `
+