diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 12e303c72..c15fab59c 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -2,6 +2,7 @@ module.exports = { env: { browser: true, es2020: true, + node: true, }, extends: [ "eslint:recommended", diff --git a/.gitignore b/.gitignore index 99a2840aa..67b28697a 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,7 @@ storybook-static *.log **/.DS_Store vite.config.ts.timestamp* + +# Generated TypeScript declaration files for SCSS modules +**/*.scss.d.ts +**/*.css.d.ts diff --git a/.prettierignore b/.prettierignore index 1758950ad..e69de29bb 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1 +0,0 @@ -src/styles/types.ts diff --git a/.storybook/main.ts b/.storybook/main.ts index 5182f9b16..686acc09c 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -1,7 +1,7 @@ import type { StorybookConfig } from "@storybook/react-vite"; const config: StorybookConfig = { core: { - disableTelemetry: true + disableTelemetry: true, }, stories: [ "../src/Introduction.mdx", @@ -9,8 +9,12 @@ const config: StorybookConfig = { "../src/**/*.stories.@(js|jsx|ts|tsx)", ], - addons: ["@storybook/addon-links", //"@storybook/addon-interactions", - "storybook-addon-pseudo-states", "@storybook/addon-a11y", "@storybook/addon-docs"], + addons: [ + "@storybook/addon-links", //"@storybook/addon-interactions", + "storybook-addon-pseudo-states", + "@storybook/addon-a11y", + "@storybook/addon-docs", + ], framework: { name: "@storybook/react-vite", @@ -33,27 +37,36 @@ const config: StorybookConfig = { }, async viteFinal(config, { configType }) { + if (config.css?.preprocessorOptions?.scss) { + config.css.preprocessorOptions.scss.api = "modern-compiler"; + } else { + config.css = config.css || {}; + config.css.preprocessorOptions = config.css.preprocessorOptions || {}; + config.css.preprocessorOptions.scss = { + api: "modern-compiler", + }; + } // Workaround for Storybook 10.0.7 bug where MDX files generate file:// imports // See: https://github.com/storybookjs/storybook/issues (mdx-react-shim resolution) config.plugins = config.plugins || []; config.plugins.push({ - name: 'fix-storybook-mdx-shim', + name: "fix-storybook-mdx-shim", resolveId(source) { // Intercept the malformed file:// URL and resolve to the correct package - if (source.includes('mdx-react-shim')) { - return this.resolve('@mdx-js/react', undefined, { skipSelf: true }); + if (source.includes("mdx-react-shim")) { + return this.resolve("@mdx-js/react", undefined, { skipSelf: true }); } return null; }, }); // Suppress Rollup warnings for production builds - if (configType === 'PRODUCTION') { + if (configType === "PRODUCTION") { config.build = config.build || {}; config.build.rollupOptions = config.build.rollupOptions || {}; const originalOnWarn = config.build.rollupOptions.onwarn; config.build.rollupOptions.onwarn = (warning, warn) => { - if (warning.message?.includes('mdx-react-shim')) return; + if (warning.message?.includes("mdx-react-shim")) return; originalOnWarn ? originalOnWarn(warning, warn) : warn(warning); }; } diff --git a/.storybook/preview.module.scss b/.storybook/preview.module.scss new file mode 100644 index 000000000..3a2cd88aa --- /dev/null +++ b/.storybook/preview.module.scss @@ -0,0 +1,25 @@ +.cuiThemeBlock { + position: absolute; + top: 0.5rem; + width: 96vw; + height: 100vh; + bottom: 0; + overflow: auto; + padding: 1rem; + background: var(--click-storybook-global-background); + + &.cuiLeft { + left: 0; + right: 50vw; + } + + &.cuiRight { + left: 50vw; + right: 0; + } + + &.cuiFill { + left: 0; + right: 0; + } +} diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx index d949d11f5..567b8c92d 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -1,24 +1,28 @@ import React from "react"; import type { Preview } from "@storybook/react-vite"; -import "../src/styles/variables.css"; +import "@/styles/cui-default-theme.css"; import { Decorator } from "@storybook/react-vite"; -import styled from "styled-components"; import { themes } from "storybook/theming"; -import ClickUIProvider from "../src/components/ClickUIProvider/ClickUIProvider"; +import { ClickUIProvider } from "@/theme/ClickUIProvider"; +import clsx from "clsx"; +import styles from "./preview.module.scss"; -const ThemeBlock = styled.div<{ $left?: boolean; $bfill?: boolean }>( - ({ $left, $bfill: fill, theme }) => ` - position: absolute; - top: 0.5rem; - left: ${$left || fill ? 0 : "50vw"}; - right: ${$left ? "50vw" : 0}; - width: 96vw; - height: 100vh; - bottom: 0; - overflow: auto; - padding: 1rem; - background: ${theme.click.storybook.global.background}; - ` +interface ThemeBlockProps { + left?: boolean; + fill?: boolean; + children: React.ReactNode; +} + +const ThemeBlock: React.FC = ({ left, fill, children }) => ( +
+ {children} +
); export const globalTypes = { @@ -27,28 +31,38 @@ export const globalTypes = { description: "Global theme for components", defaultValue: "dark", toolbar: { - // The icon for the toolbar item icon: "circlehollow", - // Array of options items: [ - { value: "dark", icon: "moon", title: "dark" }, - { value: "light", icon: "sun", title: "light" }, - { value: "classic", icon: "circle", title: "classic" }, + { value: "light", icon: "sun", title: "Light" }, + { value: "dark", icon: "moon", title: "Dark" }, + { value: "system", icon: "browser", title: "System" }, ], - // Property that specifies if the name of the item will be displayed showName: true, + dynamicTitle: true, }, }, }; const withTheme: Decorator = (StoryFn, context) => { const parameters = context.parameters; - const theme = parameters?.theme || context.globals.theme; + const theme = parameters?.theme || context.globals.theme || "light"; + return ( - + @@ -83,7 +97,24 @@ const preview: Preview = { }, docs: { theme: themes.dark, - codePanel: true + codePanel: true, + }, + backgrounds: { + default: "click-ui", + values: [ + { + name: "click-ui", + value: "var(--click-storybook-global-background)", + }, + { + name: "light", + value: "var(--click-storybook-global-background)", + }, + { + name: "dark", + value: "var(--click-storybook-global-background)", + }, + ], }, }, }; diff --git a/.vscode/settings.json b/.vscode/settings.json index 2c63c0851..1df65ee1a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,2 +1,19 @@ { + // TypeScript: Prioritize source files over declaration files + "typescript.preferences.autoImportFileExcludePatterns": [ + "**/*.scss.d.ts", + "**/node_modules/**" + ], + + // Hide generated .scss.d.ts files from search and file explorer + "files.exclude": { + "**/*.scss.d.ts": true + }, + + // Search settings to exclude generated files + "search.exclude": { + "**/*.scss.d.ts": true, + "**/node_modules": true, + "**/dist": true + } } diff --git a/README.md b/README.md index 3b2078128..5655dfcb8 100644 --- a/README.md +++ b/README.md @@ -6,35 +6,176 @@ The home of the ClickHouse design system and component library. Click UI has been tested in NextJS, Gatsby, and Vite. If you run into problems using it in your app, please create an issue and our team will try to answer. -1. Navigate to your app's route and run - `npm i @clickhouse/click-ui` - or - `yarn add @clickhouse/click-ui` -2. Make sure to wrap your application in the Click UI `ClickUIProvider`, without doing this, you may run into issues with styled-components. Once thats done, you'll be able to import the individual components that you want to use on each page. Here's an example an `App.tsx` in NextJS. +### Quick Start -```typescript -import { ClickUIProvider, Text, ThemeName, Title, Switch } from '@clickhouse/click-ui' +1. **Install Click UI** + ```bash + npm install @clickhouse/click-ui + ``` + +2. **Import the required styles** + + Add these imports at the top of your main application file (before other styles): + + **Option A: Simple (Recommended for most projects)** + ```typescript + // All-in-one: Includes both component styles and default theme + import '@clickhouse/click-ui/cui.css'; + ``` + + **Option B: Granular (For custom theming)** + ```typescript + // Component styles only + import '@clickhouse/click-ui/cui-components.css'; + + // Default theme (light + dark with automatic switching) + import '@clickhouse/click-ui/cui-default-theme.css'; + + // OR import your custom theme instead: + // import './my-custom-theme.css'; + ``` + + **Where to import:** + - **Next.js App Router**: Add to your root `layout.tsx` + - **Next.js Pages Router**: Add to `pages/_app.tsx` + - **Gatsby**: Add to `gatsby-browser.js` + - **Vite/React**: Add to `main.tsx` or `App.tsx` + +3. **Wrap your app with ClickUIProvider** + + ```typescript + import '@clickhouse/click-ui/cui.css'; + import { ClickUIProvider, Text, Title } from '@clickhouse/click-ui' + + function App() { + return ( + + Click UI Example + Welcome to Click UI! + + ) + } -function App() { - const [theme, setTheme] = useState('dark') + export default App + ``` - const toggleTheme = () => { - theme === 'dark' ? setTheme('light') : setTheme('dark') - } +### Customizing Your Theme - return ( - - toggleTheme() } /> +Click UI supports extensive theming through a CLI-based approach: - Click UI Example - Welcome to Click UI. Get started here. - - ) +#### Step 1: Create a config file + +```bash +npx click-ui init +``` + +This creates a `click-ui.config.ts` file in your project root. + +#### Step 2: Customize your theme + +```typescript +// click-ui.config.ts +export default { + // Light mode theme tokens + theme: { + button: { + color: { + primary: { + 'background-default': '#FF6B00', + 'background-hover': '#FF8533', + } + } + } + }, + + // Dark mode overrides + dark: { + button: { + color: { + primary: { + 'background-default': '#FF8533', + 'background-hover': '#FFA366', + } + } + } + }, + + // Runtime configuration (optional) + storageKey: 'my-app-theme', + tooltipConfig: { delayDuration: 200 } } +``` + +#### Step 3: Generate your custom theme CSS -export default App +```bash +npx click-ui generate ``` +This creates `cui-custom-theme.css` in your `public/` folder. + +#### Step 4: Import your custom theme instead of the default + +```typescript +import '@clickhouse/click-ui/cui-components.css'; // Component styles only +import './public/cui-custom-theme.css'; // Your custom theme +import { ClickUIProvider } from '@clickhouse/click-ui' +``` + +**Resources:** +- **[CLI Theme Generation](CLI_THEME_GENERATION.md)** - Complete CLI documentation +- **[Theme System](src/theme/index.md)** - Runtime theming, hooks, and theme switching +- **[Build-Time Configuration](BUILD_TIME_CONFIG_CLICK_UI.md)** - Theme config API reference + +--- + +## How It Works + +Click UI uses a **pre-built CSS approach** for optimal performance: + +1. **CSS Files Generated at Build Time** + - Default theme included in the library (~10 KB) + - Custom themes generated via CLI + - Uses CSS `light-dark()` function for automatic theme switching + +2. **Minimal Runtime JavaScript** + - Provider only sets `data-theme` attribute (~600 bytes) + - Zero runtime CSS generation + - Instant theme switching via CSS attributes + +3. **Perfect Tree-Shaking** + - Import only the components you use + - Unused CSS automatically eliminated by your bundler + - Optimal bundle size + +### Theme Switching + +Themes are controlled via HTML attributes and CSS custom properties: + +```html + + + + + + + + +``` + +All theme tokens are available as CSS variables with the `--click` prefix: + +```css +.my-component { + background: var(--click-global-color-background-default); + color: var(--click-global-color-text-default); +} +``` + +The provider automatically sets the theme attribute based on user preference and system settings. + +--- + ## To develop this library locally šŸš€ 1. Clone this repo, cd into the `click-ui` directory diff --git a/bin/README.md b/bin/README.md new file mode 100644 index 000000000..c5e21108e --- /dev/null +++ b/bin/README.md @@ -0,0 +1,178 @@ +# Click UI CLI + +This directory contains the Click UI command-line interface for theme customization. + +## Quick Start + +```bash +# Initialize a config file +npx click-ui init + +# Generate custom theme CSS +npx click-ui generate + +# Get help +npx click-ui --help +``` + +## Commands + +### `click-ui init` + +Creates a `click-ui.config.ts` file in your project root with default configuration. + +**Options:** +- `-f, --format ` - Config format: `js` or `ts` (default: `ts`) +- `--force` - Overwrite existing config file +- `-h, --help` - Display help + +**Example:** +```bash +npx click-ui init +npx click-ui init --format js +npx click-ui init --force +``` + +### `click-ui generate` + +Generates custom theme CSS from your `click-ui.config.ts` file. + +**Options:** +- `-o, --output ` - Output file path (default: `public/cui-custom-theme.css`) +- `-v, --verbose` - Show detailed output +- `-w, --watch` - Watch for config changes and regenerate +- `-h, --help` - Display help + +**Example:** +```bash +npx click-ui generate +npx click-ui generate --output src/theme.css +npx click-ui generate --watch +``` + +## Configuration + +See [click-ui.config.example.ts](./click-ui.config.example.ts) for a complete example configuration. + +### Basic Structure + +```typescript +import type { ThemeConfig } from '@clickhouse/click-ui/theme'; + +const config: ThemeConfig = { + // Light mode theme + theme: { + global: { + color: { + brand: '#FF6B6B', + } + } + }, + + // Dark mode overrides + dark: { + global: { + color: { + brand: '#FF8A80', + } + } + }, + + // Runtime configuration + storageKey: 'my-app-theme', +}; + +export default config; +``` + +## Workflow + +1. **Create config:** + ```bash + npx click-ui init + ``` + +2. **Customize theme** in `click-ui.config.ts`: + ```typescript + theme: { + button: { + primary: { + background: { default: '#FF6B6B' } + } + } + } + ``` + +3. **Generate CSS:** + ```bash + npx click-ui generate + ``` + +4. **Import in your app:** + ```typescript + import '@clickhouse/click-ui/cui.css'; + import './public/cui-custom-theme.css'; // Your custom theme + import { ClickUIProvider } from '@clickhouse/click-ui'; + // OR for simple setup without custom theme: + // import '@clickhouse/click-ui/cui.css'; + + function App() { + return ( + + {/* Your app */} + + ); + } + ``` + +## File Structure + +``` +bin/ +ā”œā”€ā”€ click-ui.js # CLI entry point +ā”œā”€ā”€ click-ui.config.example.ts # Example configuration +ā”œā”€ā”€ commands/ +│ ā”œā”€ā”€ init.js # Creates config file +│ ā”œā”€ā”€ generate.js # Generates theme CSS +│ └── build-default-theme.ts # Internal: builds library's default theme +└── utils/ + └── css-generator.js # CSS generation utilities +``` + +## TypeScript Support + +Full TypeScript support with autocomplete for theme configuration: + +```typescript +import type { ThemeConfig } from '@clickhouse/click-ui/theme'; + +const config: ThemeConfig = { + theme: { + // TypeScript autocomplete works here! ✨ + } +}; +``` + +## CSS Output + +The CLI generates CSS using `light-dark()` functions for automatic theme switching: + +```css +:root { + color-scheme: light dark; + --click-button-color-primary-background-default: light-dark(#FF6B6B, #FF8A80); +} +``` + +This allows the browser to automatically switch between light and dark values based on the user's system preference or your theme setting. + +## Documentation + +For more information: +- [Main README](../README.md) - Getting started +- [Theme Documentation](../src/theme/index.md) - Theme configuration guide +- [GitHub Repository](https://github.com/ClickHouse/click-ui) + +## License + +Apache-2.0 diff --git a/bin/click-ui.config.example.ts b/bin/click-ui.config.example.ts new file mode 100644 index 000000000..435be6f33 --- /dev/null +++ b/bin/click-ui.config.example.ts @@ -0,0 +1,70 @@ +import type { ThemeConfig } from "@clickhouse/click-ui/theme"; + +const config: ThemeConfig = { + storageKey: "click-ui-theme", + + // Light mode theme (default) + theme: { + global: { + color: { + brand: "#FF6B6B", + background: { + default: "#FFFFFF", + }, + }, + }, + button: { + space: { + x: "1.5rem", + y: "0.75rem", + }, + radii: { + all: "0.5rem", + }, + primary: { + background: { + default: "#FF6B6B", + hover: "#FF5252", + }, + }, + }, + }, + + // Dark mode overrides - if not defined, theme values are used for dark mode too + dark: { + global: { + color: { + background: { + default: "#0D1117", + }, + text: { + default: "#F0F6FC", + }, + }, + }, + button: { + primary: { + background: { + default: "#FF8A80", + hover: "#FF7043", + }, + }, + }, + }, + + // Tooltip configuration (optional) + // tooltipConfig: { + // delayDuration: 100, + // skipDelayDuration: 300, + // disableHoverableContent: false, + // }, + + // Toast configuration (optional) + // toastConfig: { + // duration: 4000, + // swipeDirection: "right", + // swipeThreshold: 50, + // }, +}; + +export default config; diff --git a/bin/click-ui.js b/bin/click-ui.js new file mode 100755 index 000000000..643e89f73 --- /dev/null +++ b/bin/click-ui.js @@ -0,0 +1,97 @@ +#!/usr/bin/env node + +import { initCommand } from './commands/init.js'; +import { generateCommand } from './commands/generate.js'; + +const args = process.argv.slice(2); +const command = args[0]; + +if (command === 'init') { + const options = { + format: 'ts', + force: false + }; + + // Parse options + for (let i = 1; i < args.length; i++) { + if (args[i] === '-f' || args[i] === '--format') { + options.format = args[i + 1]; + i++; + } else if (args[i] === '--force') { + options.force = true; + } else if (args[i] === '-h' || args[i] === '--help') { + console.log(` +Usage: @clickhouse/click-ui init [options] + +Initialize Click UI configuration file + +Options: + -f, --format Config format (js or ts) (default: "ts") + --force Overwrite existing config file + -h, --help Display help for command + `); + process.exit(0); + } + } + + initCommand(options); +} else if (command === 'generate') { + const options = { + output: null, + verbose: false, + watch: false + }; + + // Parse options + for (let i = 1; i < args.length; i++) { + if (args[i] === '-o' || args[i] === '--output') { + options.output = args[i + 1]; + i++; + } else if (args[i] === '-v' || args[i] === '--verbose') { + options.verbose = true; + } else if (args[i] === '-w' || args[i] === '--watch') { + options.watch = true; + } else if (args[i] === '-h' || args[i] === '--help') { + console.log(` +Usage: @clickhouse/click-ui generate [options] + +Generate custom theme CSS from click-ui.config.ts/js + +Options: + -o, --output Output file path (default: "public/cui-custom-theme.css") + -v, --verbose Show detailed output + -w, --watch Watch for config changes and regenerate + -h, --help Display help for command + +Example: + click-ui generate + click-ui generate --output src/theme.css + click-ui generate --watch + `); + process.exit(0); + } + } + + generateCommand(options); +} else if (command === '--version' || command === '-V') { + console.log('0.0.234'); +} else if (command === '--help' || command === '-h' || !command) { + console.log(` +Usage: @clickhouse/click-ui [options] [command] + +CLI for ClickHouse Click UI + +Options: + -V, --version Output the version number + -h, --help Display help for command + +Commands: + init [options] Initialize Click UI configuration file + generate [options] Generate custom theme CSS from config + help [command] Display help for command + `); +} else { + console.error(`Unknown command: ${command}`); + console.log('Run "@clickhouse/click-ui --help" for usage information.'); + process.exit(1); +} diff --git a/bin/commands/build-default-theme.ts b/bin/commands/build-default-theme.ts new file mode 100644 index 000000000..63f06719e --- /dev/null +++ b/bin/commands/build-default-theme.ts @@ -0,0 +1,48 @@ +#!/usr/bin/env node + +/** + * Generate theme-default.css with light-dark() functions + * This runs during library build to create pre-compiled CSS + */ + +import * as fs from "fs"; +import * as path from "path"; +import { getBaseTheme } from "../../src/theme/utils"; +import { generateLightDarkVariables, generateThemeOverrides } from "../../src/theme/utils/css-generator"; +import { buildCSSOutput } from "../../src/theme/utils/css-builder"; + +// Load light and dark base themes from tokens +const lightTheme = getBaseTheme("light"); +const darkTheme = getBaseTheme("dark"); + +// Debug: Check if themes loaded correctly +console.log("\nšŸ” Debug Info:"); +console.log(`Light theme loaded: ${Object.keys(lightTheme).length} keys`); +console.log(`Dark theme loaded: ${Object.keys(darkTheme).length} keys`); +console.log(`Light accordion color: ${lightTheme?.click?.accordion?.color?.default?.label?.default}`); +console.log(`Dark accordion color: ${darkTheme?.click?.accordion?.color?.default?.label?.default}`); + +// Generate CSS using the same logic as injectThemeStyles() +const lightDarkVars = generateLightDarkVariables(lightTheme, darkTheme); +const themeOverrides = generateThemeOverrides(lightTheme, darkTheme); + +// Build CSS using shared builder +const css = buildCSSOutput(lightDarkVars, themeOverrides, { + headerComment: '/* Click UI Default Theme */\n/* Generated during library build - DO NOT EDIT MANUALLY */\n/* Uses light-dark() CSS function for automatic theme switching */\n' +}); + +// Ensure output directory exists +const outputDir = path.join(process.cwd(), "src", "styles"); +if (!fs.existsSync(outputDir)) { + fs.mkdirSync(outputDir, { recursive: true }); +} + +// Write the CSS file +const outputPath = path.join(outputDir, "cui-default-theme.css"); +fs.writeFileSync(outputPath, css, "utf-8"); + +console.log(`āœ… Generated cui-default-theme.css (${css.length} bytes)`); +console.log(` Location: ${outputPath}`); +console.log(` Light vars: ${Object.keys(lightDarkVars).length}`); +console.log(` Light overrides: ${Object.keys(themeOverrides.light).length}`); +console.log(` Dark overrides: ${Object.keys(themeOverrides.dark).length}`); diff --git a/bin/commands/generate.js b/bin/commands/generate.js new file mode 100644 index 000000000..b2e016569 --- /dev/null +++ b/bin/commands/generate.js @@ -0,0 +1,117 @@ +#!/usr/bin/env node + +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import { pathToFileURL } from 'url'; +import { findConfigFile } from '../../src/theme/utils/find-config.js'; +import { loadConfig, validateConfig } from '../utils/config-loader.js'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +/** + * Generate custom theme CSS from click-ui.config.ts/js + */ +export async function generateCommand(options = {}) { + const cwd = process.cwd(); + const outputPath = options.output || path.join(cwd, 'public', 'cui-custom-theme.css'); + const verbose = options.verbose || false; + + console.log('šŸŽØ Click UI Theme Generator\n'); + + try { + // Find config file + const configFile = findConfigFile(cwd); + + if (!configFile) { + console.log('ā„¹ļø No click-ui.config.ts/js found'); + console.log(' Run "click-ui init" to create a config file'); + process.exit(0); + } + + if (verbose) { + console.log(`šŸ“„ Found config: ${path.relative(cwd, configFile)}`); + } + + // Load config + const config = await loadConfig(configFile); + + if (!validateConfig(config)) { + console.log('āš ļø Config file exists but has no theme configuration'); + console.log(' Add "theme" or "dark" properties to customize your theme'); + process.exit(0); + } + + // Generate CSS + const css = await generateCSS(config); + + // Ensure output directory exists + const outputDir = path.dirname(outputPath); + if (!fs.existsSync(outputDir)) { + fs.mkdirSync(outputDir, { recursive: true }); + if (verbose) { + console.log(`šŸ“ Created directory: ${path.relative(cwd, outputDir)}`); + } + } + + // Write CSS file + fs.writeFileSync(outputPath, css, 'utf-8'); + + console.log('āœ… Generated custom theme CSS'); + console.log(` Location: ${path.relative(cwd, outputPath)}`); + console.log(` Size: ${(css.length / 1024).toFixed(2)} KB\n`); + + console.log('šŸ“ Next steps:'); + console.log(' 1. Import the CSS in your app:'); + console.log(` import '${path.relative(cwd, outputPath).replace(/\\/g, '/')}';`); + console.log(' 2. Or add to your HTML:'); + console.log(` `); + + } catch (error) { + console.error('āŒ Failed to generate theme CSS:', error.message); + if (verbose) { + console.error(error.stack); + } + process.exit(1); + } +} + +/** + * Generate CSS from config + */ +async function generateCSS(config) { + // Import the CSS generation function from bin/utils + const cssGeneratorPath = path.join(__dirname, '../utils/css-generator.js'); + + if (!fs.existsSync(cssGeneratorPath)) { + throw new Error('CSS generator module not found at ' + cssGeneratorPath); + } + + const { generateThemeCSS } = await import(pathToFileURL(cssGeneratorPath).href); + + // Get full config and generate CSS + const fullConfig = config.default || config; + const css = generateThemeCSS(fullConfig); + + if (!css || css.length === 0) { + throw new Error('No CSS generated. Please check your theme configuration.'); + } + + return css; +} + +/** + * Split config into build-time and runtime + */ +function splitConfig(fullConfig) { + const { theme, dark, ...runtimeConfig } = fullConfig; + + return { + buildTimeConfig: { + ...(theme && { theme }), + ...(dark && { dark }), + }, + runtimeConfig, + }; +} diff --git a/bin/commands/init.js b/bin/commands/init.js new file mode 100644 index 000000000..d6c89b5d0 --- /dev/null +++ b/bin/commands/init.js @@ -0,0 +1,110 @@ +#!/usr/bin/env node + +import fs from "fs"; +import path from "path"; + +const CONFIG_BODY = ` // Optional: Customize the storage key for theme persistence + // storageKey: 'click-ui-theme', + + // Optional: Customize light mode theme + // theme: { + // global: { + // color: { + // brand: '#FFCC00', + // background: { + // default: '#FFFFFF' + // } + // } + // }, + // button: { + // space: { + // x: '1rem', + // y: '0.5rem' + // }, + // radii: { + // all: '0.375rem' + // } + // } + // }, + + // Optional: Dark mode overrides + // If not defined, theme values are used for dark mode too + // dark: { + // global: { + // color: { + // background: { + // default: '#0D1117' + // }, + // text: { + // default: '#F0F6FC' + // } + // } + // } + // }, + + // Optional: Tooltip configuration + // tooltipConfig: { + // delayDuration: 100, + // skipDelayDuration: 300, + // disableHoverableContent: false, + // }, + + // Optional: Toast configuration + // toastConfig: { + // duration: 4000, + // swipeDirection: 'right', + // swipeThreshold: 50, + // },`; + +const CONFIG_TEMPLATES = { + ts: `import type { ThemeConfig } from '@clickhouse/click-ui/theme'; + +const config: ThemeConfig = { +${CONFIG_BODY} +}; + +export default config; +`, + js: `/** @type {import('@clickhouse/click-ui/theme').ThemeConfig} */ +const config = { +${CONFIG_BODY} +}; + +export default config; +` +}; + +export const initCommand = options => { + const format = options.format === "js" ? "js" : "ts"; + const filename = `click-ui.config.${format}`; + const targetPath = path.join(process.cwd(), filename); + const configExt = format === "ts" ? "ts" : "js"; + + // Check if config already exists + if (fs.existsSync(targetPath) && !options.force) { + console.error(`āŒ ${filename} already exists. Use --force to overwrite.`); + process.exit(1); + } + + // Write config file + try { + fs.writeFileSync(targetPath, CONFIG_TEMPLATES[format], "utf-8"); + console.log(`āœ… Created ${filename}\n`); + console.log("šŸ“ Next steps:\n"); + console.log(` 1. Customize your theme in ${filename}\n`); + console.log(" 2. Generate your custom theme CSS:\n"); + console.log(" npx click-ui generate\n"); + console.log(" 3. Import the generated CSS in your app:\n"); + console.log(" import '@clickhouse/click-ui/style.css';"); + console.log(" import './public/cui-custom-theme.css'; // Your custom theme\n"); + console.log(" 4. Use ClickUIProvider in your app:\n"); + console.log(" import { ClickUIProvider } from '@clickhouse/click-ui';\n"); + console.log(" "); + console.log(" {/* Your app */}"); + console.log(" \n"); + console.log(" šŸŽØ Your custom theme will be applied!"); + } catch (error) { + console.error(`āŒ Failed to create config file: ${error.message}`); + process.exit(1); + } +}; diff --git a/bin/utils/config-loader.ts b/bin/utils/config-loader.ts new file mode 100644 index 000000000..762f4d163 --- /dev/null +++ b/bin/utils/config-loader.ts @@ -0,0 +1,61 @@ +import { pathToFileURL } from 'url'; + +/** + * Load Click UI configuration file + * Handles TypeScript, JavaScript, ESM, and CommonJS formats + * + * @param configFile - Full path to config file + * @returns Loaded configuration object + * @throws Error if config cannot be loaded or TypeScript file without tsx + * + * @example + * ```typescript + * const config = await loadConfig('/path/to/click-ui.config.js'); + * console.log('Theme:', config.theme); + * ``` + */ +export async function loadConfig(configFile: string): Promise { + try { + // For ES modules (.js, .mjs, .ts) + if (configFile.endsWith('.js') || configFile.endsWith('.mjs') || configFile.endsWith('.ts')) { + // TypeScript files require tsx or ts-node + if (configFile.endsWith('.ts')) { + console.log('āš ļø TypeScript config files require tsx or ts-node to be installed'); + console.log(' Run: npx tsx node_modules/@clickhouse/click-ui/bin/commands/generate.js'); + console.log(' Or convert your config to .js format'); + process.exit(1); + } + + // Convert to file URL for proper ESM import + const fileUrl = pathToFileURL(configFile).href; + const module = await import(fileUrl); + return module.default || module; + } + + // For CommonJS (.cjs) + if (configFile.endsWith('.cjs')) { + return require(configFile); + } + + throw new Error(`Unsupported config file extension: ${configFile}`); + } catch (error: any) { + throw new Error(`Failed to load config from ${configFile}: ${error.message}`); + } +} + +/** + * Validate that config has required theme properties + * + * @param config - Configuration object to validate + * @returns true if config has theme or dark properties + * + * @example + * ```typescript + * if (!validateConfig(config)) { + * console.error('Config missing theme configuration'); + * } + * ``` + */ +export function validateConfig(config: any): boolean { + return !!(config && (config.theme || config.dark)); +} diff --git a/bin/utils/css-generator.js b/bin/utils/css-generator.js new file mode 100644 index 000000000..dc00ecac1 --- /dev/null +++ b/bin/utils/css-generator.js @@ -0,0 +1,32 @@ +#!/usr/bin/env node + +/** + * CSS Generation for CLI + * Used by CLI generate and build-default-theme commands + */ + +import { generateLightDarkVariables, generateThemeOverrides } from '../../src/theme/utils/css-generator.js'; +import { buildCSSOutput } from '../../src/theme/utils/css-builder.js'; + +/** + * Generate CSS from theme config + * Used by CLI generate command + */ +export function generateThemeCSS(config) { + if (!config || (!config.theme && !config.dark)) { + return ''; + } + + // Get light and dark themes + const lightTheme = config.theme || {}; + const darkTheme = { ...config.theme, ...config.dark }; + + // Generate variables using light-dark() for colors + const lightDarkVars = generateLightDarkVariables(lightTheme, darkTheme); + const themeOverrides = generateThemeOverrides(lightTheme, darkTheme); + + // Build CSS using shared builder + return buildCSSOutput(lightDarkVars, themeOverrides, { + headerComment: '/* Click UI Custom Theme */\n/* Generated by click-ui generate command */\n/* Uses light-dark() CSS function for automatic theme switching */\n' + }); +} diff --git a/build-tokens.js b/build-tokens.js index c90fffb2e..5e758ce05 100644 --- a/build-tokens.js +++ b/build-tokens.js @@ -1,17 +1,42 @@ -import _ from "lodash"; import { registerTransforms, transforms } from "@tokens-studio/sd-transforms"; import StyleDictionary from "style-dictionary"; registerTransforms(StyleDictionary); -const themes = ["classic", "dark", "light"]; +const themes = ["dark", "light"]; +const setWith = (obj, path, value) => { + if (!obj || typeof obj !== "object") return obj; -function generateThemeFromDictionary(dictionary, valueFunc = (value) => value) { + const keys = Array.isArray(path) + ? path + : path.replace(/\[(\d+)\]/g, ".$1").split("."); + + let current = obj; + + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + + if (i === keys.length - 1) { + // Last key → set value + current[key] = value; + } else { + // Ensure object exists + if (current[key] == null || typeof current[key] !== "object") { + current[key] = {}; + } + current = current[key]; + } + } + + return obj; +}; + +const generateThemeFromDictionary = (dictionary, valueFunc = value => value) => { const theme = {}; - dictionary.allTokens.forEach((token) => { - _.setWith(theme, token.name, valueFunc(token.value), Object) + dictionary.allTokens.forEach(token => { + setWith(theme, token.name, valueFunc(token.value), Object) }); return theme; -} +}; StyleDictionary.registerTransform({ type: "name", @@ -27,7 +52,7 @@ StyleDictionary.registerTransform({ StyleDictionary.registerFormat({ name: "ThemeFormat", - formatter: function ({ dictionary, platform, options, file }) { + formatter: ({ dictionary }) => { const theme = generateThemeFromDictionary(dictionary); return JSON.stringify(theme, null, 2); } @@ -35,8 +60,8 @@ StyleDictionary.registerFormat({ StyleDictionary.registerFormat({ name: "TypescriptFormat", - formatter: function ({ dictionary, platform, options, file }) { - const theme = generateThemeFromDictionary(dictionary, (value) => typeof value); + formatter: ({ dictionary }) => { + const theme = generateThemeFromDictionary(dictionary, value => typeof value); return ` export interface Theme ${JSON.stringify(theme, null, 2).replaceAll("\"string\"", "string").replaceAll("\"number\"", "number")} @@ -45,7 +70,7 @@ StyleDictionary.registerFormat({ }); StyleDictionary.extend({ - source: [`./tokens/**/!(${themes.join("|*.")}).json`], + source: [`./tokens/**/*.json`], platforms: { css: { transforms: [...transforms, "name/cti/kebab"], @@ -62,7 +87,7 @@ StyleDictionary.extend({ }, js: { transforms: [...transforms, "name/cti/dot"], - buildPath: "src/styles/", + buildPath: "src/theme/tokens/", files: [ { destination: "variables.json", @@ -75,7 +100,7 @@ StyleDictionary.extend({ }, ts: { transforms: [...transforms, "name/cti/dot"], - buildPath: "src/styles/", + buildPath: "src/theme/tokens/", files: [ { destination: "types.ts", @@ -112,7 +137,7 @@ themes.forEach(theme => }, js: { transforms: [...transforms, "name/cti/dot"], - buildPath: "src/styles/", + buildPath: "src/theme/tokens/", files: [ { destination: `variables.${theme}.json`, diff --git a/package-lock.json b/package-lock.json index 03d9ef55b..3ba2f099c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,18 +21,20 @@ "@radix-ui/react-popover": "^1.0.7", "@radix-ui/react-popper": "^1.1.3", "@radix-ui/react-radio-group": "^1.1.3", - "@radix-ui/react-separator": "^1.0.3", "@radix-ui/react-switch": "^1.0.2", "@radix-ui/react-tabs": "^1.0.4", "@radix-ui/react-toast": "^1.1.5", "@radix-ui/react-tooltip": "^1.0.7", - "lodash": "^4.17.21", + "clsx": "^2.1.1", "react-sortablejs": "^6.1.4", "react-syntax-highlighter": "^15.5.0", "react-virtualized-auto-sizer": "^1.0.20", "react-window": "^1.8.9", "sortablejs": "^1.15.0" }, + "bin": { + "click-ui": "bin/click-ui.js" + }, "devDependencies": { "@storybook/addon-a11y": "^10.0.7", "@storybook/addon-docs": "^10.0.7", @@ -43,17 +45,15 @@ "@testing-library/react": "^15.0.7", "@testing-library/user-event": "^14.5.2", "@tokens-studio/sd-transforms": "^0.10.3", - "@types/lodash-es": "^4.17.7", + "@types/node": "^20.0.0", "@types/react": "^18.3.2", "@types/react-dom": "^18.3.0", "@types/react-syntax-highlighter": "^15.5.13", "@types/react-window": "^1.8.8", "@types/sortablejs": "^1.15.2", - "@types/styled-components": "^5.1.34", "@typescript-eslint/eslint-plugin": "^7.16.1", "@typescript-eslint/parser": "^7.16.1", "@vitejs/plugin-react": "^4.2.1", - "babel-plugin-styled-components": "^2.1.4", "chromatic": "^13.3.3", "eslint": "^8.57.0", "eslint-plugin-prefer-arrow-functions": "^3.3.2", @@ -62,7 +62,9 @@ "eslint-plugin-storybook": "^10.0.7", "jsdom": "^24.0.0", "prettier": "^3.6.2", - "prop-types": "^15.8.1", + "recharts": "^3.4.1", + "rollup": "^4.52.4", + "sass-embedded": "^1.93.0", "storybook": "^10.0.7", "storybook-addon-pseudo-states": "^10.0.7", "styled-components": "^6.1.11", @@ -75,10 +77,14 @@ "watch": "^1.0.2" }, "peerDependencies": { - "dayjs": "^1.11.13", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "styled-components": ">= 5" + "dayjs": "^1.11.18", + "react": "^18.2.0 || ^19.0.0", + "react-dom": "^18.2.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "dayjs": { + "optional": true + } } }, "node_modules/@adobe/css-tools": { @@ -168,18 +174,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", - "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-compilation-targets": { "version": "7.23.6", "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", @@ -362,21 +356,6 @@ "node": ">=6.0.0" } }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.1.tgz", - "integrity": "sha512-2eCtxZXf+kbkMIsXS4poTvT4Yu5rXiRa+9xGVT56raghjmBTKMpFNc9R4IDiB4emao9eO22Ox7CxuJG7BgExqA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-transform-react-jsx-self": { "version": "7.24.5", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.24.5.tgz", @@ -468,6 +447,12 @@ "node": ">=6.9.0" } }, + "node_modules/@bufbuild/protobuf": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.9.0.tgz", + "integrity": "sha512-rnJenoStJ8nvmt9Gzye8nkYd6V22xUAnu4086ER7h1zJ508vStko4pMvDeQ446ilDTFpV5wnoc5YS7XvMwwMqA==", + "dev": true + }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -1225,6 +1210,18 @@ "node": ">=6.0.0" } }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", @@ -1429,6 +1426,302 @@ "node": ">= 8" } }, + "node_modules/@parcel/watcher": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", + "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "dependencies": { + "detect-libc": "^1.0.3", + "is-glob": "^4.0.3", + "micromatch": "^4.0.5", + "node-addon-api": "^7.0.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.1", + "@parcel/watcher-darwin-arm64": "2.5.1", + "@parcel/watcher-darwin-x64": "2.5.1", + "@parcel/watcher-freebsd-x64": "2.5.1", + "@parcel/watcher-linux-arm-glibc": "2.5.1", + "@parcel/watcher-linux-arm-musl": "2.5.1", + "@parcel/watcher-linux-arm64-glibc": "2.5.1", + "@parcel/watcher-linux-arm64-musl": "2.5.1", + "@parcel/watcher-linux-x64-glibc": "2.5.1", + "@parcel/watcher-linux-x64-musl": "2.5.1", + "@parcel/watcher-win32-arm64": "2.5.1", + "@parcel/watcher-win32-ia32": "2.5.1", + "@parcel/watcher-win32-x64": "2.5.1" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz", + "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz", + "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz", + "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz", + "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz", + "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz", + "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz", + "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz", + "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz", + "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz", + "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz", + "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz", + "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz", + "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -2117,40 +2410,17 @@ } } }, - "node_modules/@radix-ui/react-separator": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.0.3.tgz", - "integrity": "sha512-itYmTy/kokS21aiV5+Z56MZB54KrhPgn6eHDKkFeOLR34HMN2s8PaN47qZZAGnvupcjxHaFZnW4pQEh0BvvVuw==", + "node_modules/@radix-ui/react-slot": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz", + "integrity": "sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==", "dependencies": { "@babel/runtime": "^7.13.10", - "@radix-ui/react-primitive": "1.0.3" + "@radix-ui/react-compose-refs": "1.0.1" }, "peerDependencies": { "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-slot": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz", - "integrity": "sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-compose-refs": "1.0.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0" + "react": "^16.8 || ^17.0 || ^18.0" }, "peerDependenciesMeta": { "@types/react": { @@ -2439,6 +2709,33 @@ "@babel/runtime": "^7.13.10" } }, + "node_modules/@reduxjs/toolkit": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.10.1.tgz", + "integrity": "sha512-/U17EXQ9Do9Yx4DlNGU6eVNfZvFJfYpUtRRdLf19PbPjdWBxNlxGZXywQZ1p1Nz8nMkWplTI7iD/23m07nolDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@standard-schema/utils": "^0.3.0", + "immer": "^10.2.0", + "redux": "^5.0.1", + "redux-thunk": "^3.1.0", + "reselect": "^5.1.0" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18 || ^19", + "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, "node_modules/@rollup/pluginutils": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.0.tgz", @@ -2462,9 +2759,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.22.4.tgz", - "integrity": "sha512-Fxamp4aEZnfPOcGA8KSNEohV8hX7zVHOemC8jVBoBUHu5zpJK/Eu3uJwt6BMgy9fkvzxDaurgj96F/NiLukF2w==", + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.4.tgz", + "integrity": "sha512-BTm2qKNnWIQ5auf4deoetINJm2JzvihvGb9R6K/ETwKLql/Bb3Eg2H1FBp1gUb4YGbydMA3jcmQTR73q7J+GAA==", "cpu": [ "arm" ], @@ -2475,9 +2772,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.22.4.tgz", - "integrity": "sha512-VXoK5UMrgECLYaMuGuVTOx5kcuap1Jm8g/M83RnCHBKOqvPPmROFJGQaZhGccnsFtfXQ3XYa4/jMCJvZnbJBdA==", + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.4.tgz", + "integrity": "sha512-P9LDQiC5vpgGFgz7GSM6dKPCiqR3XYN1WwJKA4/BUVDjHpYsf3iBEmVz62uyq20NGYbiGPR5cNHI7T1HqxNs2w==", "cpu": [ "arm64" ], @@ -2488,9 +2785,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.22.4.tgz", - "integrity": "sha512-xMM9ORBqu81jyMKCDP+SZDhnX2QEVQzTcC6G18KlTQEzWK8r/oNZtKuZaCcHhnsa6fEeOBionoyl5JsAbE/36Q==", + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.4.tgz", + "integrity": "sha512-QRWSW+bVccAvZF6cbNZBJwAehmvG9NwfWHwMy4GbWi/BQIA/laTIktebT2ipVjNncqE6GLPxOok5hsECgAxGZg==", "cpu": [ "arm64" ], @@ -2501,9 +2798,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.22.4.tgz", - "integrity": "sha512-aJJyYKQwbHuhTUrjWjxEvGnNNBCnmpHDvrb8JFDbeSH3m2XdHcxDd3jthAzvmoI8w/kSjd2y0udT+4okADsZIw==", + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.4.tgz", + "integrity": "sha512-hZgP05pResAkRJxL1b+7yxCnXPGsXU0fG9Yfd6dUaoGk+FhdPKCJ5L1Sumyxn8kvw8Qi5PvQ8ulenUbRjzeCTw==", "cpu": [ "x64" ], @@ -2513,10 +2810,36 @@ "darwin" ] }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.4.tgz", + "integrity": "sha512-xmc30VshuBNUd58Xk4TKAEcRZHaXlV+tCxIXELiE9sQuK3kG8ZFgSPi57UBJt8/ogfhAF5Oz4ZSUBN77weM+mQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.4.tgz", + "integrity": "sha512-WdSLpZFjOEqNZGmHflxyifolwAiZmDQzuOzIq9L27ButpCVpD7KzTRtEG1I0wMPFyiyUdOO+4t8GvrnBLQSwpw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.22.4.tgz", - "integrity": "sha512-j63YtCIRAzbO+gC2L9dWXRh5BFetsv0j0va0Wi9epXDgU/XUi5dJKo4USTttVyK7fGw2nPWK0PbAvyliz50SCQ==", + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.4.tgz", + "integrity": "sha512-xRiOu9Of1FZ4SxVbB0iEDXc4ddIcjCv2aj03dmW8UrZIW7aIQ9jVJdLBIhxBI+MaTnGAKyvMwPwQnoOEvP7FgQ==", "cpu": [ "arm" ], @@ -2527,9 +2850,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.22.4.tgz", - "integrity": "sha512-dJnWUgwWBX1YBRsuKKMOlXCzh2Wu1mlHzv20TpqEsfdZLb3WoJW2kIEsGwLkroYf24IrPAvOT/ZQ2OYMV6vlrg==", + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.4.tgz", + "integrity": "sha512-FbhM2p9TJAmEIEhIgzR4soUcsW49e9veAQCziwbR+XWB2zqJ12b4i/+hel9yLiD8pLncDH4fKIPIbt5238341Q==", "cpu": [ "arm" ], @@ -2540,9 +2863,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.22.4.tgz", - "integrity": "sha512-AdPRoNi3NKVLolCN/Sp4F4N1d98c4SBnHMKoLuiG6RXgoZ4sllseuGioszumnPGmPM2O7qaAX/IJdeDU8f26Aw==", + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.4.tgz", + "integrity": "sha512-4n4gVwhPHR9q/g8lKCyz0yuaD0MvDf7dV4f9tHt0C73Mp8h38UCtSCSE6R9iBlTbXlmA8CjpsZoujhszefqueg==", "cpu": [ "arm64" ], @@ -2553,9 +2876,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.22.4.tgz", - "integrity": "sha512-Gl0AxBtDg8uoAn5CCqQDMqAx22Wx22pjDOjBdmG0VIWX3qUBHzYmOKh8KXHL4UpogfJ14G4wk16EQogF+v8hmA==", + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.4.tgz", + "integrity": "sha512-u0n17nGA0nvi/11gcZKsjkLj1QIpAuPFQbR48Subo7SmZJnGxDpspyw2kbpuoQnyK+9pwf3pAoEXerJs/8Mi9g==", "cpu": [ "arm64" ], @@ -2565,10 +2888,23 @@ "linux" ] }, - "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.22.4.tgz", - "integrity": "sha512-3aVCK9xfWW1oGQpTsYJJPF6bfpWfhbRnhdlyhak2ZiyFLDaayz0EP5j9V1RVLAAxlmWKTDfS9wyRyY3hvhPoOg==", + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.4.tgz", + "integrity": "sha512-0G2c2lpYtbTuXo8KEJkDkClE/+/2AFPdPAbmaHoE870foRFs4pBrDehilMcrSScrN/fB/1HTaWO4bqw+ewBzMQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.4.tgz", + "integrity": "sha512-teSACug1GyZHmPDv14VNbvZFX779UqWTsd7KtTM9JIZRDI5NUwYSIS30kzI8m06gOPB//jtpqlhmraQ68b5X2g==", "cpu": [ "ppc64" ], @@ -2579,9 +2915,22 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.22.4.tgz", - "integrity": "sha512-ePYIir6VYnhgv2C5Xe9u+ico4t8sZWXschR6fMgoPUK31yQu7hTEJb7bCqivHECwIClJfKgE7zYsh1qTP3WHUA==", + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.4.tgz", + "integrity": "sha512-/MOEW3aHjjs1p4Pw1Xk4+3egRevx8Ji9N6HUIA1Ifh8Q+cg9dremvFCUbOX2Zebz80BwJIgCBUemjqhU5XI5Eg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.4.tgz", + "integrity": "sha512-1HHmsRyh845QDpEWzOFtMCph5Ts+9+yllCrREuBR/vg2RogAQGGBRC8lDPrPOMnrdOJ+mt1WLMOC2Kao/UwcvA==", "cpu": [ "riscv64" ], @@ -2592,9 +2941,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.22.4.tgz", - "integrity": "sha512-GqFJ9wLlbB9daxhVlrTe61vJtEY99/xB3C8e4ULVsVfflcpmR6c8UZXjtkMA6FhNONhj2eA5Tk9uAVw5orEs4Q==", + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.4.tgz", + "integrity": "sha512-seoeZp4L/6D1MUyjWkOMRU6/iLmCU2EjbMTyAG4oIOs1/I82Y5lTeaxW0KBfkUdHAWN7j25bpkt0rjnOgAcQcA==", "cpu": [ "s390x" ], @@ -2605,9 +2954,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.22.4.tgz", - "integrity": "sha512-87v0ol2sH9GE3cLQLNEy0K/R0pz1nvg76o8M5nhMR0+Q+BBGLnb35P0fVz4CQxHYXaAOhE8HhlkaZfsdUOlHwg==", + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.4.tgz", + "integrity": "sha512-Wi6AXf0k0L7E2gteNsNHUs7UMwCIhsCTs6+tqQ5GPwVRWMaflqGec4Sd8n6+FNFDw9vGcReqk2KzBDhCa1DLYg==", "cpu": [ "x64" ], @@ -2618,9 +2967,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.22.4.tgz", - "integrity": "sha512-UV6FZMUgePDZrFjrNGIWzDo/vABebuXBhJEqrHxrGiU6HikPy0Z3LfdtciIttEUQfuDdCn8fqh7wiFJjCNwO+g==", + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.4.tgz", + "integrity": "sha512-dtBZYjDmCQ9hW+WgEkaffvRRCKm767wWhxsFW3Lw86VXz/uJRuD438/XvbZT//B96Vs8oTA8Q4A0AfHbrxP9zw==", "cpu": [ "x64" ], @@ -2630,10 +2979,23 @@ "linux" ] }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.4.tgz", + "integrity": "sha512-1ox+GqgRWqaB1RnyZXL8PD6E5f7YyRUJYnCqKpNzxzP0TkaUh112NDrR9Tt+C8rJ4x5G9Mk8PQR3o7Ku2RKqKA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openharmony" + ] + }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.22.4.tgz", - "integrity": "sha512-BjI+NVVEGAXjGWYHz/vv0pBqfGoUH0IGZ0cICTn7kB9PyjrATSkX+8WkguNjWoj2qSr1im/+tTGRaY+4/PdcQw==", + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.4.tgz", + "integrity": "sha512-8GKr640PdFNXwzIE0IrkMWUNUomILLkfeHjXBi/nUvFlpZP+FA8BKGKpacjW6OUUHaNI6sUURxR2U2g78FOHWQ==", "cpu": [ "arm64" ], @@ -2644,9 +3006,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.22.4.tgz", - "integrity": "sha512-SiWG/1TuUdPvYmzmYnmd3IEifzR61Tragkbx9D3+R8mzQqDBz8v+BvZNDlkiTtI9T15KYZhP0ehn3Dld4n9J5g==", + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.4.tgz", + "integrity": "sha512-AIy/jdJ7WtJ/F6EcfOb2GjR9UweO0n43jNObQMb6oGxkYTfLcnN7vYYpG+CN3lLxrQkzWnMOoNSHTW54pgbVxw==", "cpu": [ "ia32" ], @@ -2656,10 +3018,23 @@ "win32" ] }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.4.tgz", + "integrity": "sha512-UF9KfsH9yEam0UjTwAgdK0anlQ7c8/pWPU2yVjyWcF1I1thABt6WXE47cI71pGiZ8wGvxohBoLnxM04L/wj8mQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.22.4.tgz", - "integrity": "sha512-j8pPKp53/lq9lMXN57S8cFz0MynJk8OWNuUnXct/9KCpKU7DgU3bYMJhwWmcqC0UU29p8Lr0/7KEVcaM6bf47Q==", + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.4.tgz", + "integrity": "sha512-bf9PtUa0u8IXDVxzRToFQKsNCRz9qLYfR/MpECxl4mRoWYjAeFjgxj1XdZr2M/GNVpT05p+LgQOHopYDlUu6/w==", "cpu": [ "x64" ], @@ -2829,6 +3204,20 @@ "sprintf-js": "~1.0.2" } }, + "node_modules/@standard-schema/spec": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", + "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@standard-schema/utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", + "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", + "dev": true, + "license": "MIT" + }, "node_modules/@storybook/addon-a11y": { "version": "10.0.7", "resolved": "https://registry.npmjs.org/@storybook/addon-a11y/-/addon-a11y-10.0.7.tgz", @@ -3335,6 +3724,78 @@ "@types/deep-eql": "*" } }, + "node_modules/@types/d3-array": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-shape": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz", + "integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/deep-eql": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", @@ -3357,31 +3818,6 @@ "@types/unist": "^2" } }, - "node_modules/@types/hoist-non-react-statics": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.5.tgz", - "integrity": "sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg==", - "dev": true, - "dependencies": { - "@types/react": "*", - "hoist-non-react-statics": "^3.3.0" - } - }, - "node_modules/@types/lodash": { - "version": "4.17.4", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.4.tgz", - "integrity": "sha512-wYCP26ZLxaT3R39kiN2+HcJ4kTd3U1waI/cY7ivWYqFP6pW3ZNpvi6Wd6PHZx7T/t8z0vlkXMg3QYLa7DZ/IJQ==", - "dev": true - }, - "node_modules/@types/lodash-es": { - "version": "4.17.12", - "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz", - "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==", - "dev": true, - "dependencies": { - "@types/lodash": "*" - } - }, "node_modules/@types/mdx": { "version": "2.0.13", "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.13.tgz", @@ -3394,7 +3830,6 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.12.tgz", "integrity": "sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==", "dev": true, - "peer": true, "dependencies": { "undici-types": "~5.26.4" } @@ -3454,17 +3889,6 @@ "resolved": "https://registry.npmjs.org/@types/sortablejs/-/sortablejs-1.15.8.tgz", "integrity": "sha512-b79830lW+RZfwaztgs1aVPgbasJ8e7AXtZYHTELNXZPsERt4ymJdjV4OccDbHQAvHrCcFpbF78jkm0R6h/pZVg==" }, - "node_modules/@types/styled-components": { - "version": "5.1.34", - "resolved": "https://registry.npmjs.org/@types/styled-components/-/styled-components-5.1.34.tgz", - "integrity": "sha512-mmiVvwpYklFIv9E8qfxuPyIt/OuyIrn6gMOAMOFUO3WJfSrSE+sGUoa4PiZj77Ut7bKZpaa6o1fBKS/4TOEvnA==", - "dev": true, - "dependencies": { - "@types/hoist-non-react-statics": "*", - "@types/react": "*", - "csstype": "^3.0.2" - } - }, "node_modules/@types/stylis": { "version": "4.2.5", "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.5.tgz", @@ -3476,6 +3900,13 @@ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==" }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", + "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", + "dev": true, + "license": "MIT" + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "7.16.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.16.1.tgz", @@ -4101,11 +4532,6 @@ "node": ">=10" } }, - "node_modules/aria-hidden/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" - }, "node_modules/aria-query": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", @@ -4145,12 +4571,6 @@ "node": ">=4" } }, - "node_modules/ast-types/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "dev": true - }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -4166,28 +4586,21 @@ "node": ">=4" } }, - "node_modules/babel-plugin-styled-components": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-2.1.4.tgz", - "integrity": "sha512-Xgp9g+A/cG47sUyRwwYxGM4bR/jDRg5N6it/8+HxCnbT5XNKSKDT9xm4oag/osgqjC2It/vH0yXsomOG6k558g==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-module-imports": "^7.22.5", - "@babel/plugin-syntax-jsx": "^7.22.5", - "lodash": "^4.17.21", - "picomatch": "^2.3.1" - }, - "peerDependencies": { - "styled-components": ">= 2" - } - }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/baseline-browser-mapping": { + "version": "2.8.29", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.29.tgz", + "integrity": "sha512-sXdt2elaVnhpDNRDz+1BDx1JQoJRuNk7oVlAlbGiFkLikHCAQiccexF/9e91zVi6RCgqspl04aP+6Cnl9zRLrA==", + "dev": true, + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, "node_modules/brace-expansion": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", @@ -4273,6 +4686,15 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/browser-style-dictionary/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "dev": true, + "engines": { + "node": ">= 12" + } + }, "node_modules/browser-style-dictionary/node_modules/fs-extra": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", @@ -4341,9 +4763,9 @@ } }, "node_modules/browserslist": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", - "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", + "version": "4.26.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.26.3.tgz", + "integrity": "sha512-lAUU+02RFBuCKQPj/P6NgjlbCnLBMp4UtgTx7vNHd3XSIJF87s9a5rA3aH2yw3GS9DqZAUbOtZdCCiZeVRqt0w==", "dev": true, "funding": [ { @@ -4360,10 +4782,11 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001587", - "electron-to-chromium": "^1.4.668", - "node-releases": "^2.0.14", - "update-browserslist-db": "^1.0.13" + "baseline-browser-mapping": "^2.8.9", + "caniuse-lite": "^1.0.30001746", + "electron-to-chromium": "^1.5.227", + "node-releases": "^2.0.21", + "update-browserslist-db": "^1.1.3" }, "bin": { "browserslist": "cli.js" @@ -4372,6 +4795,20 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/buffer-builder": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/buffer-builder/-/buffer-builder-0.2.0.tgz", + "integrity": "sha512-7VPMEPuYznPSoR21NE1zvd2Xna6c/CloiZCfcMXR1Jny6PjX0N4Nsa38zcBFo/FMK+BlA+FLKbJCQ0i2yxp+Xg==", + "dev": true + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "optional": true, + "peer": true + }, "node_modules/cac": { "version": "6.7.14", "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", @@ -4400,12 +4837,6 @@ "tslib": "^2.0.3" } }, - "node_modules/camel-case/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "dev": true - }, "node_modules/camelize": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", @@ -4416,9 +4847,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001620", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001620.tgz", - "integrity": "sha512-WJvYsOjd1/BYUY6SNGUosK9DUidBPDTnOARHp3fSmFO1ekdxaY6nKRttEVrfMmYi80ctS0kz1wiWmm14fVc3ew==", + "version": "1.0.30001750", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001750.tgz", + "integrity": "sha512-cuom0g5sdX6rw00qOoLNSFCJ9/mYIsuSOA+yzpDw8eopiFqcVwQvZHqov0vmEighRxX++cfC0Vg1G+1Iy/mSpQ==", "dev": true, "funding": [ { @@ -4446,12 +4877,6 @@ "upper-case-first": "^2.0.2" } }, - "node_modules/capital-case/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "dev": true - }, "node_modules/chai": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/chai/-/chai-5.2.1.tgz", @@ -4512,12 +4937,6 @@ "tslib": "^2.0.3" } }, - "node_modules/change-case/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "dev": true - }, "node_modules/character-entities": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz", @@ -4554,6 +4973,22 @@ "node": ">= 16" } }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "optional": true, + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/chromatic": { "version": "13.3.3", "resolved": "https://registry.npmjs.org/chromatic/-/chromatic-13.3.3.tgz", @@ -4582,6 +5017,14 @@ "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz", "integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==" }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "engines": { + "node": ">=6" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -4634,13 +5077,12 @@ } }, "node_modules/commander": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", - "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true, - "engines": { - "node": ">= 12" - } + "optional": true, + "peer": true }, "node_modules/computeds": { "version": "0.0.1", @@ -4665,12 +5107,6 @@ "upper-case": "^2.0.2" } }, - "node_modules/constant-case/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "dev": true - }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -4747,33 +5183,166 @@ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "devOptional": true }, - "node_modules/data-urls": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", - "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", "dev": true, + "license": "ISC", "dependencies": { - "whatwg-mimetype": "^4.0.0", - "whatwg-url": "^14.0.0" + "internmap": "1 - 2" }, "engines": { - "node": ">=18" - } - }, - "node_modules/date-fns": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", - "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", - "peer": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/kossnocorp" + "node": ">=12" } }, - "node_modules/dayjs": { - "version": "1.11.13", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", - "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/data-urls": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "dev": true, + "dependencies": { + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/date-fns": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", + "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", + "peer": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, + "node_modules/dayjs": { + "version": "1.11.18", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.18.tgz", + "integrity": "sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA==", + "optional": true, "peer": true }, "node_modules/de-indent": { @@ -4805,6 +5374,13 @@ "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==", "dev": true }, + "node_modules/decimal.js-light": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", + "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", + "dev": true, + "license": "MIT" + }, "node_modules/deep-eql": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", @@ -4847,6 +5423,19 @@ "node": ">=6" } }, + "node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "dev": true, + "optional": true, + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/detect-node-es": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", @@ -4892,12 +5481,6 @@ "tslib": "^2.0.3" } }, - "node_modules/dot-case/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "dev": true - }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -4905,9 +5488,9 @@ "dev": true }, "node_modules/electron-to-chromium": { - "version": "1.4.773", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.773.tgz", - "integrity": "sha512-87eHF+h3PlCRwbxVEAw9KtK3v7lWfc/sUDr0W76955AdYTG4bV/k0zrl585Qnj/skRMH2qOSiE+kqMeOQ+LOpw==", + "version": "1.5.237", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.237.tgz", + "integrity": "sha512-icUt1NvfhGLar5lSWH3tHNzablaA5js3HVHacQimfP8ViEBOQv+L7DKEuHdbTZ0SKCO1ogTJTIL1Gwk9S6Qvcg==", "dev": true }, "node_modules/emoji-regex": { @@ -4938,6 +5521,17 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/es-toolkit": { + "version": "1.42.0", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.42.0.tgz", + "integrity": "sha512-SLHIyY7VfDJBM8clz4+T2oquwTQxEzu263AyhVK4jREOAwJ+8eebaa4wM3nlvnAqhDrMm2EsA6hWHaQsMPQ1nA==", + "dev": true, + "license": "MIT", + "workspaces": [ + "docs", + "benchmarks" + ] + }, "node_modules/esbuild": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", @@ -4978,9 +5572,9 @@ } }, "node_modules/escalade": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", - "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "dev": true, "engines": { "node": ">=6" @@ -5455,6 +6049,13 @@ "node": ">=0.10.0" } }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "dev": true, + "license": "MIT" + }, "node_modules/exec-sh": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.2.2.tgz", @@ -5830,12 +6431,6 @@ "tslib": "^2.0.3" } }, - "node_modules/header-case/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "dev": true - }, "node_modules/highlight.js": { "version": "10.7.3", "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", @@ -5844,15 +6439,6 @@ "node": "*" } }, - "node_modules/hoist-non-react-statics": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", - "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", - "dev": true, - "dependencies": { - "react-is": "^16.7.0" - } - }, "node_modules/html-encoding-sniffer": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", @@ -5900,6 +6486,23 @@ "node": ">= 4" } }, + "node_modules/immer": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.2.0.tgz", + "integrity": "sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==", + "dev": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, + "node_modules/immutable": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.3.tgz", + "integrity": "sha512-+chQdDfvscSF1SJqv2gn4SRO2ZyS3xL3r7IW/wWEEzrzLisnOlKiQu5ytC/BVNcS15C39WT2Hg/bjKjDMcu+zg==", + "dev": true + }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -5968,6 +6571,16 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/invariant": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", @@ -6281,7 +6894,8 @@ "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true }, "node_modules/lodash.get": { "version": "4.4.2", @@ -6321,12 +6935,6 @@ "tslib": "^2.0.3" } }, - "node_modules/lower-case/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "dev": true - }, "node_modules/lowlight": { "version": "1.20.0", "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-1.20.0.tgz", @@ -6523,16 +7131,17 @@ "tslib": "^2.0.3" } }, - "node_modules/no-case/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "dev": true + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "dev": true, + "optional": true }, "node_modules/node-releases": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", - "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", + "version": "2.0.23", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.23.tgz", + "integrity": "sha512-cCmFDMSm26S6tQSDpBCg/NR8NENrVPhAJSf+XbxBG4rPFaaonlEoE9wHQmun+cls499TQGSb7ZyPBRlzgKfpeg==", "dev": true }, "node_modules/nwsapi": { @@ -6541,15 +7150,6 @@ "integrity": "sha512-QK0sRs7MKv0tKe1+5uZIQk/C8XGza4DAnztJG8iD+TpJIORARrCxczA738awHrZoHeTjSSoHqao2teO0dC/gFQ==", "dev": true }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -6616,12 +7216,6 @@ "tslib": "^2.0.3" } }, - "node_modules/param-case/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "dev": true - }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -6673,12 +7267,6 @@ "tslib": "^2.0.3" } }, - "node_modules/pascal-case/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "dev": true - }, "node_modules/path-browserify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", @@ -6695,12 +7283,6 @@ "tslib": "^2.0.3" } }, - "node_modules/path-case/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "dev": true - }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -6803,9 +7385,9 @@ } }, "node_modules/postcss": { - "version": "8.4.38", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", - "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", "dev": true, "funding": [ { @@ -6822,9 +7404,9 @@ } ], "dependencies": { - "nanoid": "^3.3.7", - "picocolors": "^1.0.0", - "source-map-js": "^1.2.0" + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12 || >=14" @@ -6913,17 +7495,6 @@ "node": ">=6" } }, - "node_modules/prop-types": { - "version": "15.8.1", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", - "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "dev": true, - "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" - } - }, "node_modules/property-information": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/property-information/-/property-information-5.6.0.tgz", @@ -7040,10 +7611,36 @@ } }, "node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.0.tgz", + "integrity": "sha512-x3Ax3kNSMIIkyVYhWPyO09bu0uttcAIoecO/um/rKGQ4EltYWVYtyiGkS/3xMynrbVQdS69Jhlv8FXUEZehlzA==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/react-redux": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", + "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/use-sync-external-store": "^0.0.6", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "@types/react": "^18.2.25 || ^19", + "react": "^18.0 || ^19", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "redux": { + "optional": true + } + } }, "node_modules/react-refresh": { "version": "0.14.2", @@ -7099,16 +7696,6 @@ } } }, - "node_modules/react-remove-scroll-bar/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" - }, - "node_modules/react-remove-scroll/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" - }, "node_modules/react-sortablejs": { "version": "6.1.4", "resolved": "https://registry.npmjs.org/react-sortablejs/-/react-sortablejs-6.1.4.tgz", @@ -7151,11 +7738,6 @@ } } }, - "node_modules/react-style-singleton/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" - }, "node_modules/react-syntax-highlighter": { "version": "15.5.0", "resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-15.5.0.tgz", @@ -7196,6 +7778,20 @@ "react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "optional": true, + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/recast": { "version": "0.23.7", "resolved": "https://registry.npmjs.org/recast/-/recast-0.23.7.tgz", @@ -7212,11 +7808,36 @@ "node": ">= 4" } }, - "node_modules/recast/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "dev": true + "node_modules/recharts": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-3.4.1.tgz", + "integrity": "sha512-35kYg6JoOgwq8sE4rhYkVWwa6aAIgOtT+Ob0gitnShjwUwZmhrmy7Jco/5kJNF4PnLXgt9Hwq+geEMS+WrjU1g==", + "dev": true, + "license": "MIT", + "workspaces": [ + "www" + ], + "dependencies": { + "@reduxjs/toolkit": "1.x.x || 2.x.x", + "clsx": "^2.1.1", + "decimal.js-light": "^2.5.1", + "es-toolkit": "^1.39.3", + "eventemitter3": "^5.0.1", + "immer": "^10.1.1", + "react-redux": "8.x.x || 9.x.x", + "reselect": "5.1.1", + "tiny-invariant": "^1.3.3", + "use-sync-external-store": "^1.2.2", + "victory-vendor": "^37.0.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } }, "node_modules/redent": { "version": "3.0.0", @@ -7243,14 +7864,31 @@ "node": ">=8" } }, - "node_modules/refractor": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/refractor/-/refractor-3.6.0.tgz", - "integrity": "sha512-MY9W41IOWxxk31o+YvFCNyNzdkc9M20NoZK5vq6jkv4I/uh2zkWcfudj0Q1fovjUQJrNewS9NMzeTtqPf+n5EA==", - "dependencies": { - "hastscript": "^6.0.0", - "parse-entities": "^2.0.0", - "prismjs": "~1.27.0" + "node_modules/redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", + "dev": true, + "license": "MIT" + }, + "node_modules/redux-thunk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "redux": "^5.0.0" + } + }, + "node_modules/refractor": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/refractor/-/refractor-3.6.0.tgz", + "integrity": "sha512-MY9W41IOWxxk31o+YvFCNyNzdkc9M20NoZK5vq6jkv4I/uh2zkWcfudj0Q1fovjUQJrNewS9NMzeTtqPf+n5EA==", + "dependencies": { + "hastscript": "^6.0.0", + "parse-entities": "^2.0.0", + "prismjs": "~1.27.0" }, "funding": { "type": "github", @@ -7276,6 +7914,13 @@ "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", "dev": true }, + "node_modules/reselect": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", + "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==", + "dev": true, + "license": "MIT" + }, "node_modules/resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", @@ -7361,12 +8006,12 @@ } }, "node_modules/rollup": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.22.4.tgz", - "integrity": "sha512-vD8HJ5raRcWOyymsR6Z3o6+RzfEPCnVLMFJ6vRslO1jt4LO6dUo5Qnpg7y4RkZFM2DMe3WUirkI5c16onjrc6A==", + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.4.tgz", + "integrity": "sha512-CLEVl+MnPAiKh5pl4dEWSyMTpuflgNQiLGhMv8ezD5W/qP8AKvmYpCOKRRNOh7oRKnauBZ4SyeYkMS+1VSyKwQ==", "dev": true, "dependencies": { - "@types/estree": "1.0.5" + "@types/estree": "1.0.8" }, "bin": { "rollup": "dist/bin/rollup" @@ -7376,31 +8021,31 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.22.4", - "@rollup/rollup-android-arm64": "4.22.4", - "@rollup/rollup-darwin-arm64": "4.22.4", - "@rollup/rollup-darwin-x64": "4.22.4", - "@rollup/rollup-linux-arm-gnueabihf": "4.22.4", - "@rollup/rollup-linux-arm-musleabihf": "4.22.4", - "@rollup/rollup-linux-arm64-gnu": "4.22.4", - "@rollup/rollup-linux-arm64-musl": "4.22.4", - "@rollup/rollup-linux-powerpc64le-gnu": "4.22.4", - "@rollup/rollup-linux-riscv64-gnu": "4.22.4", - "@rollup/rollup-linux-s390x-gnu": "4.22.4", - "@rollup/rollup-linux-x64-gnu": "4.22.4", - "@rollup/rollup-linux-x64-musl": "4.22.4", - "@rollup/rollup-win32-arm64-msvc": "4.22.4", - "@rollup/rollup-win32-ia32-msvc": "4.22.4", - "@rollup/rollup-win32-x64-msvc": "4.22.4", + "@rollup/rollup-android-arm-eabi": "4.52.4", + "@rollup/rollup-android-arm64": "4.52.4", + "@rollup/rollup-darwin-arm64": "4.52.4", + "@rollup/rollup-darwin-x64": "4.52.4", + "@rollup/rollup-freebsd-arm64": "4.52.4", + "@rollup/rollup-freebsd-x64": "4.52.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.52.4", + "@rollup/rollup-linux-arm-musleabihf": "4.52.4", + "@rollup/rollup-linux-arm64-gnu": "4.52.4", + "@rollup/rollup-linux-arm64-musl": "4.52.4", + "@rollup/rollup-linux-loong64-gnu": "4.52.4", + "@rollup/rollup-linux-ppc64-gnu": "4.52.4", + "@rollup/rollup-linux-riscv64-gnu": "4.52.4", + "@rollup/rollup-linux-riscv64-musl": "4.52.4", + "@rollup/rollup-linux-s390x-gnu": "4.52.4", + "@rollup/rollup-linux-x64-gnu": "4.52.4", + "@rollup/rollup-linux-x64-musl": "4.52.4", + "@rollup/rollup-openharmony-arm64": "4.52.4", + "@rollup/rollup-win32-arm64-msvc": "4.52.4", + "@rollup/rollup-win32-ia32-msvc": "4.52.4", + "@rollup/rollup-win32-x64-gnu": "4.52.4", + "@rollup/rollup-win32-x64-msvc": "4.52.4", "fsevents": "~2.3.2" } }, - "node_modules/rollup/node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", - "dev": true - }, "node_modules/rrweb-cssom": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz", @@ -7430,12 +8075,402 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "dev": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true }, + "node_modules/sass": { + "version": "1.93.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.93.0.tgz", + "integrity": "sha512-CQi5/AzCwiubU3dSqRDJ93RfOfg/hhpW1l6wCIvolmehfwgCI35R/0QDs1+R+Ygrl8jFawwwIojE2w47/mf94A==", + "dev": true, + "optional": true, + "dependencies": { + "chokidar": "^4.0.0", + "immutable": "^5.0.2", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" + } + }, + "node_modules/sass-embedded": { + "version": "1.93.0", + "resolved": "https://registry.npmjs.org/sass-embedded/-/sass-embedded-1.93.0.tgz", + "integrity": "sha512-dQACVfrbwKtvnrA0xH67YAdUYi6k7XcPg8uNF3DPf/VaJMQzduE1z5w3NFa9oVjtqXM4+FA9P7Qdv06Bzf614g==", + "dev": true, + "dependencies": { + "@bufbuild/protobuf": "^2.5.0", + "buffer-builder": "^0.2.0", + "colorjs.io": "^0.5.0", + "immutable": "^5.0.2", + "rxjs": "^7.4.0", + "supports-color": "^8.1.1", + "sync-child-process": "^1.0.2", + "varint": "^6.0.0" + }, + "bin": { + "sass": "dist/bin/sass.js" + }, + "engines": { + "node": ">=16.0.0" + }, + "optionalDependencies": { + "sass-embedded-all-unknown": "1.93.0", + "sass-embedded-android-arm": "1.93.0", + "sass-embedded-android-arm64": "1.93.0", + "sass-embedded-android-riscv64": "1.93.0", + "sass-embedded-android-x64": "1.93.0", + "sass-embedded-darwin-arm64": "1.93.0", + "sass-embedded-darwin-x64": "1.93.0", + "sass-embedded-linux-arm": "1.93.0", + "sass-embedded-linux-arm64": "1.93.0", + "sass-embedded-linux-musl-arm": "1.93.0", + "sass-embedded-linux-musl-arm64": "1.93.0", + "sass-embedded-linux-musl-riscv64": "1.93.0", + "sass-embedded-linux-musl-x64": "1.93.0", + "sass-embedded-linux-riscv64": "1.93.0", + "sass-embedded-linux-x64": "1.93.0", + "sass-embedded-unknown-all": "1.93.0", + "sass-embedded-win32-arm64": "1.93.0", + "sass-embedded-win32-x64": "1.93.0" + } + }, + "node_modules/sass-embedded-all-unknown": { + "version": "1.93.0", + "resolved": "https://registry.npmjs.org/sass-embedded-all-unknown/-/sass-embedded-all-unknown-1.93.0.tgz", + "integrity": "sha512-fBTnh5qgOyw0CGVaF2iPsIIRj40D9Mnf19WerixjmWwmYKaGhxd62STsuMt6t1dWS5lkUZWRgrJ+2biQiEcCBg==", + "cpu": [ + "!arm", + "!arm64", + "!riscv64", + "!x64" + ], + "dev": true, + "optional": true, + "dependencies": { + "sass": "1.93.0" + } + }, + "node_modules/sass-embedded-android-arm": { + "version": "1.93.0", + "resolved": "https://registry.npmjs.org/sass-embedded-android-arm/-/sass-embedded-android-arm-1.93.0.tgz", + "integrity": "sha512-oMm6RafXdpWDejufUs+GcgBSS/wa/iG1zRhwsCrkIkMLhqa34oN7xLkNs9Ieg337nlIryUBijwAVMFlAs/mgIg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-android-arm64": { + "version": "1.93.0", + "resolved": "https://registry.npmjs.org/sass-embedded-android-arm64/-/sass-embedded-android-arm64-1.93.0.tgz", + "integrity": "sha512-bwU+0uWUVoATaYAb9mnDj7GCEnNAIrinzT4UlA6GlicH+ELEZlNwVjaPJfdCyyYs8iOKuzUPfZrFZuwRCsXXqw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-android-riscv64": { + "version": "1.93.0", + "resolved": "https://registry.npmjs.org/sass-embedded-android-riscv64/-/sass-embedded-android-riscv64-1.93.0.tgz", + "integrity": "sha512-lKk7elql2abYeLY+wNBW8DB13W8An9JWlAr/BWOAtluz1RMsPVZwv0amQiP2PcR6HA02QDoLfRE/QpnPDHzCuw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-android-x64": { + "version": "1.93.0", + "resolved": "https://registry.npmjs.org/sass-embedded-android-x64/-/sass-embedded-android-x64-1.93.0.tgz", + "integrity": "sha512-wuyphs1VMS/PRXtCBLhA0bVo5nyKFCXKaVKMbqPylOTvoTHe7u0zxjWRN4eF5LTPVuQp0A+LYgJz07duzxwJew==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-darwin-arm64": { + "version": "1.93.0", + "resolved": "https://registry.npmjs.org/sass-embedded-darwin-arm64/-/sass-embedded-darwin-arm64-1.93.0.tgz", + "integrity": "sha512-lEb5J/jabesh16xdocRFgpzIa8GAZCLrdKtUnGbn9a4Y4WkEKHtUkvAm9ZtqE8YiuIm8PwHW/zBUKtZYoGYoYA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-darwin-x64": { + "version": "1.93.0", + "resolved": "https://registry.npmjs.org/sass-embedded-darwin-x64/-/sass-embedded-darwin-x64-1.93.0.tgz", + "integrity": "sha512-mo9OfKyNF6MiFf711c+QGR7aPpFqAC9FttiLKPYH3RRBZQZU/UcG4mbg+yXfKbhZrJmYngbGiTzE9B+xiOz27Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-arm": { + "version": "1.93.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-arm/-/sass-embedded-linux-arm-1.93.0.tgz", + "integrity": "sha512-wtO2vB8rMc5zF29xwC3AMgmBgNgm3i3/8zog5vQBD4yddqCJ93JcWDjdUqYmq0H/DLD/Z7q91j6X/YgPq1WuEg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-arm64": { + "version": "1.93.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-arm64/-/sass-embedded-linux-arm64-1.93.0.tgz", + "integrity": "sha512-bJclpjTeP/qCu7zYLZQXROx4xIT3x+qfj/q92fripV9L9Oj2khfUm+2nW0Cq7DS6UrHphrWZ9QSnVYFhkCKtEA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-musl-arm": { + "version": "1.93.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-arm/-/sass-embedded-linux-musl-arm-1.93.0.tgz", + "integrity": "sha512-mMGAy+2VLLTMDPDG/mfzMmoy09potXp/ZRPRsyJEYVjF0rQij6Iss3qsZbCjVJa4atLwBtPJ14M0NvqpAa2WIg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-musl-arm64": { + "version": "1.93.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-arm64/-/sass-embedded-linux-musl-arm64-1.93.0.tgz", + "integrity": "sha512-VH0zFGqsTy+lThHAm3y8Dpd/X4nC5DLJvk66+mJTg7rwblRhfPpsVO6n8QHeN5ZV1ATTnLh/PbZ7uEPiyAg2wg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-musl-riscv64": { + "version": "1.93.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-riscv64/-/sass-embedded-linux-musl-riscv64-1.93.0.tgz", + "integrity": "sha512-/a+MvExFEKvwPXyZsQ8b1DWYJMpTnXSdwpe9pDNkdTIcliMAtP952krCx14nBP0UqqNoU/TetyMR8H0WwyeJEA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-musl-x64": { + "version": "1.93.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-x64/-/sass-embedded-linux-musl-x64-1.93.0.tgz", + "integrity": "sha512-o168nV9QI5U+2LFBMmMecWzu6yJ7WJZZfQGlo4Frvg9vC3Em3W02GfAel+g9leJg+0PDnpJLqOsPdrngg25T/Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-riscv64": { + "version": "1.93.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-riscv64/-/sass-embedded-linux-riscv64-1.93.0.tgz", + "integrity": "sha512-KYHED49coJQT633cBbqBfBOPmRe3yNbE+D2kqMONADBqzGyxHZpQRStCenhPmDabVLI4fgc3fn//6ubqH724jA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-x64": { + "version": "1.93.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-x64/-/sass-embedded-linux-x64-1.93.0.tgz", + "integrity": "sha512-9OD9OlZ61dmz/BbW4n29l3v74//ibiQCmWu8YBoXVgxxgcbi+2CFv+vRE8guA73BgEdPComw0tpgD1FkW3v12g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-unknown-all": { + "version": "1.93.0", + "resolved": "https://registry.npmjs.org/sass-embedded-unknown-all/-/sass-embedded-unknown-all-1.93.0.tgz", + "integrity": "sha512-Hh9OPBMg+i1g8OzQyOtQuJg/3ncup4Z+FHdXNzPIeFXcIeS+TVuVQyvJfnB+hYgvVGyBJ+9ekuUYzB+1zA82nw==", + "dev": true, + "optional": true, + "os": [ + "!android", + "!darwin", + "!linux", + "!win32" + ], + "dependencies": { + "sass": "1.93.0" + } + }, + "node_modules/sass-embedded-win32-arm64": { + "version": "1.93.0", + "resolved": "https://registry.npmjs.org/sass-embedded-win32-arm64/-/sass-embedded-win32-arm64-1.93.0.tgz", + "integrity": "sha512-3SNRTxBVk+c0Oyd4gCp4/KAQ+S6B9S5ihq5dxMMfWpvoQSUqn6mqhkEFrofG1oNlP7KsA2UzhTnFGDRid1An+A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-win32-x64": { + "version": "1.93.0", + "resolved": "https://registry.npmjs.org/sass-embedded-win32-x64/-/sass-embedded-win32-x64-1.93.0.tgz", + "integrity": "sha512-6/RJGOdm3bwe71YJaYanQ81I6KA//T/a+MnKlRpP5zk5fy2ygAIGNeNr2ENEBu/KZCuFg7KY49g46v+hPKT6Ow==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded/node_modules/colorjs.io": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/colorjs.io/-/colorjs.io-0.5.2.tgz", + "integrity": "sha512-twmVoizEW7ylZSN32OgKdXRmo1qg+wT5/6C3xu5b9QsWzSFAhHLn2xd8ro0diCsKfCj1RdaTP/nrcW+vAoQPIw==", + "dev": true + }, + "node_modules/sass-embedded/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/sass-embedded/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, "node_modules/saxes": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", @@ -7476,12 +8511,6 @@ "upper-case-first": "^2.0.2" } }, - "node_modules/sentence-case/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "dev": true - }, "node_modules/shallowequal": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", @@ -7546,12 +8575,6 @@ "tslib": "^2.0.3" } }, - "node_modules/snake-case/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "dev": true - }, "node_modules/sortablejs": { "version": "1.15.2", "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.2.tgz", @@ -7576,6 +8599,18 @@ "node": ">=0.10.0" } }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, "node_modules/space-separated-tokens": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz", @@ -7941,6 +8976,15 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/style-dictionary/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "dev": true, + "engines": { + "node": ">= 12" + } + }, "node_modules/style-dictionary/node_modules/fs-extra": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", @@ -7977,9 +9021,9 @@ } }, "node_modules/styled-components": { - "version": "6.1.11", - "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.11.tgz", - "integrity": "sha512-Ui0jXPzbp1phYij90h12ksljKGqF8ncGx+pjrNPsSPhbUUjWT2tD1FwGo2LF6USCnbrsIhNngDfodhxbegfEOA==", + "version": "6.1.19", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.19.tgz", + "integrity": "sha512-1v/e3Dl1BknC37cXMhwGomhO8AkYmN41CqyX9xhUDxry1ns3BFQy2lLDRQXJRdVVWB9OHemv/53xaStimvWyuA==", "dev": true, "dependencies": { "@emotion/is-prop-valid": "1.2.2", @@ -7987,7 +9031,7 @@ "@types/stylis": "4.2.5", "css-to-react-native": "3.2.0", "csstype": "3.1.3", - "postcss": "8.4.38", + "postcss": "8.4.49", "shallowequal": "1.1.0", "stylis": "4.3.2", "tslib": "2.6.2" @@ -8004,6 +9048,40 @@ "react-dom": ">= 16.8.0" } }, + "node_modules/styled-components/node_modules/postcss": { + "version": "8.4.49", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", + "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/styled-components/node_modules/stylis": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.2.tgz", + "integrity": "sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg==", + "dev": true + }, "node_modules/styled-components/node_modules/tslib": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", @@ -8011,9 +9089,9 @@ "dev": true }, "node_modules/stylis": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.2.tgz", - "integrity": "sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg==", + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz", + "integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==", "dev": true }, "node_modules/supports-color": { @@ -8046,6 +9124,47 @@ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", "dev": true }, + "node_modules/sync-child-process": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/sync-child-process/-/sync-child-process-1.0.2.tgz", + "integrity": "sha512-8lD+t2KrrScJ/7KXCSyfhT3/hRq78rC0wBFqNJXv3mZyn6hW2ypM05JmlSvtqRbeq6jqA94oHbxAr2vYsJ8vDA==", + "dev": true, + "dependencies": { + "sync-message-port": "^1.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/sync-message-port": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sync-message-port/-/sync-message-port-1.1.3.tgz", + "integrity": "sha512-GTt8rSKje5FilG+wEdfCkOcLL7LWqpMlr2c3LRuKt/YXxcJ52aGSbGBAdI4L3aaqfrBt6y711El53ItyH1NWzg==", + "dev": true, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/terser": { + "version": "5.44.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.44.0.tgz", + "integrity": "sha512-nIVck8DK+GM/0Frwd+nIhZ84pR/BX7rmXMfYwyg+Sri5oGVE99/E3KvXqpC2xHFxyqXyGHTKBSioxxplrO4I4w==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.15.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -8251,6 +9370,11 @@ "node": ">=6" } }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -8280,8 +9404,7 @@ "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true, - "peer": true + "dev": true }, "node_modules/universalify": { "version": "2.0.1", @@ -8322,9 +9445,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.0.16", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.16.tgz", - "integrity": "sha512-KVbTxlBYlckhF5wgfyZXTWnMn7MMZjMu9XG8bPlliUOP9ThaF4QnhP8qrjrH7DRzHfSk0oQv1wToW+iA5GajEQ==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", "dev": true, "funding": [ { @@ -8341,8 +9464,8 @@ } ], "dependencies": { - "escalade": "^3.1.2", - "picocolors": "^1.0.1" + "escalade": "^3.2.0", + "picocolors": "^1.1.1" }, "bin": { "update-browserslist-db": "cli.js" @@ -8369,18 +9492,6 @@ "tslib": "^2.0.3" } }, - "node_modules/upper-case-first/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "dev": true - }, - "node_modules/upper-case/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "dev": true - }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -8420,11 +9531,6 @@ } } }, - "node_modules/use-callback-ref/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" - }, "node_modules/use-sidecar": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.2.tgz", @@ -8446,10 +9552,15 @@ } } }, - "node_modules/use-sidecar/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", @@ -8466,6 +9577,35 @@ "node": ">= 0.10" } }, + "node_modules/varint": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz", + "integrity": "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==", + "dev": true + }, + "node_modules/victory-vendor": { + "version": "37.3.6", + "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-37.3.6.tgz", + "integrity": "sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==", + "dev": true, + "license": "MIT AND ISC", + "dependencies": { + "@types/d3-array": "^3.0.3", + "@types/d3-ease": "^3.0.0", + "@types/d3-interpolate": "^3.0.1", + "@types/d3-scale": "^4.0.2", + "@types/d3-shape": "^3.1.0", + "@types/d3-time": "^3.0.0", + "@types/d3-timer": "^3.0.0", + "d3-array": "^3.1.6", + "d3-ease": "^3.0.1", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-shape": "^3.1.0", + "d3-time": "^3.0.0", + "d3-timer": "^3.0.1" + } + }, "node_modules/vite": { "version": "5.4.21", "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", @@ -8575,35 +9715,6 @@ } } }, - "node_modules/vite/node_modules/postcss": { - "version": "8.5.3", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", - "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.8", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, "node_modules/vitest": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.0.3.tgz", diff --git a/package.json b/package.json index a28a666b6..f65a3c738 100644 --- a/package.json +++ b/package.json @@ -4,9 +4,21 @@ "description": "Official ClickHouse design system react library", "type": "module", "license": "Apache-2.0", + "sideEffects": [ + "**/*.css", + "**/*.scss", + "./dist/cui.css", + "./dist/cui-components.css", + "./dist/cui-default-theme.css", + "./src/theme/global.scss" + ], "files": [ - "dist" + "dist", + "bin" ], + "bin": { + "click-ui": "./bin/click-ui.js" + }, "exports": { ".": { "types": "./dist/index.d.ts", @@ -17,7 +29,14 @@ "types": "./dist/index.d.ts", "import": "./dist/click-ui.bundled.es.js", "require": "./dist/click-ui.bundled.umd.js" - } + }, + "./theme": { + "types": "./dist/theme/index.d.ts" + }, + "./cui.css": "./dist/cui.css", + "./cui-components.css": "./dist/cui-components.css", + "./cui-default-theme.css": "./dist/cui-default-theme.css", + "./style.css": "./dist/cui-components.css" }, "main": "./dist/click-ui.umd.js", "module": "./dist/click-ui.es.js", @@ -33,12 +52,16 @@ }, "homepage": "https://clickhouse.com", "scripts": { - "build": "tsc && vite build && yarn build:bundled", + "build": "npm run generate-theme-css && tsc && vite build && npm run build:bundled && npm run copy-css-files", "build:bundled": "vite build -- bundled", + "copy-css-files": "npm run rename-css && cp src/styles/cui-default-theme.css dist/ && npm run build-combined-css", + "rename-css": "mv dist/style.css dist/cui-components.css", + "build-combined-css": "echo '/* Click UI - Combined CSS */\n@import \"./cui-default-theme.css\";\n@import \"./cui-components.css\";' > dist/cui.css", "build-storybook": "storybook build", "build:watch": "watch 'npm run build' ./src", "chromatic": "npx chromatic", "dev": "vite", + "generate-theme-css": "npx tsx bin/commands/build-default-theme.ts", "generate-tokens": "node build-tokens.js", "lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0", "prettify": "prettier --write \"src/**/*.{js,jsx,ts,tsx}\" --config .prettierrc", @@ -63,12 +86,11 @@ "@radix-ui/react-popover": "^1.0.7", "@radix-ui/react-popper": "^1.1.3", "@radix-ui/react-radio-group": "^1.1.3", - "@radix-ui/react-separator": "^1.0.3", "@radix-ui/react-switch": "^1.0.2", "@radix-ui/react-tabs": "^1.0.4", "@radix-ui/react-toast": "^1.1.5", "@radix-ui/react-tooltip": "^1.0.7", - "lodash": "^4.17.21", + "clsx": "^2.1.1", "react-sortablejs": "^6.1.4", "react-syntax-highlighter": "^15.5.0", "react-virtualized-auto-sizer": "^1.0.20", @@ -85,17 +107,15 @@ "@testing-library/react": "^15.0.7", "@testing-library/user-event": "^14.5.2", "@tokens-studio/sd-transforms": "^0.10.3", - "@types/lodash-es": "^4.17.7", + "@types/node": "^20.0.0", "@types/react": "^18.3.2", "@types/react-dom": "^18.3.0", "@types/react-syntax-highlighter": "^15.5.13", "@types/react-window": "^1.8.8", "@types/sortablejs": "^1.15.2", - "@types/styled-components": "^5.1.34", "@typescript-eslint/eslint-plugin": "^7.16.1", "@typescript-eslint/parser": "^7.16.1", "@vitejs/plugin-react": "^4.2.1", - "babel-plugin-styled-components": "^2.1.4", "chromatic": "^13.3.3", "eslint": "^8.57.0", "eslint-plugin-prefer-arrow-functions": "^3.3.2", @@ -104,7 +124,9 @@ "eslint-plugin-storybook": "^10.0.7", "jsdom": "^24.0.0", "prettier": "^3.6.2", - "prop-types": "^15.8.1", + "recharts": "^3.4.1", + "rollup": "^4.52.4", + "sass-embedded": "^1.93.0", "storybook": "^10.0.7", "storybook-addon-pseudo-states": "^10.0.7", "styled-components": "^6.1.11", @@ -117,9 +139,13 @@ "watch": "^1.0.2" }, "peerDependencies": { - "dayjs": "^1.11.13", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "styled-components": ">= 5" + "dayjs": "^1.11.18", + "react": "^18.2.0 || ^19.0.0", + "react-dom": "^18.2.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "dayjs": { + "optional": true + } } } diff --git a/setupTests.ts b/setupTests.ts index 7a79577a8..64201b3e8 100644 --- a/setupTests.ts +++ b/setupTests.ts @@ -1,3 +1,18 @@ import { TextEncoder } from "util"; global.TextEncoder = TextEncoder; + +// Mock window.matchMedia for tests +Object.defineProperty(window, "matchMedia", { + writable: true, + value: (query: string) => ({ + matches: false, + media: query, + onchange: null, + addListener: () => {}, // deprecated + removeListener: () => {}, // deprecated + addEventListener: () => {}, + removeEventListener: () => {}, + dispatchEvent: () => true, + }), +}); diff --git a/src/App.module.css b/src/App.module.scss similarity index 52% rename from src/App.module.css rename to src/App.module.scss index 3904fef61..520e6f427 100644 --- a/src/App.module.css +++ b/src/App.module.scss @@ -1,3 +1,8 @@ +.cuiBackgroundWrapper { + background: var(--global-color-background-default); + padding: 6rem; +} + .main { display: flex; flex-direction: column; @@ -15,22 +20,22 @@ width: 100%; z-index: 2; font-family: var(--font-mono); -} -.description a { - display: flex; - justify-content: center; - align-items: center; - gap: 0.5rem; -} + a { + display: flex; + justify-content: center; + align-items: center; + gap: 0.5rem; + } -.description p { - position: relative; - margin: 0; - padding: 1rem; - background-color: rgba(var(--callout-rgb), 0.5); - border: 1px solid rgba(var(--callout-border-rgb), 0.3); - border-radius: var(--border-radius); + p { + position: relative; + margin: 0; + padding: 1rem; + background-color: rgba(var(--callout-rgb), 0.5); + border: 1px solid rgba(var(--callout-border-rgb), 0.3); + border-radius: var(--border-radius); + } } .code { @@ -51,24 +56,24 @@ background: rgba(var(--card-rgb), 0); border: 1px solid rgba(var(--card-border-rgb), 0); transition: background 200ms, border 200ms; -} -.card span { - display: inline-block; - transition: transform 200ms; -} + span { + display: inline-block; + transition: transform 200ms; + } -.card h2 { - font-weight: 600; - margin-bottom: 0.7rem; -} + h2 { + font-weight: 600; + margin-bottom: 0.7rem; + } -.card p { - margin: 0; - opacity: 0.6; - font-size: 0.9rem; - line-height: 1.5; - max-width: 30ch; + p { + margin: 0; + opacity: 0.6; + font-size: 0.9rem; + line-height: 1.5; + max-width: 30ch; + } } .center { @@ -77,44 +82,52 @@ align-items: center; position: relative; padding: 4rem 0; -} -.center::before { - background: var(--secondary-glow); - border-radius: 50%; - width: 480px; - height: 360px; - margin-left: -400px; -} + &::before { + background: var(--secondary-glow); + border-radius: 50%; + width: 480px; + height: 360px; + margin-left: -400px; + } -.center::after { - background: var(--primary-glow); - width: 240px; - height: 180px; - z-index: -1; -} + &::after { + background: var(--primary-glow); + width: 240px; + height: 180px; + z-index: -1; + } -.center::before, -.center::after { - content: ''; - left: 50%; - position: absolute; - filter: blur(45px); - transform: translateZ(0); + &::before, + &::after { + content: ''; + left: 50%; + position: absolute; + filter: blur(45px); + transform: translateZ(0); + } } .logo { position: relative; } + +.flexWrap { + display: flex; + flex-flow: row wrap; + gap: 10px; + padding: 10px 0; +} + /* Enable hover only on non-touch devices */ @media (hover: hover) and (pointer: fine) { .card:hover { background: rgba(var(--card-rgb), 0.1); border: 1px solid rgba(var(--card-border-rgb), 0.15); - } - .card:hover span { - transform: translateX(4px); + span { + transform: translateX(4px); + } } } @@ -139,65 +152,65 @@ .card { padding: 1rem 2.5rem; - } - .card h2 { - margin-bottom: 0.5rem; + h2 { + margin-bottom: 0.5rem; + } } .center { padding: 8rem 0 6rem; - } - .center::before { - transform: none; - height: 300px; + &::before { + transform: none; + height: 300px; + } } .description { font-size: 0.8rem; - } - - .description a { - padding: 1rem; - } - .description p, - .description div { - display: flex; - justify-content: center; - position: fixed; - width: 100%; - } - - .description p { - align-items: center; - inset: 0 0 auto; - padding: 2rem 1rem 1.4rem; - border-radius: 0; - border: none; - border-bottom: 1px solid rgba(var(--callout-border-rgb), 0.25); - background: linear-gradient( - to bottom, - rgba(var(--background-start-rgb), 1), - rgba(var(--callout-rgb), 0.5) - ); - background-clip: padding-box; - backdrop-filter: blur(24px); - } - - .description div { - align-items: flex-end; - pointer-events: none; - inset: auto 0 0; - padding: 2rem; - height: 200px; - background: linear-gradient( - to bottom, - transparent 0%, - rgb(var(--background-end-rgb)) 40% - ); - z-index: 1; + a { + padding: 1rem; + } + + p, + div { + display: flex; + justify-content: center; + position: fixed; + width: 100%; + } + + p { + align-items: center; + inset: 0 0 auto; + padding: 2rem 1rem 1.4rem; + border-radius: 0; + border: none; + border-bottom: 1px solid rgba(var(--callout-border-rgb), 0.25); + background: linear-gradient( + to bottom, + rgba(var(--background-start-rgb), 1), + rgba(var(--callout-rgb), 0.5) + ); + background-clip: padding-box; + backdrop-filter: blur(24px); + } + + div { + align-items: flex-end; + pointer-events: none; + inset: auto 0 0; + padding: 2rem; + height: 200px; + background: linear-gradient( + to bottom, + transparent 0%, + rgb(var(--background-end-rgb)) 40% + ); + z-index: 1; + } } } @@ -225,11 +238,4 @@ to { transform: rotate(0deg); } -} - -.flexWrap { - display: flex; - flex-flow: row wrap; - gap: 10px; - padding: 10px 0; -} +} \ No newline at end of file diff --git a/src/App.tsx b/src/App.tsx index e57ee0e37..6e3b94655 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,11 +1,9 @@ import { useRef, useState } from "react"; import "@/styles/globals.css"; -import "./styles/variables.css"; -import "./styles/variables.dark.css"; - -import styles from "./App.module.css"; -import { ThemeName } from "./theme"; +import styles from "./App.module.scss"; +import { ThemeName } from "@/theme"; +import { ClickUIProvider } from "@/theme/ClickUIProvider"; import { Accordion, Alert, @@ -13,7 +11,6 @@ import { Badge, Button, ButtonGroup, - ClickUIProvider, CardSecondary, Checkbox, DangerAlert, @@ -49,15 +46,9 @@ import { } from "@/components"; import { Dialog } from "@/components/Dialog/Dialog"; import { ConfirmationDialog } from "@/components/ConfirmationDialog/ConfirmationDialog"; -import { ProgressBar } from "./components/ProgressBar/ProgressBar"; -import GridExample from "./examples/GridExample"; -import MultiAccordionDemo from "./components/MultiAccordion/MultiAccordionDemo"; -import { styled } from "styled-components"; - -const BackgroundWrapper = styled.div` - background: ${({ theme }) => theme.global.color.background.default}; - padding: 6rem; -`; +import { ProgressBar } from "@/components/ProgressBar/ProgressBar"; +import GridExample from "@/examples/GridExample"; +import MultiAccordionDemo from "@/components/MultiAccordion/MultiAccordionDemo"; const headers: Array = [ { label: "Company", isSortable: true, sortDir: "asc" }, { label: "Contact", isSortable: true, sortDir: "desc", sortPosition: "start" }, @@ -111,6 +102,34 @@ const App = () => { theme={currentTheme} config={{ tooltip: { delayDuration: 0 } }} > +
+ + + +
- - - + )} {showIcon && ( - - - + )} - - {title && {title}} - {text} - +
+ {title &&
{title}
} +
{text}
+
{dismissible && ( - @@ -85,87 +99,12 @@ const Alert = ({ name="cross" aria-label="close" /> - + )} - + ) : null; }; -const Wrapper = styled.div<{ - $state: AlertState; - $size: AlertSize; - $type: AlertType; -}>` - display: flex; - border-radius: ${({ $type, theme }) => - $type === "banner" ? theme.sizes[0] : theme.click.alert.radii.end}; - justify-content: ${({ $type }) => ($type === "banner" ? "center" : "start")}; - overflow: hidden; - background-color: ${({ $state = "neutral", theme }) => - theme.click.alert.color.background[$state]}; - color: ${({ $state = "neutral", theme }) => theme.click.alert.color.text[$state]}; - width: 100%; -`; - -const IconWrapper = styled.div<{ - $state: AlertState; - $size: AlertSize; - $type: AlertType; -}>` - display: flex; - align-items: center; - background-color: ${({ $state = "neutral", $type, theme }) => - $type === "banner" ? "none" : theme.click.alert.color.iconBackground[$state]}; - ${({ $state = "neutral", $size, theme }) => ` - color: ${theme.click.alert.color.iconForeground[$state]}; - padding: ${theme.click.alert[$size].space.y} ${theme.click.alert[$size].space.x}; - `} -`; - -const StyledIcon = styled(Icon)<{ $size: AlertSize }>` - ${({ $size, theme }) => ` - height: ${theme.click.alert[$size].icon.height}; - width: ${theme.click.alert[$size].icon.width}; - `} -`; -const TextWrapper = styled.div<{ $state: AlertState; $size: AlertSize }>` - display: flex; - flex-flow: column; - word-break: break-word; - ${({ $size, theme }) => ` - gap: ${theme.click.alert[$size].space.gap}; - padding: ${theme.click.alert[$size].space.y} ${theme.click.alert[$size].space.x}; - `} - - a, - a:focus, - a:visited, - a:hover { - font: inherit; - color: inherit; - text-decoration: underline; - } -`; - -const Title = styled.h6<{ $size: AlertSize }>` - margin: 0; - font: ${({ theme, $size }) => theme.click.alert[$size].typography.title.default}; -`; -const Text = styled.div<{ $size: AlertSize }>` - margin: 0; - font: ${({ theme, $size }) => theme.click.alert[$size].typography.text.default}; -`; - -const DismissWrapper = styled.button` - display: flex; - align-items: center; - margin-left: auto; - border: none; - background-color: transparent; - color: inherit; - cursor: pointer; -`; - const DangerAlert = (props: AlertProps) => ( { childrenType: "children" | "options"; diff --git a/src/components/AutoComplete/AutoComplete.test.tsx b/src/components/AutoComplete/AutoComplete.test.tsx index 60db6cc57..d7e72707f 100644 --- a/src/components/AutoComplete/AutoComplete.test.tsx +++ b/src/components/AutoComplete/AutoComplete.test.tsx @@ -1,7 +1,7 @@ import { act, fireEvent } from "@testing-library/react"; import { AutoComplete, AutoCompleteProps } from "@/components"; import { renderCUI } from "@/utils/test-utils"; -import { selectOptions } from "../Select/selectOptions"; +import { selectOptions } from "@/components/Select/selectOptions"; describe("AutoComplete", () => { beforeAll(() => { window.HTMLElement.prototype.scrollIntoView = vi.fn(); diff --git a/src/components/AutoComplete/AutoComplete.tsx b/src/components/AutoComplete/AutoComplete.tsx index 78cf10344..d168ad469 100644 --- a/src/components/AutoComplete/AutoComplete.tsx +++ b/src/components/AutoComplete/AutoComplete.tsx @@ -1,3 +1,5 @@ +"use client"; + import { Children, FunctionComponent, @@ -22,14 +24,15 @@ import { SearchField, Separator, } from "@/components"; -import { styled } from "styled-components"; -import { GenericMenuItem } from "../GenericMenu"; +import clsx from "clsx"; +import { GenericMenuItem } from "@/components/GenericMenu"; import { useOption, useSearch } from "./useOption"; -import IconWrapper from "../IconWrapper/IconWrapper"; +import { IconWrapper } from "@/components"; import { OptionContext } from "./OptionContext"; import { mergeRefs } from "@/utils/mergeRefs"; import { getTextFromNodes } from "@/lib/getTextFromNodes"; import AutoCompleteOptionList from "./AutoCompleteOptionList"; +import styles from "./AutoComplete.module.scss"; type DivProps = HTMLAttributes; interface SelectItemComponentProps @@ -108,78 +111,6 @@ type SelectItemObject = { export type AutoCompleteProps = (SelectOptionType & Props) | (SelectChildrenType & Props); -export const SelectPopoverRoot = styled(Root)` - width: 100%; - ${({ theme }) => ` - border: 1px solid ${theme.click.genericMenu.item.color.stroke.default}; - background: ${theme.click.genericMenu.item.color.background.default}; - box-shadow: 0px 1px 3px 0px rgba(16, 24, 40, 0.1), - 0px 1px 2px 0px rgba(16, 24, 40, 0.06); - border-radius: 0.25rem; - `} - overflow: hidden; - display: flex; - padding: 0.5rem 0rem; - align-items: flex-start; - gap: 0.625rem; -`; - -const PopoverContent = styled(Content)` - width: var(--radix-popover-trigger-width); - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 5px; -`; -const SelectGroupContainer = styled.div` - display: flex; - flex-direction: column; - align-items: flex-start; - justify-content: center; - width: -webkit-fill-available; - width: fill-available; - width: stretch; - overflow: hidden; - background: transparent; - &[aria-selected] { - outline: none; - } - - ${({ theme }) => ` - font: ${theme.click.genericMenu.item.typography.sectionHeader.default}; - color: ${theme.click.genericMenu.item.color.text.muted}; - `}; - &[hidden] { - display: none; - } -`; - -const SelectGroupName = styled.div` - display: flex; - width: 100%; - flex-direction: column; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - ${({ theme }) => ` - font: ${theme.click.genericMenu.item.typography.sectionHeader.default}; - color: ${theme.click.genericMenu.item.color.text.muted}; - padding: ${theme.click.genericMenu.sectionHeader.space.top} ${theme.click.genericMenu.item.space.x} ${theme.click.genericMenu.sectionHeader.space.bottom}; - gap: ${theme.click.genericMenu.item.space.gap}; - border-bottom: 1px solid ${theme.click.genericMenu.item.color.stroke.default}; - `} -`; - -const SelectGroupContent = styled.div` - width: inherit; -`; - -const SelectListContent = styled.div` - width: inherit; - overflow: overlay; - flex: 1; -`; - type CallbackProps = SelectItemObject & { nodeProps: SelectItemProps; }; @@ -223,37 +154,6 @@ const childrenToComboboxItemArray = ( return []; }); }; -const SelectNoDataContainer = styled.div` - border: none; - display: block; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - text-align: left; - cursor: default; - &[hidden="true"] { - display: none; - } - ${({ theme }) => ` - font: ${theme.click.genericMenu.button.typography.label.default} - padding: ${theme.click.genericMenu.button.space.y} ${theme.click.genericMenu.item.space.x}; - background: ${theme.click.genericMenu.button.color.background.default}; - color: ${theme.click.genericMenu.button.color.label.default}; - `} -`; - -const SelectList = styled.div` - display: flex; - flex-direction: column; - width: inherit; - max-height: var(--radix-popover-content-available-height); - ${({ theme }) => ` - border: 1px solid ${theme.click.genericMenu.item.color.stroke.default}; - background: ${theme.click.genericMenu.item.color.background.default}; - box-shadow: ${theme.click.genericMenu.panel.shadow.default}; - border-radius: 0.25rem; - `} -`; export const AutoComplete = ({ onSelect: onSelectProp, @@ -483,7 +383,7 @@ export const AutoComplete = ({ }; return ( - @@ -505,7 +405,8 @@ export const AutoComplete = ({ - { @@ -523,8 +424,8 @@ export const AutoComplete = ({ }} onFocusOutside={onFocusOutside} > - - +
+
{options && options.length > 0 ? ( - +
{visibleList.current.length === 0 && ( - +
No Options found{search.length > 0 ? ` for "${search}" ` : ""} - +
)} - - +
+
-
+ ); }; @@ -552,7 +456,8 @@ export const Group = forwardRef( ({ children, heading, ...props }, forwardedRef) => { useSearch(); return ( - ( }, ])} > - {heading} - {children} - +
{heading}
+
{children}
+ ); } ); Group.displayName = "AutoComplete.Group"; -const CheckIcon = styled.svg<{ $showCheck: boolean }>` - opacity: ${({ $showCheck }) => ($showCheck ? 1 : 0)}; -`; - export const Item = forwardRef( ( { @@ -639,11 +540,10 @@ export const Item = forwardRef( > {label ?? children} - {separator && } diff --git a/src/components/Avatar/Avatar.module.scss b/src/components/Avatar/Avatar.module.scss new file mode 100644 index 000000000..517131358 --- /dev/null +++ b/src/components/Avatar/Avatar.module.scss @@ -0,0 +1,65 @@ +@use "cui-mixins" as mixins; +@use "cui-variants" as variants; + +.cuiAvatarRoot { + width: var(--click-avatar-size-width); + height: var(--click-avatar-size-height); + display: inline-flex; + align-items: center; + justify-content: center; + vertical-align: middle; + overflow: hidden; + user-select: none; + background-color: var(--click-avatar-color-background-default); + color: var(--click-avatar-color-text-default); + border-radius: var(--click-avatar-radii-all); + + &:active { + background-color: var(--click-avatar-color-background-active); + color: var(--click-avatar-color-text-active); + } + + &:hover { + background-color: var(--click-avatar-color-background-hover); + color: var(--click-avatar-color-text-hover); + } +} + +.cuiAvatarImage { + width: 100%; + height: 100%; + object-fit: cover; + border-radius: inherit; +} + +.cuiAvatarFallback { + width: var(--click-avatar-size-label-width); + display: inline-flex; + align-items: center; + justify-content: center; + + // Text size variants - using :where() for low specificity + @include variants.variant('cuiTextSizeMd') { + font: var(--click-avatar-typography-label-md-default); + + .cuiAvatarRoot:active & { + font: var(--click-avatar-typography-label-md-active); + } + + .cuiAvatarRoot:hover & { + font: var(--click-avatar-typography-label-md-hover); + } + } + + @include variants.variant('cuiTextSizeSm') { + font: var(--click-avatar-typography-label-sm-default); + + .cuiAvatarRoot:active & { + font: var(--click-avatar-typography-label-sm-active); + } + + .cuiAvatarRoot:hover & { + font: var(--click-avatar-typography-label-sm-hover); + } + } +} diff --git a/src/components/Avatar/Avatar.tsx b/src/components/Avatar/Avatar.tsx index bbc5ef917..ac9c4f04d 100644 --- a/src/components/Avatar/Avatar.tsx +++ b/src/components/Avatar/Avatar.tsx @@ -4,7 +4,9 @@ import { Image, Root, } from "@radix-ui/react-avatar"; -import { styled } from "styled-components"; +import clsx from "clsx"; +import { capitalize } from "../../utils/capitalize"; +import styles from "./Avatar.module.scss"; type TextSize = "md" | "sm"; @@ -15,74 +17,40 @@ export interface AvatarProps extends RadixAvatarProps { srcSet?: string; } -const Avatar = ({ text, textSize = "sm", src, srcSet, ...delegated }: AvatarProps) => ( - - - { + const textSizeClass = `cuiTextSize${capitalize(textSize)}`; + + return ( + - {text - .trim() - .replace(/(^.)([^ ]* )?(.).*/, "$1$3") - .trim() - .toUpperCase()} - - -); - -const StyledRoot = styled(Root)` - width: ${props => props.theme.click.avatar.size.width}; - height: ${props => props.theme.click.avatar.size.height}; - display: inline-flex; - align-items: center; - justify-content: center; - vertical-align: middle; - overflow: hidden; - user-select: none; - - background-color: ${props => props.theme.click.avatar.color.background.default}; - color: ${props => props.theme.click.avatar.color.text.default}; - border-radius: ${props => props.theme.click.avatar.radii.all}; - - &:active { - background-color: ${props => props.theme.click.avatar.color.background.active}; - color: ${props => props.theme.click.avatar.color.text.active}; - } - - &:hover { - background-color: ${props => props.theme.click.avatar.color.background.hover}; - color: ${props => props.theme.click.avatar.color.text.hover}; - } -`; - -const AvatarImage = styled(Image)` - width: 100%; - height: 100%; - object-fit: cover; - border-radius: inherit; -`; - -const StyledFallback = styled(Fallback)<{ $textSize: TextSize }>` - width: ${props => props.theme.click.avatar.size.label.width}; - display: inline-flex; - align-items: center; - justify-content: center; - ${({ theme, $textSize = "sm" }) => ` - font: ${theme.click.avatar.typography.label[$textSize].default}; - - &:active { - font: ${theme.click.avatar.typography.label[$textSize].active}; - } - - &:hover { - font: ${theme.click.avatar.typography.label[$textSize].hover}; - } - `} -`; + {text} + + {text + .trim() + .replace(/(^.)([^ ]* )?(.).*/, "$1$3") + .trim() + .toUpperCase()} + + + ); +}; export { Avatar }; diff --git a/src/components/Badge/Badge.module.scss b/src/components/Badge/Badge.module.scss new file mode 100644 index 000000000..67090aa21 --- /dev/null +++ b/src/components/Badge/Badge.module.scss @@ -0,0 +1,382 @@ +@use "cui-mixins" as mixins; +@use "cui-variants" as variants; + +// Base wrapper styles - using variant mixins for modern CSS +.cuiWrapper { + display: inline-flex; + border-radius: var(--click-badge-radii-all); + + // Size variants + @include variants.variant('cuiSizeSm') { + padding: var(--click-badge-space-sm-y) var(--click-badge-space-sm-x); + font: var(--click-badge-typography-label-sm-default); + } + + @include variants.variant('cuiSizeMd') { + padding: var(--click-badge-space-md-y) var(--click-badge-space-md-x); + font: var(--click-badge-typography-label-md-default); + } + + // Type and state combinations - opaque + @include variants.variant('cuiTypeOpaque') { + @include variants.variant('cuiStateDefault') { + background-color: var(--click-badge-opaque-color-background-default); + color: var(--click-badge-opaque-color-text-default); + border: var(--click-badge-stroke) solid var(--click-badge-opaque-color-stroke-default); + } + + @include variants.variant('cuiStateSuccess') { + background-color: var(--click-badge-opaque-color-background-success); + color: var(--click-badge-opaque-color-text-success); + border: var(--click-badge-stroke) solid var(--click-badge-opaque-color-stroke-success); + } + + @include variants.variant('cuiStateNeutral') { + background-color: var(--click-badge-opaque-color-background-neutral); + color: var(--click-badge-opaque-color-text-neutral); + border: var(--click-badge-stroke) solid var(--click-badge-opaque-color-stroke-neutral); + } + + @include variants.variant('cuiStateDanger') { + background-color: var(--click-badge-opaque-color-background-danger); + color: var(--click-badge-opaque-color-text-danger); + border: var(--click-badge-stroke) solid var(--click-badge-opaque-color-stroke-danger); + } + + @include variants.variant('cuiStateDisabled') { + background-color: var(--click-badge-opaque-color-background-disabled); + color: var(--click-badge-opaque-color-text-disabled); + border: var(--click-badge-stroke) solid var(--click-badge-opaque-color-stroke-disabled); + } + + @include variants.variant('cuiStateWarning') { + background-color: var(--click-badge-opaque-color-background-warning); + color: var(--click-badge-opaque-color-text-warning); + border: var(--click-badge-stroke) solid var(--click-badge-opaque-color-stroke-warning); + } + + @include variants.variant('cuiStateInfo') { + background-color: var(--click-badge-opaque-color-background-info); + color: var(--click-badge-opaque-color-text-info); + border: var(--click-badge-stroke) solid var(--click-badge-opaque-color-stroke-info); + } + } + + // Type and state combinations - solid + @include variants.variant('cuiTypeSolid') { + @include variants.variant('cuiStateDefault') { + background-color: var(--click-badge-solid-color-background-default); + color: var(--click-badge-solid-color-text-default); + border: var(--click-badge-stroke) solid var(--click-badge-solid-color-stroke-default); + } + + @include variants.variant('cuiStateSuccess') { + background-color: var(--click-badge-solid-color-background-success); + color: var(--click-badge-solid-color-text-success); + border: var(--click-badge-stroke) solid var(--click-badge-solid-color-stroke-success); + } + + @include variants.variant('cuiStateNeutral') { + background-color: var(--click-badge-solid-color-background-neutral); + color: var(--click-badge-solid-color-text-neutral); + border: var(--click-badge-stroke) solid var(--click-badge-solid-color-stroke-neutral); + } + + @include variants.variant('cuiStateDanger') { + background-color: var(--click-badge-solid-color-background-danger); + color: var(--click-badge-solid-color-text-danger); + border: var(--click-badge-stroke) solid var(--click-badge-solid-color-stroke-danger); + } + + @include variants.variant('cuiStateDisabled') { + background-color: var(--click-badge-solid-color-background-disabled); + color: var(--click-badge-solid-color-text-disabled); + border: var(--click-badge-stroke) solid var(--click-badge-solid-color-stroke-disabled); + } + + @include variants.variant('cuiStateWarning') { + background-color: var(--click-badge-solid-color-background-warning); + color: var(--click-badge-solid-color-text-warning); + border: var(--click-badge-stroke) solid var(--click-badge-solid-color-stroke-warning); + } + + @include variants.variant('cuiStateInfo') { + background-color: var(--click-badge-solid-color-background-info); + color: var(--click-badge-solid-color-text-info); + border: var(--click-badge-stroke) solid var(--click-badge-solid-color-stroke-info); + } + } +} + +// Content container - inherits sizing from wrapper +.cuiContent { + display: inline-flex; + align-items: center; + max-width: 100%; + justify-content: flex-start; +} + +// Size-specific gap using parent class selectors +.cuiWrapper { + @include variants.variant('cuiSizeSm') { + .cuiContent { + gap: var(--click-badge-space-sm-gap); + } + } + + @include variants.variant('cuiSizeMd') { + .cuiContent { + gap: var(--click-badge-space-md-gap); + } + } +} + +// Badge content wrapper +.cuiBadgeContent { + width: auto; + overflow: hidden; + + svg { + gap: inherit; + } +} + +// Icon size and color variants using parent wrapper classes +.cuiWrapper { + // Icon size variants + @include variants.variant('cuiSizeSm') { + .cuiBadgeContent svg { + height: var(--click-badge-icon-sm-size-height); + width: var(--click-badge-icon-sm-size-width); + } + } + + @include variants.variant('cuiSizeMd') { + .cuiBadgeContent svg { + height: var(--click-badge-icon-md-size-height); + width: var(--click-badge-icon-md-size-width); + } + } + + // Icon color variants for opaque type + @include variants.variant('cuiTypeOpaque') { + @include variants.variant('cuiStateDefault') { + .cuiBadgeContent svg { + color: var(--click-badge-opaque-color-text-default); + } + } + + @include variants.variant('cuiStateSuccess') { + .cuiBadgeContent svg { + color: var(--click-badge-opaque-color-text-success); + } + } + + @include variants.variant('cuiStateNeutral') { + .cuiBadgeContent svg { + color: var(--click-badge-opaque-color-text-neutral); + } + } + + @include variants.variant('cuiStateDanger') { + .cuiBadgeContent svg { + color: var(--click-badge-opaque-color-text-danger); + } + } + + @include variants.variant('cuiStateDisabled') { + .cuiBadgeContent svg { + color: var(--click-badge-opaque-color-text-disabled); + } + } + + @include variants.variant('cuiStateWarning') { + .cuiBadgeContent svg { + color: var(--click-badge-opaque-color-text-warning); + } + } + + @include variants.variant('cuiStateInfo') { + .cuiBadgeContent svg { + color: var(--click-badge-opaque-color-text-info); + } + } + } + + // Icon color variants for solid type + @include variants.variant('cuiTypeSolid') { + @include variants.variant('cuiStateDefault') { + .cuiBadgeContent svg { + color: var(--click-badge-solid-color-text-default); + } + } + + @include variants.variant('cuiStateSuccess') { + .cuiBadgeContent svg { + color: var(--click-badge-solid-color-text-success); + } + } + + @include variants.variant('cuiStateNeutral') { + .cuiBadgeContent svg { + color: var(--click-badge-solid-color-text-neutral); + } + } + + @include variants.variant('cuiStateDanger') { + .cuiBadgeContent svg { + color: var(--click-badge-solid-color-text-danger); + } + } + + @include variants.variant('cuiStateDisabled') { + .cuiBadgeContent svg { + color: var(--click-badge-solid-color-text-disabled); + } + } + + @include variants.variant('cuiStateWarning') { + .cuiBadgeContent svg { + color: var(--click-badge-solid-color-text-warning); + } + } + + @include variants.variant('cuiStateInfo') { + .cuiBadgeContent svg { + color: var(--click-badge-solid-color-text-info); + } + } + } +} + +// Close icon container +.cuiCloseIcon { + // Base styles only +} + +// Close icon size and color variants using parent wrapper classes +.cuiWrapper { + // Size variants + @include variants.variant('cuiSizeSm') { + .cuiCloseIcon { + height: var(--click-badge-icon-sm-size-height); + width: var(--click-badge-icon-sm-size-width); + } + } + + @include variants.variant('cuiSizeMd') { + .cuiCloseIcon { + height: var(--click-badge-icon-md-size-height); + width: var(--click-badge-icon-md-size-width); + } + } + + // Color variants for opaque type + @include variants.variant('cuiTypeOpaque') { + @include variants.variant('cuiStateDefault') { + .cuiCloseIcon { + color: var(--click-badge-opaque-color-text-default); + } + } + + @include variants.variant('cuiStateSuccess') { + .cuiCloseIcon { + color: var(--click-badge-opaque-color-text-success); + } + } + + @include variants.variant('cuiStateNeutral') { + .cuiCloseIcon { + color: var(--click-badge-opaque-color-text-neutral); + } + } + + @include variants.variant('cuiStateDanger') { + .cuiCloseIcon { + color: var(--click-badge-opaque-color-text-danger); + } + } + + @include variants.variant('cuiStateDisabled') { + .cuiCloseIcon { + color: var(--click-badge-opaque-color-text-disabled); + } + } + + @include variants.variant('cuiStateWarning') { + .cuiCloseIcon { + color: var(--click-badge-opaque-color-text-warning); + } + } + + @include variants.variant('cuiStateInfo') { + .cuiCloseIcon { + color: var(--click-badge-opaque-color-text-info); + } + } + } + + // Color variants for solid type + @include variants.variant('cuiTypeSolid') { + @include variants.variant('cuiStateDefault') { + .cuiCloseIcon { + color: var(--click-badge-solid-color-text-default); + } + } + + @include variants.variant('cuiStateSuccess') { + .cuiCloseIcon { + color: var(--click-badge-solid-color-text-success); + } + } + + @include variants.variant('cuiStateNeutral') { + .cuiCloseIcon { + color: var(--click-badge-solid-color-text-neutral); + } + } + + @include variants.variant('cuiStateDanger') { + .cuiCloseIcon { + color: var(--click-badge-solid-color-text-danger); + } + } + + @include variants.variant('cuiStateDisabled') { + .cuiCloseIcon { + color: var(--click-badge-solid-color-text-disabled); + } + } + + @include variants.variant('cuiStateWarning') { + .cuiCloseIcon { + color: var(--click-badge-solid-color-text-warning); + } + } + + @include variants.variant('cuiStateInfo') { + .cuiCloseIcon { + color: var(--click-badge-solid-color-text-info); + } + } + } +} + +/* Variant-specific styles for card-top-badge */ +.cuiWrapper[data-cui-variant="card-top-badge"] { + /* Position the badge at the top center of the card */ + position: absolute; + top: 0; + left: 50%; + transform: translate(-50%, -50%); +} + +/* Selected state for card-top-badge */ +.cuiWrapper[data-cui-variant="card-top-badge"][data-cui-selected="true"] { + border-color: var(--click-button-basic-color-primary-stroke-active); +} + +/* Active state when the sibling div is active */ +div:active + .cuiWrapper[data-cui-variant="card-top-badge"] { + border-color: var(--click-button-basic-color-primary-stroke-active); +} diff --git a/src/components/Badge/Badge.test.tsx b/src/components/Badge/Badge.test.tsx index 3a5d9fd0b..e87684e2c 100644 --- a/src/components/Badge/Badge.test.tsx +++ b/src/components/Badge/Badge.test.tsx @@ -4,7 +4,7 @@ import { renderCUI } from "@/utils/test-utils"; describe("Badge", () => { test("given a text, should render ellipsed badge", () => { const text = "text to render"; - const rendered = renderCUI(, "light"); + const rendered = renderCUI(); expect(rendered.getByText(text).textContent).toEqual(text); expect(rendered.queryByTestId("ellipsed-badge-content")).not.toBeNull(); @@ -18,8 +18,7 @@ describe("Badge", () => { , - "light" + /> ); expect(rendered.getByText(text).textContent).toEqual(text); diff --git a/src/components/Badge/Badge.tsx b/src/components/Badge/Badge.tsx index 18ea52b64..6a0bf0dd2 100644 --- a/src/components/Badge/Badge.tsx +++ b/src/components/Badge/Badge.tsx @@ -1,9 +1,11 @@ -import { styled } from "styled-components"; +import clsx from "clsx"; import { HorizontalDirection } from "@/components"; import { HTMLAttributes, MouseEvent, ReactNode } from "react"; import { ImageName } from "@/components/Icon/types"; import { Icon } from "@/components/Icon/Icon"; import IconWrapper from "@/components/IconWrapper/IconWrapper"; +import { capitalize } from "@/utils/capitalize"; +import styles from "./Badge.module.scss"; export type BadgeState = | "default" @@ -37,54 +39,6 @@ export interface NonDismissibleBadge extends CommonBadgeProps { onClose?: never; } -const Wrapper = styled.div<{ $state?: BadgeState; $size?: BadgeSize; $type?: BadgeType }>` - display: inline-flex; - ${({ $state = "default", $size = "md", $type = "opaque", theme }) => ` - background-color: ${theme.click.badge[$type].color.background[$state]}; - color: ${theme.click.badge[$type].color.text[$state]}; - font: ${theme.click.badge.typography.label[$size].default}; - border-radius: ${theme.click.badge.radii.all}; - border: ${theme.click.badge.stroke} solid ${theme.click.badge[$type].color.stroke[$state]}; - padding: ${theme.click.badge.space[$size].y} ${theme.click.badge.space[$size].x}; - `} -`; - -const Content = styled.div<{ $state?: BadgeState; $size?: BadgeSize }>` - display: inline-flex; - align-items: center; - gap: ${({ $size = "md", theme }) => theme.click.badge.space[$size].gap}; - max-width: 100%; - justify-content: flex-start; -`; - -const SvgContainer = styled.svg<{ - $state?: BadgeState; - $size?: BadgeSize; - $type?: BadgeType; -}>` - ${({ $state = "default", $size = "md", $type = "opaque", theme }) => ` - color: ${theme.click.badge[$type].color.text[$state]}; - height: ${theme.click.badge.icon[$size].size.height}; - width: ${theme.click.badge.icon[$size].size.width}; - `} -`; -const BadgeContent = styled.div<{ - $state?: BadgeState; - $size?: BadgeSize; - $type?: BadgeType; -}>` - width: auto; - overflow: hidden; - svg { - ${({ $state = "default", $size = "md", $type = "opaque", theme }) => ` - color: ${theme.click.badge[$type].color.text[$state]}; - height: ${theme.click.badge.icon[$size].size.height}; - width: ${theme.click.badge.icon[$size].size.width}; - gap: inherit; - `} - } -`; - export type BadgeProps = NonDismissibleBadge | DismissibleBadge; export const Badge = ({ @@ -92,39 +46,54 @@ export const Badge = ({ iconDir, text, state = "default", - size, - type, + size = "md", + type = "opaque", dismissible, onClose, ellipsisContent = true, + className, ...props -}: BadgeProps) => ( - - - - {text} - - {dismissible && ( - +}: BadgeProps) => { + const sizeClass = `cuiSize${capitalize(size)}`; + const typeClass = `cuiType${capitalize(type)}`; + const stateClass = `cuiState${capitalize(state)}`; + + return ( +
- -); + data-cui-size={size} + data-cui-type={type} + data-cui-state={state} + {...props} + > +
+ + {text} + + {dismissible && ( + + )} +
+
+ ); +}; diff --git a/src/components/BigStat/BigStat.module.scss b/src/components/BigStat/BigStat.module.scss new file mode 100644 index 000000000..af1005fc9 --- /dev/null +++ b/src/components/BigStat/BigStat.module.scss @@ -0,0 +1,129 @@ +@use "cui-mixins" as mixins; +@use "cui-variants" as variants; + +.cuiWrapper { + display: flex; + justify-content: center; + box-sizing: border-box; + border-radius: var(--click-bigStat-radii-all); + border: var(--click-bigStat-stroke) solid; + padding: var(--click-bigStat-space-all); + + // State variants + @include variants.variant('cuiStateDefault') { + background-color: var(--click-bigStat-color-background-default); + color: var(--click-bigStat-color-label-default); + border-color: var(--click-bigStat-color-stroke-default); + } + + @include variants.variant('cuiStateMuted') { + background-color: var(--click-bigStat-color-background-muted); + color: var(--click-bigStat-color-label-muted); + border-color: var(--click-bigStat-color-stroke-muted); + } + + @include variants.variant('cuiStateError') { + border-color: var(--click-bigStat-color-stroke-danger); + } + + // Size variants + @include variants.variant('cuiSizeSm') { + font: var(--click-bigStat-typography-sm-label-default); + + @include variants.variant('cuiStateMuted') { + font: var(--click-bigStat-typography-sm-label-muted); + } + } + + @include variants.variant('cuiSizeLg') { + font: var(--click-bigStat-typography-lg-label-default); + + @include variants.variant('cuiStateMuted') { + font: var(--click-bigStat-typography-lg-label-muted); + } + } + + // Spacing variants + @include variants.variant('cuiSpacingSm') { + gap: var(--click-bigStat-space-sm-gap); + } + + @include variants.variant('cuiSpacingLg') { + gap: var(--click-bigStat-space-lg-gap); + } + + // Layout variants + @include variants.variant('cuiOrderTitleTop') { + flex-direction: column; + } + + @include variants.variant('cuiOrderTitleBottom') { + flex-direction: column-reverse; + } + + @include variants.variant('cuiWidthFill') { + width: 100%; + } + + @include variants.variant('cuiWidthAuto') { + width: auto; + } +} + +.cuiLabel { + // State variants + @include variants.variant('cuiStateDefault') { + color: var(--click-bigStat-color-label-default); + + @include variants.variant('cuiSizeSm') { + font: var(--click-bigStat-typography-sm-label-default); + } + + @include variants.variant('cuiSizeLg') { + font: var(--click-bigStat-typography-lg-label-default); + } + } + + @include variants.variant('cuiStateMuted') { + color: var(--click-bigStat-color-label-muted); + + @include variants.variant('cuiSizeSm') { + font: var(--click-bigStat-typography-sm-label-muted); + } + + @include variants.variant('cuiSizeLg') { + font: var(--click-bigStat-typography-lg-label-muted); + } + } + + @include variants.variant('cuiStateError') { + color: var(--click-bigStat-color-label-danger); + } +} + +.cuiTitle { + // State variants + @include variants.variant('cuiStateDefault') { + color: var(--click-bigStat-color-title-default); + + @include variants.variant('cuiSizeSm') { + font: var(--click-bigStat-typography-sm-title-default); + } + + @include variants.variant('cuiSizeLg') { + font: var(--click-bigStat-typography-lg-title-default); + } + } + + @include variants.variant('cuiStateMuted') { + color: var(--click-bigStat-color-title-muted); + + @include variants.variant('cuiSizeSm') { + font: var(--click-bigStat-typography-sm-title-muted); + } + + @include variants.variant('cuiSizeLg') { + font: var(--click-bigStat-typography-lg-title-muted); + } + } +} diff --git a/src/components/BigStat/BigStat.tsx b/src/components/BigStat/BigStat.tsx index a83e46d5b..123bc5df0 100644 --- a/src/components/BigStat/BigStat.tsx +++ b/src/components/BigStat/BigStat.tsx @@ -1,5 +1,7 @@ import { HTMLAttributes } from "react"; -import { styled } from "styled-components"; +import { capitalize } from "@/utils/capitalize"; +import clsx from "clsx"; +import styles from "./BigStat.module.scss"; export type bigStatOrder = "titleTop" | "titleBottom"; export type bigStatSize = "sm" | "lg"; export type bigStatSpacing = "sm" | "lg"; @@ -25,99 +27,47 @@ export const BigStat = ({ height = "6rem", label = "Label", order = "titleTop", - size, + size = "lg", spacing = "sm", state = "default", title = "Title", error = false, + style, ...props -}: BigStatProps) => ( - - - - {title} - - -); - -const Wrapper = styled.div<{ - $fillWidth?: boolean; - $maxWidth?: string; - $height?: string; - $order?: bigStatOrder; - $size?: bigStatSize; - $spacing?: bigStatSpacing; - $state?: bigStatState; - $error?: boolean; -}>` - display: flex; - justify-content: center; - box-sizing: border-box; - ${({ - $fillWidth = false, - $maxWidth = "none", - $state = "default", - $size = "lg", - $height = "fixed", - $order, - $spacing = "sm", - $error = false, - theme, - }) => ` - background-color: ${theme.click.bigStat.color.background[$state]}; - color: ${theme.click.bigStat.color.label[$state]}; - font: ${theme.click.bigStat.typography[$size].label[$state]}; - border-radius: ${theme.click.bigStat.radii.all}; - border: ${theme.click.bigStat.stroke} solid ${ - $error - ? theme.click.bigStat.color.stroke.danger - : theme.click.bigStat.color.stroke[$state] - }; - gap: ${theme.click.bigStat.space[$spacing].gap}; - padding: ${theme.click.bigStat.space.all}; - min-height: ${$height !== undefined ? `${$height}` : "auto"}; - flex-direction: ${$order === "titleBottom" ? "column-reverse" : "column"}; - width: ${$fillWidth === true ? "100%" : "auto"}; - max-width: ${$maxWidth ? $maxWidth : "none"}; - `} -`; +}: BigStatProps) => { + const stateClass = error ? "cuiStateError" : `cuiState${capitalize(state)}`; + const sizeClass = `cuiSize${capitalize(size)}`; + const spacingClass = `cuiSpacing${capitalize(spacing)}`; + const orderClass = `cuiOrder${capitalize(order)}`; + const widthClass = fillWidth ? "cuiWidthFill" : "cuiWidthAuto"; -const Label = styled.div<{ - $state?: bigStatState; - $size?: bigStatSize; - $error?: boolean; -}>` - ${({ $state = "default", $size = "lg", $error = false, theme }) => ` - color: ${$error ? theme.click.bigStat.color.label.danger : theme.click.bigStat.color.label[$state]}; - font: ${theme.click.bigStat.typography[$size].label[$state]}; - `} -`; - -const Title = styled.div<{ - $state?: bigStatState; - $size?: bigStatSize; -}>` - ${({ $state = "default", $size = "lg", theme }) => ` - color: ${theme.click.bigStat.color.title[$state]}; - font: ${theme.click.bigStat.typography[$size].title[$state]}; - `} -`; + return ( +
+
+ {label} +
+
+ {title} +
+
+ ); +}; diff --git a/src/components/Button/Button.module.scss b/src/components/Button/Button.module.scss new file mode 100644 index 000000000..d0891de6e --- /dev/null +++ b/src/components/Button/Button.module.scss @@ -0,0 +1,165 @@ +@use "cui-mixins" as mixins; +@use "cui-variants" as variants; + +.cuiButton { + @include mixins.cuiBaseButton; + position: relative; + white-space: nowrap; + + &:hover { + transition: var(--transition-default); + } + + // Button type variants - using :where() for low specificity (0,0,1,0) + @include variants.variant('cuiPrimary') { + color: var(--click-button-basic-color-primary-text-default); + background-color: var(--click-button-basic-color-primary-background-default); + border: var(--click-button-stroke) solid var(--click-button-basic-color-primary-stroke-default); + + &:hover { + background-color: var(--click-button-basic-color-primary-background-hover); + border: var(--click-button-stroke) solid var(--click-button-basic-color-primary-stroke-hover); + font: var(--click-button-basic-typography-label-hover); + } + + &:active, + &:focus { + background-color: var(--click-button-basic-color-primary-background-active); + border: 1px solid var(--click-button-basic-color-primary-stroke-active); + font: var(--click-button-basic-typography-label-active); + } + + &:disabled, + &:disabled:hover, + &:disabled:active { + background-color: var(--click-button-basic-color-primary-background-disabled); + color: var(--click-button-basic-color-primary-text-disabled); + border: var(--click-button-stroke) solid var(--click-button-basic-color-primary-stroke-disabled); + font: var(--click-button-basic-typography-label-disabled); + } + } + + @include variants.variant('cuiSecondary') { + color: var(--click-button-basic-color-secondary-text-default); + background-color: var(--click-button-basic-color-secondary-background-default); + border: var(--click-button-stroke) solid var(--click-button-basic-color-secondary-stroke-default); + + &:hover { + background-color: var(--click-button-basic-color-secondary-background-hover); + border: var(--click-button-stroke) solid var(--click-button-basic-color-secondary-stroke-hover); + font: var(--click-button-basic-typography-label-hover); + } + + &:active, + &:focus { + background-color: var(--click-button-basic-color-secondary-background-active); + border: 1px solid var(--click-button-basic-color-secondary-stroke-active); + font: var(--click-button-basic-typography-label-active); + } + + &:disabled, + &:disabled:hover, + &:disabled:active { + background-color: var(--click-button-basic-color-secondary-background-disabled); + color: var(--click-button-basic-color-secondary-text-disabled); + border: var(--click-button-stroke) solid var(--click-button-basic-color-secondary-stroke-disabled); + font: var(--click-button-basic-typography-label-disabled); + } + } + + @include variants.variant('cuiEmpty') { + color: var(--click-button-basic-color-empty-text-default); + background-color: var(--click-button-basic-color-empty-background-default); + border: var(--click-button-stroke) solid var(--click-button-basic-color-empty-stroke-default); + + &:hover { + background-color: var(--click-button-basic-color-empty-background-hover); + border: var(--click-button-stroke) solid var(--click-button-basic-color-empty-stroke-hover); + font: var(--click-button-basic-typography-label-hover); + } + + &:active, + &:focus { + background-color: var(--click-button-basic-color-empty-background-active); + border: 1px solid var(--click-button-basic-color-empty-stroke-active); + font: var(--click-button-basic-typography-label-active); + } + + &:disabled, + &:disabled:hover, + &:disabled:active { + background-color: var(--click-button-basic-color-empty-background-disabled); + color: var(--click-button-basic-color-empty-text-disabled); + border: var(--click-button-stroke) solid var(--click-button-basic-color-empty-stroke-disabled); + font: var(--click-button-basic-typography-label-disabled); + } + } + + @include variants.variant('cuiDanger') { + color: var(--click-button-basic-color-danger-text-default); + background-color: var(--click-button-basic-color-danger-background-default); + border: var(--click-button-stroke) solid var(--click-button-basic-color-danger-stroke-default); + + &:hover { + background-color: var(--click-button-basic-color-danger-background-hover); + border: var(--click-button-stroke) solid var(--click-button-basic-color-danger-stroke-hover); + font: var(--click-button-basic-typography-label-hover); + } + + &:active, + &:focus { + background-color: var(--click-button-basic-color-danger-background-active); + border: 1px solid var(--click-button-basic-color-danger-stroke-active); + font: var(--click-button-basic-typography-label-active); + } + + &:disabled, + &:disabled:hover, + &:disabled:active { + background-color: var(--click-button-basic-color-danger-background-disabled); + color: var(--click-button-basic-color-danger-text-disabled); + border: var(--click-button-stroke) solid var(--click-button-basic-color-danger-stroke-disabled); + font: var(--click-button-basic-typography-label-disabled); + } + } + + // Alignment variants - using :where() for low specificity + @include variants.variant('cuiAlignLeft') { + justify-content: flex-start; + } + + @include variants.variant('cuiAlignCenter') { + justify-content: center; + } + + // Fill width variant - using :where() for low specificity + @include variants.variant('cuiFillWidth') { + width: 100%; + } +} + +// Loading icon wrapper +.cuiLoadingIconWrapper { + background-color: inherit; + top: 0; + left: 0; + bottom: 0; + right: 0; + display: flex; + align-content: center; + justify-content: center; + align-items: center; + gap: 0.5rem; +} + +// Button icon styling +.cuiButtonIcon { + composes: cuiIconWrapper from '../Icon/Icon.module.scss'; + height: var(--click-button-basic-size-icon-all); + width: var(--click-button-basic-size-icon-all); + + svg { + height: var(--click-button-basic-size-icon-all); + width: var(--click-button-basic-size-icon-all); + } +} diff --git a/src/components/Button/Button.tsx b/src/components/Button/Button.tsx index 3cd5cab67..a8ad11e4c 100644 --- a/src/components/Button/Button.tsx +++ b/src/components/Button/Button.tsx @@ -1,6 +1,9 @@ +"use client"; + import { Icon, IconName } from "@/components"; -import { styled } from "styled-components"; -import { BaseButton } from "../commonElement"; +import clsx from "clsx"; +import { capitalize } from "../../utils/capitalize"; +import styles from "./Button.module.scss"; import React from "react"; export type ButtonType = "primary" | "secondary" | "empty" | "danger"; @@ -30,122 +33,66 @@ export const Button = ({ loading = false, disabled, showLabelWithLoading = false, + className, ...delegated -}: ButtonProps) => ( - - {!loading && ( - <> - {iconLeft && ( - - )} - - {label ?? children} - - {iconRight && ( - - )} - - )} - {loading && ( - - - {showLabelWithLoading ? (label ?? children) : ""} - - )} - -); +}: ButtonProps) => { + const typeClass = `cui${capitalize(type)}`; + const alignClass = `cuiAlign${capitalize(align)}`; -const LoadingIconWrapper = styled.div` - background-color: inherit; - top: 0; - left: 0; - bottom: 0; - right: 0; - display: flex; - align-content: center; - justify-content: center; - align-items: center; - gap: 0.5rem; -`; + return ( + + ); +}; diff --git a/src/components/ButtonGroup/ButtonGroup.module.scss b/src/components/ButtonGroup/ButtonGroup.module.scss new file mode 100644 index 000000000..4c6bbf952 --- /dev/null +++ b/src/components/ButtonGroup/ButtonGroup.module.scss @@ -0,0 +1,103 @@ +@use "cui-mixins" as mixins; +@use "cui-variants" as variants; + +.cuiButtonGroupWrapper { + display: inline-flex; + box-sizing: border-box; + flex-direction: row; + justify-content: center; + align-items: center; + background: var(--click-button-group-color-background-panel); + border-radius: var(--click-button-group-radii-panel-all); + + &.cuiFillWidth { + width: 100%; + } + + // Type variants + @include variants.variant('cuiTypeDefault') { + padding: var(--click-button-group-space-panel-default-x) var(--click-button-group-space-panel-default-y); + gap: var(--click-button-group-space-panel-default-gap); + border: 1px solid var(--click-button-group-color-panel-stroke-default); + } + + @include variants.variant('cuiTypeBorderless') { + padding: var(--click-button-group-space-panel-borderless-x) var(--click-button-group-space-panel-borderless-y); + gap: var(--click-button-group-space-panel-borderless-gap); + border: none; + } +} + +.cuiButton { + box-sizing: border-box; + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + color: var(--click-button-group-color-text-default); + font: var(--click-button-group-typography-label-default); + cursor: pointer; + border: none; + + &.cuiFillWidth { + flex: 1; + } + + // Active/inactive background states + &.cuiActive { + background: var(--click-button-group-color-background-active); + } + + &.cuiInactive { + background: var(--click-button-group-color-background-default); + } + + // Type variants + @include variants.variant('cuiTypeDefault') { + padding: var(--click-button-group-space-button-default-y) var(--click-button-group-space-button-default-x); + border-radius: var(--click-button-group-radii-button-default-all); + } + + @include variants.variant('cuiTypeBorderless') { + padding: var(--click-button-group-space-button-borderless-y) var(--click-button-group-space-button-borderless-x); + border-radius: var(--click-button-group-radii-button-borderless-all); + } + + &:hover:not(:disabled) { + background: var(--click-button-group-color-background-hover); + font: var(--click-button-group-typography-label-hover); + color: var(--click-button-group-color-text-hover); + } + + &:disabled { + cursor: not-allowed; + font: var(--click-button-group-typography-label-disabled); + color: var(--click-button-group-color-text-disabled); + + &.cuiActive { + background: var(--click-button-group-color-background-disabled-active); + } + + &.cuiInactive { + background: var(--click-button-group-color-background-disabled); + } + + &:active, + &:focus, + &[aria-pressed="true"] { + color: var(--click-button-group-color-text-disabled); + } + } + + &:active:not(:disabled), + &:focus:not(:disabled), + &[aria-pressed="true"]:not(:disabled) { + background: var(--click-button-group-color-background-active); + font: var(--click-button-group-typography-label-active); + color: var(--click-button-group-color-text-active); + + &:disabled { + background: var(--click-button-group-color-background-disabled-active); + } + } +} diff --git a/src/components/ButtonGroup/ButtonGroup.tsx b/src/components/ButtonGroup/ButtonGroup.tsx index 9eb90b4ac..f22060b6d 100644 --- a/src/components/ButtonGroup/ButtonGroup.tsx +++ b/src/components/ButtonGroup/ButtonGroup.tsx @@ -1,5 +1,9 @@ +"use client"; + import { HTMLAttributes, ReactNode } from "react"; -import { DefaultTheme, styled } from "styled-components"; +import clsx from "clsx"; +import { capitalize } from "@/utils/capitalize"; +import styles from "@/components/ButtonGroup/ButtonGroup.module.scss"; type ButtonGroupType = "default" | "borderless"; @@ -24,111 +28,43 @@ export const ButtonGroup = ({ fillWidth = false, onClick, type = "default", + className, ...props }: ButtonGroupProps) => { - const buttons = options.map(({ value, label, ...props }) => ( - + )); return ( - {buttons} - + ); }; - -const ButtonGroupWrapper = styled.div<{ $fillWidth: boolean; $type: ButtonGroupType }>` - display: inline-flex; - box-sizing: border-box; - flex-direction: row; - justify-content: center; - align-items: center; - padding: ${({ theme, $type }) => - `${theme.click.button.group.space.panel[$type].x} ${theme.click.button.group.space.panel[$type].y}`}; - gap: ${({ theme, $type }) => theme.click.button.group.space.panel[$type].gap}; - border: ${({ theme, $type }) => - $type === "default" - ? `1px solid ${theme.click.button.group.color.panel.stroke[$type]}` - : "none"}; - background: ${({ theme }) => theme.click.button.group.color.background.panel}; - border-radius: ${({ theme }) => theme.click.button.group.radii.panel.all}; - width: ${({ $fillWidth }) => ($fillWidth ? "100%" : "auto")}; -`; - -interface ButtonProps { - $active: boolean; - theme: DefaultTheme; - $fillWidth: boolean; - $type: ButtonGroupType; -} - -const Button = styled.button.attrs((props: ButtonProps) => ({ - "aria-pressed": props.$active, -}))` - box-sizing: border-box; - display: flex; - flex-direction: row; - justify-content: center; - align-items: center; - background: ${({ $active, theme }: ButtonProps) => - $active - ? theme.click.button.group.color.background.active - : theme.click.button.group.color.background.default}; - color: ${({ theme }) => theme.click.button.group.color.text.default}; - font: ${({ theme }) => theme.click.button.group.typography.label.default}; - padding: ${({ theme, $type }) => - `${theme.click.button.group.space.button[$type].y} ${theme.click.button.group.space.button[$type].x}`}; - ${({ $fillWidth }) => ($fillWidth ? "flex: 1;" : "")}; - border-radius: ${({ theme, $type }) => - theme.click.button.group.radii.button[$type].all}; - cursor: pointer; - border: none; - - &:hover { - background: ${({ theme }) => theme.click.button.group.color.background.hover}; - font: ${({ theme }) => theme.click.button.group.typography.label.hover}; - color: ${({ theme }) => theme.click.button.group.color.text.hover}; - } - - &:disabled { - cursor: not-allowed; - font: ${({ theme }) => theme.click.button.group.typography.label.disabled}; - color: ${({ theme }) => theme.click.button.group.color.text.disabled}; - background: ${({ theme, $active }) => - theme.click.button.group.color.background[ - $active ? "disabled-active" : "disabled" - ]}; - - &:active, - &:focus, - &[aria-pressed="true"] { - color: ${({ theme }) => theme.click.button.group.color.text.disabled}; - } - } - - &:active, - &:focus, - &[aria-pressed="true"] { - background: ${({ theme }) => theme.click.button.group.color.background.active}; - font: ${({ theme }) => theme.click.button.group.typography.label.active}; - color: ${({ theme }) => theme.click.button.group.color.text.active}; - &:disabled { - background: ${({ theme }) => - theme.click.button.group.color.background["disabled-active"]}; - } - } -`; diff --git a/src/components/CardHorizontal/CardHorizontal.module.scss b/src/components/CardHorizontal/CardHorizontal.module.scss new file mode 100644 index 000000000..0c0763083 --- /dev/null +++ b/src/components/CardHorizontal/CardHorizontal.module.scss @@ -0,0 +1,288 @@ +@use "cui-mixins" as mixins; +@use "cui-variants" as variants; + +.cuiWrapper { + @include mixins.cuiCardBase; + + display: inline-flex; + width: 100%; + max-width: 100%; + align-items: center; + justify-content: flex-start; + border-radius: var(--click-card-horizontal-radii-all); + transition: all 0.2s ease-in-out; + + // Size variants + @include variants.variant('cuiSizeMd') { + padding: var(--click-card-horizontal-space-md-y) var(--click-card-horizontal-space-md-x); + gap: var(--click-card-horizontal-space-md-gap); + + .cuiDescription { + gap: var(--click-card-horizontal-space-md-gap); + } + } + + @include variants.variant('cuiSizeSm') { + padding: var(--click-card-horizontal-space-sm-y) var(--click-card-horizontal-space-sm-x); + gap: var(--click-card-horizontal-space-sm-gap); + + .cuiDescription { + gap: var(--click-card-horizontal-space-sm-gap); + } + } + + .cuiDescription { + display: flex; + flex-direction: column; + align-self: start; + flex: 1; + width: 100%; + } + + // Color variant: default + @include variants.variant('cuiColorDefault') { + background: var(--click-card-horizontal-default-color-background-default); + color: var(--click-card-horizontal-default-color-title-default); + border: 1px solid var(--click-card-horizontal-default-color-stroke-default); + font: var(--click-card-horizontal-typography-title-default); + + .cuiDescription { + color: var(--click-card-horizontal-default-color-description-default); + font: var(--click-card-horizontal-typography-description-default); + } + + // Selectable hover state + &.cuiIsSelectable:hover { + background-color: var(--click-card-horizontal-default-color-background-hover); + color: var(--click-card-horizontal-default-color-title-hover); + border: 1px solid var(--click-card-horizontal-default-color-stroke-default); + cursor: pointer; + font: var(--click-card-horizontal-typography-title-hover); + + .cuiDescription { + color: var(--click-card-horizontal-default-color-description-hover); + font: var(--click-card-horizontal-typography-description-hover); + } + } + + // Selectable hover + selected + &.cuiIsSelectable:hover { + @include variants.variant('cuiSelected') { + border: 1px solid var(--click-card-horizontal-default-color-stroke-active); + } + } + + // Selectable active/focus states + &.cuiIsSelectable:active, + &.cuiIsSelectable:focus, + &.cuiIsSelectable:focus-within { + background-color: var(--click-card-horizontal-default-color-background-active); + color: var(--click-card-horizontal-default-color-title-active); + border: 1px solid var(--click-card-horizontal-default-color-stroke-active); + + .cuiDescription { + color: var(--click-card-horizontal-default-color-description-active); + font: var(--click-card-horizontal-typography-description-active); + } + } + + // Selected state + @include variants.variant('cuiSelected') { + border: 1px solid var(--click-card-horizontal-default-color-stroke-active); + + &:hover { + border: 1px solid var(--click-card-horizontal-default-color-stroke-active); + } + } + + // Disabled state + @include variants.variant('cuiDisabled') { + pointer-events: none; + background-color: var(--click-card-horizontal-default-color-background-disabled); + color: var(--click-card-horizontal-default-color-title-disabled); + border: 1px solid var(--click-card-horizontal-default-color-stroke-disabled); + cursor: not-allowed; + + .cuiDescription { + color: var(--click-card-horizontal-default-color-description-disabled); + font: var(--click-card-horizontal-typography-description-disabled); + } + + &:hover, + &:active, + &:focus, + &:focus-within { + background-color: var(--click-card-horizontal-default-color-background-disabled); + color: var(--click-card-horizontal-default-color-title-disabled); + cursor: not-allowed; + + .cuiDescription { + color: var(--click-card-horizontal-default-color-description-disabled); + font: var(--click-card-horizontal-typography-description-disabled); + } + } + + @include variants.variant('cuiSelected') { + border: 1px solid var(--click-card-horizontal-default-color-stroke-active); + + &:active, + &:focus, + &:focus-within { + border: 1px solid var(--click-card-horizontal-default-color-stroke-active); + } + } + } + } + + // Color variant: muted + @include variants.variant('cuiColorMuted') { + background: var(--click-card-horizontal-muted-color-background-default); + color: var(--click-card-horizontal-muted-color-title-default); + border: 1px solid var(--click-card-horizontal-muted-color-stroke-default); + font: var(--click-card-horizontal-typography-title-default); + + .cuiDescription { + color: var(--click-card-horizontal-muted-color-description-default); + font: var(--click-card-horizontal-typography-description-default); + } + + // Selectable hover state + &.cuiIsSelectable:hover { + background-color: var(--click-card-horizontal-muted-color-background-hover); + color: var(--click-card-horizontal-muted-color-title-hover); + border: 1px solid var(--click-card-horizontal-muted-color-stroke-default); + cursor: pointer; + font: var(--click-card-horizontal-typography-title-hover); + + .cuiDescription { + color: var(--click-card-horizontal-muted-color-description-hover); + font: var(--click-card-horizontal-typography-description-hover); + } + } + + // Selectable hover + selected + &.cuiIsSelectable:hover { + @include variants.variant('cuiSelected') { + border: 1px solid var(--click-card-horizontal-muted-color-stroke-active); + } + } + + // Selectable active/focus states + &.cuiIsSelectable:active, + &.cuiIsSelectable:focus, + &.cuiIsSelectable:focus-within { + background-color: var(--click-card-horizontal-muted-color-background-active); + color: var(--click-card-horizontal-muted-color-title-active); + border: 1px solid var(--click-card-horizontal-muted-color-stroke-active); + + .cuiDescription { + color: var(--click-card-horizontal-muted-color-description-active); + font: var(--click-card-horizontal-typography-description-active); + } + } + + // Selected state + @include variants.variant('cuiSelected') { + border: 1px solid var(--click-card-horizontal-muted-color-stroke-active); + + &:hover { + border: 1px solid var(--click-card-horizontal-muted-color-stroke-active); + } + } + + // Disabled state + @include variants.variant('cuiDisabled') { + pointer-events: none; + background-color: var(--click-card-horizontal-muted-color-background-disabled); + color: var(--click-card-horizontal-muted-color-title-disabled); + border: 1px solid var(--click-card-horizontal-muted-color-stroke-disabled); + cursor: not-allowed; + + .cuiDescription { + color: var(--click-card-horizontal-muted-color-description-disabled); + font: var(--click-card-horizontal-typography-description-disabled); + } + + &:hover, + &:active, + &:focus, + &:focus-within { + background-color: var(--click-card-horizontal-muted-color-background-disabled); + color: var(--click-card-horizontal-muted-color-title-disabled); + cursor: not-allowed; + + .cuiDescription { + color: var(--click-card-horizontal-muted-color-description-disabled); + font: var(--click-card-horizontal-typography-description-disabled); + } + } + + @include variants.variant('cuiSelected') { + border: 1px solid var(--click-card-horizontal-muted-color-stroke-active); + + &:active, + &:focus, + &:focus-within { + border: 1px solid var(--click-card-horizontal-muted-color-stroke-active); + } + } + } + } +} + +.cuiHeader { + max-width: 100%; + gap: inherit; + display: flex; + width: 100%; + justify-content: space-between; + align-items: center; +} + +.cuiDescription { + display: flex; + flex-direction: column; + align-self: start; + gap: var(--click-card-horizontal-space-gap); + flex: 1; + width: 100%; +} + +.cuiCardIcon { + composes: cuiIconWrapper from '../Icon/Icon.module.scss'; + height: var(--click-card-horizontal-icon-size-all); + width: var(--click-card-horizontal-icon-size-all); +} + +.cuiContentWrapper { + display: flex; + flex-direction: row; + width: 100%; + + @include variants.variant('cuiSizeMd') { + gap: var(--click-card-horizontal-space-md-gap); + } + + @include variants.variant('cuiSizeSm') { + gap: var(--click-card-horizontal-space-sm-gap); + } + + @include mixins.cuiMobile { + flex-direction: column; + } +} + +.cuiIconTextContentWrapper { + display: flex; + flex-direction: row; + align-items: center; + width: 100%; + + @include variants.variant('cuiSizeMd') { + gap: var(--click-card-horizontal-space-md-gap); + } + + @include variants.variant('cuiSizeSm') { + gap: var(--click-card-horizontal-space-sm-gap); + } +} diff --git a/src/components/CardHorizontal/CardHorizontal.stories.module.scss b/src/components/CardHorizontal/CardHorizontal.stories.module.scss new file mode 100644 index 000000000..68b21750d --- /dev/null +++ b/src/components/CardHorizontal/CardHorizontal.stories.module.scss @@ -0,0 +1,4 @@ +.cuiGridCenter { + display: grid; + width: 60%; +} \ No newline at end of file diff --git a/src/components/CardHorizontal/CardHorizontal.stories.tsx b/src/components/CardHorizontal/CardHorizontal.stories.tsx index 9da52300a..914dc7f62 100644 --- a/src/components/CardHorizontal/CardHorizontal.stories.tsx +++ b/src/components/CardHorizontal/CardHorizontal.stories.tsx @@ -1,54 +1,88 @@ -import { Meta, StoryObj } from "@storybook/react-vite"; -import { styled } from "styled-components"; - -import { ICON_NAMES } from "../Icon/types"; - import { CardHorizontal } from "./CardHorizontal"; +import { ICON_NAMES } from "@/components/Icon/types.ts"; +import styles from "./CardHorizontal.stories.module.scss"; -const GridCenter = styled.div` - display: grid; - width: 60%; -`; +const CardHorizontalExample = ({ ...props }) => { + return ( +
+ +
+ ); +}; -const meta: Meta = { - component: CardHorizontal, +export default { + component: CardHorizontalExample, title: "Cards/Horizontal Card", tags: ["cardHorizontal", "autodocs"], argTypes: { - icon: { type: { name: "enum", value: [...ICON_NAMES] } }, - badgeIcon: { type: { name: "enum", value: [...ICON_NAMES] } }, + icon: { control: "select", options: ICON_NAMES, description: "`IconName`" }, + size: { + control: "radio", + options: ["sm", "md"], + description: "`sm` `md`", + defaultValue: { summary: "md" }, + }, + badgeIcon: { control: "select", options: ICON_NAMES, description: "`IconName`" }, + badgeText: { + control: "text", + description: "Shows and hides the badge
`string`", + }, badgeState: { - type: { - name: "enum", - // FIXME should refer to the Badge constants - value: ["default", "success", "neutral", "danger", "disabled", "warning", "info"], - }, + control: "select", + options: ["default", "info", "success", "warning", "danger"], + description: "`BadgeState`", + }, + badgeIconDir: { + control: "radio", + options: ["start", "end"], + description: "`start` `end`", + }, + title: { control: "text", description: "`ReactNode`" }, + description: { control: "text", description: "`ReactNode`" }, + infoText: { + control: "text", + description: "Shows and hides the button
`string`", + }, + infoUrl: { control: "text", description: "`string`" }, + disabled: { + control: "boolean", + description: "`boolean`", + defaultValue: { summary: "false" }, + }, + isSelected: { + control: "boolean", + description: "`boolean`", + defaultValue: { summary: "false" }, }, - // FIXME should refer to a constant - badgeIconDir: { type: { name: "enum", value: ["start", "end"] } }, }, - decorators: Story => ( - - - - ), }; -export default meta; - -export const Playground: StoryObj = { +export const Playground = { args: { icon: "building", title: "Card title", description: "A description very interesting that presumably relates to the card.", disabled: false, isSelected: false, + size: "md", badgeText: "", - badgeIcon: undefined, + badgeIcon: null, badgeState: "default", - badgeIconDir: undefined, + badgeIconDir: "", infoText: "", infoUrl: "", - size: "md", }, }; diff --git a/src/components/CardHorizontal/CardHorizontal.tsx b/src/components/CardHorizontal/CardHorizontal.tsx index 1165b8b44..ede8cdd38 100644 --- a/src/components/CardHorizontal/CardHorizontal.tsx +++ b/src/components/CardHorizontal/CardHorizontal.tsx @@ -1,5 +1,7 @@ +"use client"; + import { HTMLAttributes, MouseEventHandler, ReactNode } from "react"; -import { styled } from "styled-components"; +import clsx from "clsx"; import { Badge, BadgeState, @@ -9,6 +11,8 @@ import { Icon, IconName, } from "@/components"; +import { capitalize } from "@/utils/capitalize"; +import styles from "./CardHorizontal.module.scss"; type CardColor = "default" | "muted"; export type CardSize = "sm" | "md"; @@ -35,177 +39,6 @@ export interface CardHorizontalProps onButtonClick?: MouseEventHandler; } -const Header = styled.div` - max-width: 100%; - gap: inherit; -`; - -const Description = styled.div` - display: flex; - flex-direction: column; - align-self: start; - gap: ${({ theme }) => theme.click.card.horizontal.space.md.gap}; - flex: 1; - width: 100%; -`; - -const Wrapper = styled.div<{ - $hasShadow?: boolean; - $disabled?: boolean; - $isSelected?: boolean; - $isSelectable?: boolean; - $color: CardColor; - $size?: CardSize; -}>` - display: inline-flex; - width: 100%; - max-width: 100%; - align-items: center; - justify-content: flex-start; - - ${({ theme, $color, $size, $isSelected, $isSelectable, $disabled }) => ` - background: ${theme.click.card.horizontal[$color].color.background.default}; - color: ${theme.click.card.horizontal[$color].color.title.default}; - border-radius: ${theme.click.card.horizontal.radii.all}; - border: 1px solid ${ - theme.click.card.horizontal[$color].color.stroke[ - $isSelectable ? ($isSelected ? "active" : "hover") : "default" - ] - }; - padding: ${ - $size === "md" - ? `${theme.click.card.horizontal.space.md.y} ${theme.click.card.horizontal.space.md.x}` - : `${theme.click.card.horizontal.space.sm.y} ${theme.click.card.horizontal.space.sm.x}` - }; - font: ${theme.click.card.horizontal.typography.title.default}; - ${Description} { - color: ${theme.click.card.horizontal[$color].color.description.default}; - font: ${theme.click.card.horizontal.typography.description.default}; - } - &:hover{ - background-color: ${ - theme.click.card.horizontal[$color].color.background[ - $isSelectable ? "hover" : "default" - ] - }; - color: ${ - theme.click.card.horizontal[$color].color.title[ - $isSelectable ? "hover" : "default" - ] - }; - border: 1px solid ${ - theme.click.card.horizontal[$color].color.stroke[ - $isSelectable ? ($isSelected ? "active" : "default") : "default" - ] - }; - cursor: ${$isSelectable ? "pointer" : "default"}; - font: ${theme.click.card.horizontal.typography.title.hover}; - ${Description} { - color: ${ - theme.click.card.horizontal[$color].color.description[ - $isSelectable ? "hover" : "default" - ] - }; - font: ${ - theme.click.card.horizontal.typography.description[ - $isSelectable ? "hover" : "default" - ] - }; - } - } - - &:active, &:focus, &:focus-within { - background-color: ${ - theme.click.card.horizontal[$color].color.background[ - $isSelectable ? "active" : "default" - ] - }; - color: ${ - theme.click.card.horizontal[$color].color.title[ - $isSelectable ? "active" : "default" - ] - }; - border: 1px solid ${ - theme.click.card.horizontal[$color].color.stroke[ - $isSelectable ? "active" : "default" - ] - }; - ${Description} { - color: ${ - theme.click.card.horizontal[$color].color.description[ - $isSelectable ? "active" : "default" - ] - }; - font: ${ - theme.click.card.horizontal.typography.description[ - $isSelectable ? "active" : "default" - ] - }; - } - } - ${ - $disabled - ? ` - pointer-events: none; - &, - &:hover, - &:active, &:focus, &:focus-within { - background-color: ${ - theme.click.card.horizontal[$color].color.background.disabled - }; - color: ${theme.click.card.horizontal[$color].color.title.disabled}; - border: 1px solid ${ - theme.click.card.horizontal[$color].color.stroke[ - $isSelected ? "active" : "disabled" - ] - }; - cursor: not-allowed; - ${Description} { - color: ${theme.click.card.horizontal[$color].color.description.disabled}; - font: ${theme.click.card.horizontal.typography.description.disabled}; - } - }, - &:active, &:focus, &:focus-within { - border: 1px solid ${theme.click.card.horizontal[$color].color.stroke.active}; - } - ` - : "" - } - `} -`; - -const CardIcon = styled(Icon)` - ${({ theme }) => ` - height: ${theme.click.card.horizontal.icon.size.all}; - width: ${theme.click.card.horizontal.icon.size.all}; - `} -`; - -const ContentWrapper = styled.div<{ $size: CardSize }>` - display: flex; - flex-direction: row; - width: 100%; - gap: ${({ theme, $size }) => - $size === "md" - ? theme.click.card.horizontal.space.md.gap - : theme.click.card.horizontal.space.sm.gap}; - - @media (max-width: ${({ theme }) => theme.breakpoint.sizes.md}) { - flex-direction: column; - } -`; - -const IconTextContentWrapper = styled.div<{ $size: CardSize }>` - display: flex; - flex-direction: row; - align-items: center; - width: 100%; - gap: ${({ theme, $size }) => - $size === "md" - ? theme.click.card.horizontal.space.md.gap - : theme.click.card.horizontal.space.sm.gap}; -`; - export const CardHorizontal = ({ title, icon, @@ -223,10 +56,10 @@ export const CardHorizontal = ({ badgeIcon, badgeIconDir, onButtonClick, + className, ...props }: CardHorizontalProps) => { const handleClick = (e: React.MouseEvent) => { - MouseEvent; if (typeof onButtonClick === "function") { onButtonClick(e); } @@ -234,23 +67,49 @@ export const CardHorizontal = ({ window.open(infoUrl, "_blank"); } }; + + const colorClass = `cuiColor${capitalize(color)}`; + const sizeClass = `cuiSize${capitalize(size)}`; + const selectedClass = isSelected ? "cuiSelected" : undefined; + const disabledClass = disabled ? "cuiDisabled" : undefined; + + const wrapperClasses = clsx( + styles.cuiWrapper, + styles[colorClass], + styles[sizeClass], + selectedClass && styles[selectedClass], + disabledClass && styles[disabledClass], + { + [styles.cuiIsSelectable]: isSelectable, + }, + className + ); + + const contentWrapperClasses = clsx(styles.cuiContentWrapper, styles[sizeClass]); + const iconTextContentWrapperClasses = clsx( + styles.cuiIconTextContentWrapper, + styles[sizeClass] + ); + const iconClasses = clsx(styles.cuiCardIcon); + return ( - - - +
+
{icon && ( - )} {title && ( -
+
)} -
+
)} - {description && {description}} - {children && {children}} + {description &&
{description}
} + {children &&
{children}
} - +
{infoText && ( )} -
-
+ + ); }; diff --git a/src/components/CardPrimary/CardPrimary.module.scss b/src/components/CardPrimary/CardPrimary.module.scss new file mode 100644 index 000000000..d490ffbfb --- /dev/null +++ b/src/components/CardPrimary/CardPrimary.module.scss @@ -0,0 +1,189 @@ +@use "cui-mixins" as mixins; +@use "cui-variants" as variants; + +// Icon styles with composes - overrides Icon's default size +.cuiIconSm { + composes: cuiIconWrapper from "../Icon/Icon.module.scss"; + width: var(--click-card-primary-size-icon-sm-all); + height: var(--click-card-primary-size-icon-sm-all); +} + +.cuiIconMd { + composes: cuiIconWrapper from "../Icon/Icon.module.scss"; + width: var(--click-card-primary-size-icon-md-all); + height: var(--click-card-primary-size-icon-md-all); +} + +.cuiWrapper { + @include mixins.cuiCardBase; + + background-color: var(--click-card-primary-color-background-default); + border-radius: var(--click-card-primary-radii-all); + border: 1px solid var(--click-card-primary-color-stroke-default); + display: flex; + width: 100%; + max-width: 100%; + flex-direction: column; + box-shadow: none; + + &.cuiHasShadow { + box-shadow: var(--shadow-1); + } + + // Size variants - using :where() for low specificity + @include variants.variant("cuiSizeSm") { + padding: var(--click-card-primary-space-sm-x) var(--click-card-primary-space-sm-y); + gap: var(--click-card-primary-space-sm-gap); + } + + @include variants.variant("cuiSizeMd") { + padding: var(--click-card-primary-space-md-x) var(--click-card-primary-space-md-y); + gap: var(--click-card-primary-space-md-gap); + } + + // Align variants - using :where() for low specificity + @include variants.variant("cuiAlignStart") { + text-align: left; + } + + @include variants.variant("cuiAlignCenter") { + text-align: center; + } + + @include variants.variant("cuiAlignEnd") { + text-align: right; + } + + &:hover, + &:focus { + background-color: var(--click-card-secondary-color-background-hover); + cursor: pointer; + + button { + background-color: var(--click-button-basic-color-primary-background-hover); + border-color: var(--click-button-basic-color-primary-stroke-hover); + + &:active { + background-color: var(--click-button-basic-color-primary-background-active); + border-color: var(--click-button-basic-color-primary-stroke-active); + } + } + } + + &:active { + border-color: var(--click-button-basic-color-primary-stroke-active); + } + + &.cuiIsSelected { + border-color: var(--click-button-basic-color-primary-stroke-active); + } + + &[aria-disabled="true"], + &[aria-disabled="true"]:hover, + &[aria-disabled="true"]:focus, + &[aria-disabled="true"]:active { + pointer-events: none; + background-color: var(--click-card-primary-color-background-disabled); + color: var(--click-card-primary-color-title-disabled); + border: 1px solid var(--click-card-primary-color-stroke-disabled); + cursor: not-allowed; + + button { + background-color: var(--click-button-basic-color-primary-background-disabled); + border-color: var(--click-button-basic-color-primary-stroke-disabled); + + &:active { + background-color: var(--click-button-basic-color-primary-background-disabled); + border-color: var(--click-button-basic-color-primary-stroke-disabled); + } + } + } +} + +.cuiHeader { + display: flex; + flex-direction: column; + + // Align variants - using :where() for low specificity + @include variants.variant("cuiAlignStart") { + align-items: flex-start; + } + + @include variants.variant("cuiAlignCenter") { + align-items: center; + } + + @include variants.variant("cuiAlignEnd") { + align-items: flex-end; + } + + // Size variants - using :where() for low specificity + @include variants.variant("cuiSizeSm") { + gap: var(--click-card-primary-space-sm-gap); + } + + @include variants.variant("cuiSizeMd") { + gap: var(--click-card-primary-space-md-gap); + } + + h3 { + color: var(--global-color-text-default); + } + + &.cuiDisabled h3 { + color: var(--global-color-text-muted); + } + + // Icon wrapper sizing (applies to Icon component wrapper) + .cuiSizeSm { + height: var(--click-card-primary-size-icon-sm-all); + width: var(--click-card-primary-size-icon-sm-all); + } + + .cuiSizeMd { + height: var(--click-card-primary-size-icon-md-all); + width: var(--click-card-primary-size-icon-md-all); + } + + // Direct img sizing (for iconUrl prop) + img { + &.cuiSizeSm { + height: var(--click-card-primary-size-icon-sm-all); + width: var(--click-card-primary-size-icon-sm-all); + } + + &.cuiSizeMd { + height: var(--click-card-primary-size-icon-md-all); + width: var(--click-card-primary-size-icon-md-all); + } + } +} + +.cuiContent { + width: 100%; + display: flex; + flex-direction: column; + flex: 1; + + // Align variants - using :where() for low specificity + @include variants.variant("cuiAlignStart") { + align-self: flex-start; + } + + @include variants.variant("cuiAlignCenter") { + align-self: center; + } + + @include variants.variant("cuiAlignEnd") { + align-self: flex-end; + } + + // Size variants - using :where() for low specificity + @include variants.variant("cuiSizeSm") { + gap: var(--click-card-primary-space-sm-gap); + } + + @include variants.variant("cuiSizeMd") { + gap: var(--click-card-primary-space-md-gap); + } +} diff --git a/src/components/CardPrimary/CardPrimary.tsx b/src/components/CardPrimary/CardPrimary.tsx index 8ae47ccce..c8d4880d4 100644 --- a/src/components/CardPrimary/CardPrimary.tsx +++ b/src/components/CardPrimary/CardPrimary.tsx @@ -1,9 +1,13 @@ -import { styled } from "styled-components"; +"use client"; + +import clsx from "clsx"; import { Button, Icon, Spacer, IconName } from "@/components"; import { Title } from "@/components/Typography/Title/Title"; import { Text, TextAlignment } from "@/components/Typography/Text/Text"; import { HTMLAttributes, MouseEvent, MouseEventHandler, ReactNode } from "react"; import { WithTopBadgeProps, withTopBadge } from "@/components/CardPrimary/withTopBadge"; +import { capitalize } from "../../utils/capitalize"; +import styles from "./CardPrimary.module.scss"; export type CardPrimarySize = "sm" | "md"; type ContentAlignment = "start" | "center" | "end"; @@ -25,109 +29,6 @@ export interface CardPrimaryProps onButtonClick?: MouseEventHandler; } -const Wrapper = styled.div<{ - $size?: CardPrimarySize; - $hasShadow?: boolean; - $isSelected?: boolean; - $alignContent?: ContentAlignment; -}>` - background-color: ${({ theme }) => theme.click.card.primary.color.background.default}; - border-radius: ${({ theme }) => theme.click.card.primary.radii.all}; - border: ${({ theme }) => `1px solid ${theme.click.card.primary.color.stroke.default}`}; - display: flex; - width: 100%; - max-width: 100%; - text-align: ${({ $alignContent }) => - $alignContent === "start" ? "left" : $alignContent === "end" ? "right" : "center"}; - flex-direction: column; - padding: ${({ $size = "md", theme }) => - `${theme.click.card.primary.space[$size].x} ${theme.click.card.primary.space[$size].y}`}; - gap: ${({ $size = "md", theme }) => theme.click.card.primary.space[$size].gap}; - box-shadow: ${({ $hasShadow, theme }) => ($hasShadow ? theme.shadow[1] : "none")}; - - &:hover, - &:focus { - background-color: ${({ theme }) => theme.click.card.secondary.color.background.hover}; - cursor: pointer; - button { - background-color: ${({ theme }) => - theme.click.button.basic.color.primary.background.hover}; - border-color: ${({ theme }) => theme.click.button.basic.color.primary.stroke.hover}; - &:active { - background-color: ${({ theme }) => - theme.click.button.basic.color.primary.background.active}; - border-color: ${({ theme }) => - theme.click.button.basic.color.primary.stroke.active}; - } - } - } - - &:active { - border-color: ${({ theme }) => theme.click.button.basic.color.primary.stroke.active}; - } - - &[aria-disabled="true"], - &[aria-disabled="true"]:hover, - &[aria-disabled="true"]:focus, - &[aria-disabled="true"]:active { - pointer-events: none; - ${({ theme }) => ` - background-color: ${theme.click.card.primary.color.background.disabled}; - color: ${theme.click.card.primary.color.title.disabled}; - border: 1px solid ${theme.click.card.primary.color.stroke.disabled}; - cursor: not-allowed; - - button { - background-color: ${theme.click.button.basic.color.primary.background.disabled}; - border-color: ${theme.click.button.basic.color.primary.stroke.disabled}; - &:active { - background-color: ${theme.click.button.basic.color.primary.background.disabled}; - border-color: ${theme.click.button.basic.color.primary.stroke.disabled}; - } - }`} - } - - ${({ $isSelected, theme }) => - $isSelected - ? `border-color: ${theme.click.button.basic.color.primary.stroke.active};` - : ""} -`; - -const Header = styled.div<{ - $size?: "sm" | "md"; - $disabled?: boolean; - $alignContent?: ContentAlignment; -}>` - display: flex; - flex-direction: column; - align-items: ${({ $alignContent = "center" }) => - ["start", "end"].includes($alignContent) ? `flex-${$alignContent}` : $alignContent}; - gap: ${({ $size = "md", theme }) => theme.click.card.primary.space[$size].gap}; - - h3 { - color: ${({ $disabled, theme }) => - $disabled == true - ? theme.click.global.color.text.muted - : theme.click.global.color.text.default}; - } - - svg, - img { - height: ${({ $size = "md", theme }) => theme.click.card.primary.size.icon[$size].all}; - width: ${({ $size = "md", theme }) => theme.click.card.primary.size.icon[$size].all}; - } -`; - -const Content = styled.div<{ $size?: "sm" | "md"; $alignContent?: ContentAlignment }>` - width: 100%; - display: flex; - flex-direction: column; - align-self: ${({ $alignContent = "center" }) => - ["start", "end"].includes($alignContent) ? `flex-${$alignContent}` : $alignContent}; - gap: ${({ $size = "md", theme }) => theme.click.card.primary.space[$size].gap}; - flex: 1; -`; - const convertCardAlignToTextAlign = (align: ContentAlignment): TextAlignment => { if (align === "center") { return "center"; @@ -136,7 +37,7 @@ const convertCardAlignToTextAlign = (align: ContentAlignment): TextAlignment => }; const Card = ({ - alignContent, + alignContent = "center", title, icon, iconUrl, @@ -144,11 +45,12 @@ const Card = ({ description, infoUrl, infoText, - size, + size = "md", disabled = false, onButtonClick, isSelected, children, + className, ...props }: CardPrimaryProps) => { const handleClick = (e: MouseEvent) => { @@ -160,56 +62,81 @@ const Card = ({ } }; + const sizeClass = `cuiSize${capitalize(size)}`; + const alignClass = `cuiAlign${capitalize(alignContent)}`; + const iconSizeClass = `cuiIcon${capitalize(size)}`; + + const wrapperClasses = clsx( + styles.cuiWrapper, + styles[sizeClass], + styles[alignClass], + { + [styles.cuiHasShadow]: hasShadow, + [styles.cuiIsSelected]: isSelected, + }, + className + ); + + const headerClasses = clsx(styles.cuiHeader, styles[sizeClass], styles[alignClass], { + [styles.cuiDisabled]: disabled, + }); + + const contentClasses = clsx(styles.cuiContent, styles[sizeClass], styles[alignClass]); + + const iconClasses = styles[iconSizeClass]; + const Component = !!infoUrl || typeof onButtonClick === "function" ? Button : "div"; return ( - {(icon || title) && ( -
{iconUrl ? ( card icon ) : ( icon && ( ) )} {title && {title}} -
+ )} {(description || children) && ( - {description && ( {description} )} {children} - + )} {size == "sm" && } @@ -222,7 +149,7 @@ const Card = ({ {infoText} )} -
+ ); }; diff --git a/src/components/CardPrimary/CardPrimaryTopBadge.module.scss b/src/components/CardPrimary/CardPrimaryTopBadge.module.scss new file mode 100644 index 000000000..fe0d8307f --- /dev/null +++ b/src/components/CardPrimary/CardPrimaryTopBadge.module.scss @@ -0,0 +1,12 @@ +@use "cui-mixins" as mixins; + +.cuiTopBadgeWrapper { + position: relative; +} + +.cuiCardPrimaryTopBadge { + position: absolute; + top: 0; + left: 50%; + transform: translate(-50%, -50%); +} diff --git a/src/components/CardPrimary/CardPrimaryTopBadge.tsx b/src/components/CardPrimary/CardPrimaryTopBadge.tsx index da2835f7b..bcfaed224 100644 --- a/src/components/CardPrimary/CardPrimaryTopBadge.tsx +++ b/src/components/CardPrimary/CardPrimaryTopBadge.tsx @@ -1,21 +1,39 @@ -import { Badge } from "@/components/Badge/Badge"; -import { Container } from "@/components/Container/Container"; -import { styled } from "styled-components"; +import clsx from "clsx"; +import { Badge, CommonBadgeProps } from "@/components/Badge/Badge"; +import { Container, ContainerProps } from "@/components/Container/Container"; +import styles from "./CardPrimaryTopBadge.module.scss"; -export const TopBadgeWrapper = styled(Container)` - position: relative; -`; +interface TopBadgeWrapperProps extends ContainerProps { + className?: string; +} -export const CardPrimaryTopBadge = styled(Badge)<{ $isSelected?: boolean }>` - position: absolute; - top: 0; - left: 50%; - transform: translate(-50%, -50%); - ${({ $isSelected, theme }) => - $isSelected - ? `border-color: ${theme.click.button.basic.color.primary.stroke.active};` - : ""} - div:active + & { - border-color: ${({ theme }) => theme.click.button.basic.color.primary.stroke.active}; - } -`; +export const TopBadgeWrapper = ({ className, ...props }: TopBadgeWrapperProps) => ( + +); + +interface CardPrimaryTopBadgeProps extends CommonBadgeProps { + $isSelected?: boolean; + className?: string; + dismissible?: never; + onClose?: never; +} + +export const CardPrimaryTopBadge = ({ + $isSelected, + className, + ...props +}: CardPrimaryTopBadgeProps) => { + const badgeClasses = clsx(styles.cuiCardPrimaryTopBadge, className); + + return ( + + ); +}; diff --git a/src/components/CardPromotion/CardPromotion.module.scss b/src/components/CardPromotion/CardPromotion.module.scss new file mode 100644 index 000000000..86adbe490 --- /dev/null +++ b/src/components/CardPromotion/CardPromotion.module.scss @@ -0,0 +1,62 @@ +@use "cui-mixins" as mixins; + +// Icon styles with composes - overrides Icon's default size +.cuiCardIcon { + composes: cuiIconWrapper from '../Icon/Icon.module.scss'; + height: var(--click-card-promotion-icon-size-all); + width: var(--click-card-promotion-icon-size-all); + color: var(--click-card-promotion-color-icon-default); +} + +.cuiBackground { + background-image: var(--click-card-promotion-color-stroke-default); + padding: 1px; + border-radius: var(--click-card-promotion-radii-all); + box-shadow: var(--click-card-shadow); + display: flex; + + &:focus { + background: var(--click-card-promotion-color-stroke-focus); + } +} + +.cuiWrapper { + display: flex; + width: 100%; + align-items: center; + justify-content: flex-start; + cursor: pointer; + background: var(--click-card-promotion-color-background-default); + color: var(--click-card-promotion-color-text-default); + border-radius: var(--click-card-promotion-radii-all); + padding: var(--click-card-promotion-space-y) var(--click-card-promotion-space-x); + gap: var(--click-card-promotion-space-gap); + transition: 0.2s ease-in-out all; + + &:hover { + background: var(--click-card-promotion-color-background-hover); + color: var(--click-card-promotion-color-text-hover); + } + + &:active, + &:focus { + background: var(--click-card-promotion-color-background-active); + color: var(--click-card-promotion-color-text-active); + } +} + +.cuiCardIcon { + height: var(--click-card-promotion-icon-size-all); + width: var(--click-card-promotion-icon-size-all); + color: var(--click-card-promotion-color-icon-default); +} + +.cuiDismissWrapper { + display: flex; + align-items: center; + margin-left: auto; + border: none; + background-color: transparent; + color: inherit; + cursor: pointer; +} diff --git a/src/components/CardPromotion/CardPromotion.tsx b/src/components/CardPromotion/CardPromotion.tsx index b177c8001..b939b6f96 100644 --- a/src/components/CardPromotion/CardPromotion.tsx +++ b/src/components/CardPromotion/CardPromotion.tsx @@ -1,95 +1,42 @@ +"use client"; + import { HTMLAttributes, useState } from "react"; -import { styled } from "styled-components"; +import clsx from "clsx"; import { Icon, IconName, Text } from "@/components"; +import styles from "./CardPromotion.module.scss"; export interface CardPromotionProps extends HTMLAttributes { label: string; icon: IconName; dismissible?: boolean; } -const Background = styled.div` - ${({ theme }) => ` - background-image: ${theme.click.card.promotion.color.stroke.default}; - padding: 1px; - border-radius: ${theme.click.card.promotion.radii.all}; - box-shadow: ${theme.click.card.shadow}; - display: flex; - - &:focus { - background: ${theme.click.card.promotion.color.stroke.focus}; - } - `} -`; -const Wrapper = styled.div<{ - $dismissible?: boolean; -}>` - display: flex; - width: 100%; - align-items: center; - justify-content: flex-start; - cursor: pointer; - - ${({ theme }) => ` - background: ${theme.click.card.promotion.color.background.default}; - color: ${theme.click.card.promotion.color.text.default}; - border-radius: ${theme.click.card.promotion.radii.all}; - padding: ${theme.click.card.promotion.space.y} ${theme.click.card.promotion.space.x}; - gap: ${theme.click.card.promotion.space.gap}; - transition: .2s ease-in-out all; - - &:hover { - background: ${theme.click.card.promotion.color.background.hover}; - color: ${theme.click.card.promotion.color.text.hover}; - } - - &:active, &:focus { - background: ${theme.click.card.promotion.color.background.active}; - color: ${theme.click.card.promotion.color.text.active}; - } - `} -`; - -const CardIcon = styled(Icon)` - ${({ theme }) => ` - height: ${theme.click.card.promotion.icon.size.all}; - width: ${theme.click.card.promotion.icon.size.all}; - color: ${theme.click.card.promotion.color.icon.default}; - `} -`; - -const DismissWrapper = styled.button` - display: flex; - align-items: center; - margin-left: auto; - border: none; - background-color: transparent; - color: inherit; - cursor: pointer; -`; export const CardPromotion = ({ label, icon, dismissible = false, + className, ...props }: CardPromotionProps) => { const [isVisible, setIsVisible] = useState(true); return isVisible ? ( - - +
- {label} {dismissible && ( - setIsVisible(false)} > @@ -97,9 +44,9 @@ export const CardPromotion = ({ name="cross" aria-label="close" /> - + )} - - +
+ ) : null; }; diff --git a/src/components/CardSecondary/CardSecondary.module.scss b/src/components/CardSecondary/CardSecondary.module.scss new file mode 100644 index 000000000..ac704284e --- /dev/null +++ b/src/components/CardSecondary/CardSecondary.module.scss @@ -0,0 +1,112 @@ +@use "cui-mixins" as mixins; +@use "cui-variants" as variants; + +.cuiWrapper { + background-color: var(--click-card-secondary-color-background-default); + border-radius: var(--click-card-secondary-radii-all); + border: 1px solid var(--click-card-secondary-color-stroke-default); + max-width: 420px; + min-width: 320px; + display: flex; + flex-direction: column; + padding: var(--click-card-secondary-space-all); + gap: var(--click-card-secondary-space-gap); + + @include variants.variant('cuiHasShadow') { + box-shadow: var(--shadow-1); + } + + &:hover, + &:focus { + background-color: var(--click-card-secondary-color-background-hover); + cursor: pointer; + + .cuiLinkText, + .cuiLinkIcon { + color: var(--click-card-secondary-color-link-hover); + } + } + + @include variants.variant('cuiDisabled') { + pointer-events: none; + background-color: var(--click-card-secondary-color-background-disabled); + color: var(--click-card-secondary-color-title-disabled); + border: 1px solid var(--click-card-secondary-color-stroke-disabled); + cursor: not-allowed; + + .cuiLinkText, + .cuiLinkIcon { + color: var(--click-card-secondary-color-link-disabled); + } + + &:hover, + &:focus, + &:active { + pointer-events: none; + background-color: var(--click-card-secondary-color-background-disabled); + color: var(--click-card-secondary-color-title-disabled); + border: 1px solid var(--click-card-secondary-color-stroke-disabled); + cursor: not-allowed; + + .cuiLinkText, + .cuiLinkIcon { + color: var(--click-card-secondary-color-link-disabled); + } + } + } +} + +.cuiHeader { + display: flex; + justify-content: space-between; + align-items: center; +} + +.cuiHeaderLeft { + display: flex; + align-items: center; + gap: var(--click-card-secondary-space-gap); + + h3, + svg { + color: var(--global-color-text-default); + } + + @include variants.variant('cuiDisabled') { + h3, + svg { + color: var(--global-color-text-muted); + } + } +} + +.cuiContent { + display: flex; + flex-direction: column; + flex: 1; +} + +.cuiCustomIcon { + composes: cuiIconWrapper from '../Icon/Icon.module.scss'; + height: var(--click-image-lg-size-height); + width: var(--click-image-lg-size-width); +} + +.cuiInfoLink { + display: flex; + align-items: center; + color: var(--click-card-secondary-color-link-default); + gap: var(--click-card-secondary-space-link-gap); + text-decoration: none; +} + +.cuiLinkText { + color: var(--click-card-secondary-color-link-default); +} + +.cuiLinkIcon { + composes: cuiIconWrapper from '../Icon/Icon.module.scss'; + color: var(--click-card-secondary-color-link-default); + height: var(--click-image-md-size-height); + width: var(--click-image-md-size-width); +} diff --git a/src/components/CardSecondary/CardSecondary.tsx b/src/components/CardSecondary/CardSecondary.tsx index a6b92c45f..175d63fea 100644 --- a/src/components/CardSecondary/CardSecondary.tsx +++ b/src/components/CardSecondary/CardSecondary.tsx @@ -1,9 +1,12 @@ -import { styled } from "styled-components"; +"use client"; + import { Badge, Icon, IconName } from "@/components"; import { Title } from "@/components/Typography/Title/Title"; import { Text } from "@/components/Typography/Text/Text"; import { IconSize } from "@/components/Icon/types"; import { HTMLAttributes, ReactNode } from "react"; +import clsx from "clsx"; +import styles from "./CardSecondary.module.scss"; export type BadgeState = | "default" @@ -29,97 +32,6 @@ export interface CardSecondaryProps extends HTMLAttributes { infoIconSize?: IconSize; } -const Header = styled.div` - display: flex; - justify-content: space-between; - align-items: center; -`; - -const HeaderLeft = styled.div<{ $disabled?: boolean }>` - display: flex; - align-items: center; - gap: ${({ theme }) => theme.click.card.secondary.space.gap}; - - h3, - svg { - color: ${({ $disabled, theme }) => - $disabled == true - ? theme.click.global.color.text.muted - : theme.click.global.color.text.default}; - } -`; - -const Content = styled.div` - display: flex; - flex-direction: column; - flex: 1; -`; - -const CustomIcon = styled.img` - height: ${({ theme }) => theme.click.image.lg.size.height}; - width: ${({ theme }) => theme.click.image.lg.size.width}; -`; - -const InfoLink = styled.a` - display: flex; - align-items: center; - color: ${({ theme }) => theme.click.card.secondary.color.link.default}; - gap: ${({ theme }) => theme.click.card.secondary.space.link.gap}; - text-decoration: none; -`; -const LinkIconContainer = styled(Icon)` - color: ${({ theme }) => theme.click.card.secondary.color.link.default}; - height: ${({ theme }) => theme.click.image.md.size.height}; - width: ${({ theme }) => theme.click.image.md.size.width}; -`; - -const LinkText = styled(Text)``; -const LinkIcon = styled(LinkIconContainer)``; - -const Wrapper = styled.div<{ - $hasShadow?: boolean; -}>` - background-color: ${({ theme }) => theme.click.card.secondary.color.background.default}; - border-radius: ${({ theme }) => theme.click.card.secondary.radii.all}; - border: ${({ theme }) => - `1px solid ${theme.click.card.secondary.color.stroke.default}`}; - max-width: 420px; - min-width: 320px; - display: flex; - flex-direction: column; - padding: ${({ theme }) => theme.click.card.secondary.space.all}; - gap: ${({ theme }) => theme.click.card.secondary.space.gap}; - box-shadow: ${({ $hasShadow, theme }) => ($hasShadow ? theme.shadow[1] : "none")}; - - &:hover, - :focus { - background-color: ${({ theme }) => theme.click.card.secondary.color.background.hover}; - cursor: pointer; - ${LinkText}, - ${LinkIcon} { - color: ${({ theme }) => theme.click.card.secondary.color.link.hover}; - } - } - - &[aria-disabled="true"], - &[aria-disabled="true"]:hover, - &[aria-disabled="true"]:focus, - &[aria-disabled="true"]:active { - pointer-events: none; - ${({ theme }) => ` - background-color: ${theme.click.card.secondary.color.background.disabled}; - color: ${theme.click.card.secondary.color.title.disabled}; - border: 1px solid ${theme.click.card.secondary.color.stroke.disabled}; - cursor: not-allowed; - - ${LinkText}, - ${LinkIcon} { - color: ${theme.click.card.secondary.color.link.disabled}; - } - `} - } -`; - export const CardSecondary = ({ title, icon, @@ -133,19 +45,35 @@ export const CardSecondary = ({ infoText, infoIcon = "chevron-right", infoIconSize = "md", + className, ...props }: CardSecondaryProps) => { + const InfoLinkComponent = disabled || !infoUrl || infoUrl.length === 0 ? "div" : "a"; + return ( - -
- +
+
{iconUrl ? ( - {title} - +
{badgeText && ( )} -
+ - +
{description} - +
{(infoUrl || infoText) && ( - - {infoText} - {infoText} + - + )} -
+ ); }; diff --git a/src/components/Checkbox/Checkbox.module.scss b/src/components/Checkbox/Checkbox.module.scss new file mode 100644 index 000000000..aedbf9d89 --- /dev/null +++ b/src/components/Checkbox/Checkbox.module.scss @@ -0,0 +1,192 @@ +@use "cui-mixins" as mixins; +@use "cui-variants" as variants; + +.cuiWrapper { + @include mixins.cuiFormRoot($align: center, $maxWidth: fit-content); + + // Orientation and direction combinations using :where() for low specificity + @include variants.variant('cuiOrientationHorizontal') { + @include variants.variant('cuiDirStart') { + flex-direction: row-reverse; + } + @include variants.variant('cuiDirEnd') { + flex-direction: row; + } + } + + @include variants.variant('cuiOrientationVertical') { + @include variants.variant('cuiDirStart') { + flex-direction: column-reverse; + } + @include variants.variant('cuiDirEnd') { + flex-direction: column; + } + } +} + +.cuiCheckInput { + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + border-radius: var(--click-checkbox-radii-all); + width: var(--click-checkbox-size-all); + height: var(--click-checkbox-size-all); + cursor: pointer; + border: 1px solid; + + // Variant styles using :where() for low specificity + @include variants.variant('cuiVariantDefault') { + background: var(--click-checkbox-color-variations-default-background-default); + border-color: var(--click-checkbox-color-variations-default-stroke-default); + + @include variants.variant('cuiCheckedTrue') { + border-color: var(--click-checkbox-color-variations-default-stroke-active); + background: var(--click-checkbox-color-variations-default-background-active); + } + + @include variants.variant('cuiCheckedIndeterminate') { + border-color: var(--click-checkbox-color-variations-default-stroke-active); + background: var(--click-checkbox-color-variations-default-background-active); + } + } + + @include variants.variant('cuiVariantVar1') { + background: var(--click-checkbox-color-variations-var1-background-default); + border-color: var(--click-checkbox-color-variations-var1-stroke-default); + + &:hover { + background: var(--click-checkbox-color-variations-var1-background-hover); + } + + @include variants.variant('cuiCheckedTrue') { + border-color: var(--click-checkbox-color-variations-var1-stroke-active); + background: var(--click-checkbox-color-variations-var1-background-active); + } + + @include variants.variant('cuiCheckedIndeterminate') { + border-color: var(--click-checkbox-color-variations-var1-stroke-active); + background: var(--click-checkbox-color-variations-var1-background-active); + } + } + + @include variants.variant('cuiVariantVar2') { + background: var(--click-checkbox-color-variations-var2-background-default); + border-color: var(--click-checkbox-color-variations-var2-stroke-default); + + &:hover { + background: var(--click-checkbox-color-variations-var2-background-hover); + } + + @include variants.variant('cuiCheckedTrue') { + border-color: var(--click-checkbox-color-variations-var2-stroke-active); + background: var(--click-checkbox-color-variations-var2-background-active); + } + + @include variants.variant('cuiCheckedIndeterminate') { + border-color: var(--click-checkbox-color-variations-var2-stroke-active); + background: var(--click-checkbox-color-variations-var2-background-active); + } + } + + @include variants.variant('cuiVariantVar3') { + background: var(--click-checkbox-color-variations-var3-background-default); + border-color: var(--click-checkbox-color-variations-var3-stroke-default); + + &:hover { + background: var(--click-checkbox-color-variations-var3-background-hover); + } + + @include variants.variant('cuiCheckedTrue') { + border-color: var(--click-checkbox-color-variations-var3-stroke-active); + background: var(--click-checkbox-color-variations-var3-background-active); + } + + @include variants.variant('cuiCheckedIndeterminate') { + border-color: var(--click-checkbox-color-variations-var3-stroke-active); + background: var(--click-checkbox-color-variations-var3-background-active); + } + } + + @include variants.variant('cuiVariantVar4') { + background: var(--click-checkbox-color-variations-var4-background-default); + border-color: var(--click-checkbox-color-variations-var4-stroke-default); + + &:hover { + background: var(--click-checkbox-color-variations-var4-background-hover); + } + + @include variants.variant('cuiCheckedTrue') { + border-color: var(--click-checkbox-color-variations-var4-stroke-active); + background: var(--click-checkbox-color-variations-var4-background-active); + } + + @include variants.variant('cuiCheckedIndeterminate') { + border-color: var(--click-checkbox-color-variations-var4-stroke-active); + background: var(--click-checkbox-color-variations-var4-background-active); + } + } + + @include variants.variant('cuiVariantVar5') { + background: var(--click-checkbox-color-variations-var5-background-default); + border-color: var(--click-checkbox-color-variations-var5-stroke-default); + + &:hover { + background: var(--click-checkbox-color-variations-var5-background-hover); + } + + @include variants.variant('cuiCheckedTrue') { + border-color: var(--click-checkbox-color-variations-var5-stroke-active); + background: var(--click-checkbox-color-variations-var5-background-active); + } + + @include variants.variant('cuiCheckedIndeterminate') { + border-color: var(--click-checkbox-color-variations-var5-stroke-active); + background: var(--click-checkbox-color-variations-var5-background-active); + } + } + + @include variants.variant('cuiVariantVar6') { + background: var(--click-checkbox-color-variations-var6-background-default); + border-color: var(--click-checkbox-color-variations-var6-stroke-default); + + &:hover { + background: var(--click-checkbox-color-variations-var6-background-hover); + } + + @include variants.variant('cuiCheckedTrue') { + border-color: var(--click-checkbox-color-variations-var6-stroke-active); + background: var(--click-checkbox-color-variations-var6-background-active); + } + + @include variants.variant('cuiCheckedIndeterminate') { + border-color: var(--click-checkbox-color-variations-var6-stroke-active); + background: var(--click-checkbox-color-variations-var6-background-active); + } + } + + // Disabled state using :where() for low specificity + @include variants.variant('cuiDisabled') { + background: var(--click-checkbox-color-background-disabled); + border-color: var(--click-checkbox-color-stroke-disabled); + cursor: not-allowed; + + @include variants.variant('cuiCheckedTrue') { + background: var(--click-checkbox-color-background-disabled); + border-color: var(--click-checkbox-color-stroke-disabled); + } + + @include variants.variant('cuiCheckedIndeterminate') { + background: var(--click-checkbox-color-background-disabled); + border-color: var(--click-checkbox-color-stroke-disabled); + } + } +} + +.cuiCheckIconWrapper { + color: var(--click-checkbox-color-check-active); + + @include variants.variant('cuiDisabled') { + color: var(--click-checkbox-color-check-disabled); + } +} diff --git a/src/components/Checkbox/Checkbox.test.tsx b/src/components/Checkbox/Checkbox.test.tsx index 452ec2789..2e8f170c4 100644 --- a/src/components/Checkbox/Checkbox.test.tsx +++ b/src/components/Checkbox/Checkbox.test.tsx @@ -30,8 +30,8 @@ describe("Checkbox", () => { const checkbox = getByTestId("checkbox"); - const computedStyle = window.getComputedStyle(checkbox); - expect(computedStyle.cursor).toBe("not-allowed"); + // Check if disabled attribute is set + expect(checkbox).toBeDisabled(); fireEvent.click(checkbox); diff --git a/src/components/Checkbox/Checkbox.tsx b/src/components/Checkbox/Checkbox.tsx index e81662094..9fbeaec69 100644 --- a/src/components/Checkbox/Checkbox.tsx +++ b/src/components/Checkbox/Checkbox.tsx @@ -1,8 +1,11 @@ +"use client"; + import { GenericLabel, Icon } from "@/components"; import * as RadixCheckbox from "@radix-ui/react-checkbox"; import { ReactNode, useId } from "react"; -import { styled } from "styled-components"; -import { FormRoot } from "../commonElement"; +import clsx from "clsx"; +import { capitalize } from "../../utils/capitalize"; +import styles from "./Checkbox.module.scss"; export type CheckboxVariants = | "default" @@ -20,11 +23,6 @@ export interface CheckboxProps extends RadixCheckbox.CheckboxProps { dir?: "start" | "end"; } -const Wrapper = styled(FormRoot)` - align-items: center; - max-width: fit-content; -`; - export const Checkbox = ({ id, label, @@ -33,30 +31,68 @@ export const Checkbox = ({ orientation = "horizontal", dir = "end", checked, + className, ...delegated }: CheckboxProps) => { const defaultId = useId(); + + const variantClass = `cuiVariant${capitalize(variant)}`; + const orientationClass = `cuiOrientation${capitalize(orientation)}`; + const dirClass = `cuiDir${capitalize(dir)}`; + const checkedClass = + checked === true + ? "cuiCheckedTrue" + : checked === "indeterminate" + ? "cuiCheckedIndeterminate" + : ""; + const disabledClass = disabled ? "cuiDisabled" : ""; + return ( - - - + - - + + {label && ( )} - + ); }; - -const CheckInput = styled(RadixCheckbox.Root)<{ - variant: CheckboxVariants; -}>` - display: flex; - align-items: center; - justify-content: center; - flex-shrink: 0; - - ${({ theme, variant }) => ` - border-radius: ${theme.click.checkbox.radii.all}; - width: ${theme.click.checkbox.size.all}; - height: ${theme.click.checkbox.size.all}; - background: ${theme.click.checkbox.color.variations[variant].background.default}; - border: 1px solid ${theme.click.checkbox.color.variations[variant].stroke.default}; - cursor: pointer; - - &:hover { - background: ${theme.click.checkbox.color.variations[variant].background.hover}; - } - &[data-state="checked"], - &[data-state="indeterminate"] { - border-color: ${theme.click.checkbox.color.variations[variant].stroke.active}; - background: ${theme.click.checkbox.color.variations[variant].background.active}; - } - &[data-disabled] { - background: ${theme.click.checkbox.color.background.disabled}; - border-color: ${theme.click.checkbox.color.stroke.disabled}; - cursor: not-allowed; - &[data-state="checked"], - &[data-state="indeterminate"] { - background: ${theme.click.checkbox.color.background.disabled}; - border-color: ${theme.click.checkbox.color.stroke.disabled}; - } - } - `}; -`; - -const CheckIconWrapper = styled(RadixCheckbox.Indicator)` - ${({ theme }) => ` - color: ${theme.click.checkbox.color.check.active}; - &[data-disabled] { - color: ${theme.click.checkbox.color.check.disabled}; - } - `} -`; diff --git a/src/components/ClickUIProvider/ClickUIProvider.tsx b/src/components/ClickUIProvider/ClickUIProvider.tsx deleted file mode 100644 index c6345a14a..000000000 --- a/src/components/ClickUIProvider/ClickUIProvider.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { - Provider as TooltipProvider, - TooltipProviderProps, -} from "@radix-ui/react-tooltip"; -import { ToastProvider, ToastProviderProps } from "@/components/Toast/Toast"; -import { ThemeName } from "@/theme"; -import { ThemeProvider } from "@/theme/theme"; -import { ReactNode } from "react"; - -interface Props { - config?: { - tooltip?: Omit; - toast?: Omit; - }; - theme: ThemeName; - children: ReactNode; -} - -const ClickUIProvider = ({ children, theme, config = {} }: Props) => { - const { toast = {}, tooltip = {} } = config; - return ( - - - {children} - - - ); -}; - -export default ClickUIProvider; diff --git a/src/components/CodeBlock/CodeBlock.module.scss b/src/components/CodeBlock/CodeBlock.module.scss new file mode 100644 index 000000000..427223caf --- /dev/null +++ b/src/components/CodeBlock/CodeBlock.module.scss @@ -0,0 +1,70 @@ +@use "cui-mixins" as mixins; +@use "cui-variants" as variants; + +.cuiCodeBlockContainer { + @include mixins.cuiFullWidthStretch; + position: relative; + --cui-codeblock-pre-background: light-dark( + var(--click-codeblock-lightMode-color-background-default), + var(--click-codeblock-darkMode-color-background-default) + ); + --cui-codeblock-pre-text: light-dark( + var(--click-codeblock-lightMode-color-text-default), + var(--click-codeblock-darkMode-color-text-default) + ); + + color: light-dark( + var(--click-codeblock-lightMode-color-numbers-default), + var(--click-codeblock-darkMode-color-numbers-default) + ); + + :global(.linenumber) { + color: light-dark( + var(--click-codeblock-lightMode-color-numbers-default), + var(--click-codeblock-darkMode-color-numbers-default) + ); + } + + @include variants.variant('cuiLightMode') { + color-scheme: light; + } + + @include variants.variant('cuiDarkMode') { + color-scheme: dark; + } +} + +.cuiCodeButton { + @include mixins.cuiEmptyButton; + + @include variants.variant('cuiCopied') { + color: var(--click-alert-color-text-success); + } + + @include variants.variant('cuiError') { + color: var(--click-alert-color-text-danger); + } + + @include variants.variant('cuiNormal') { + color: inherit; + } +} + +.cuiHighlighter { + background: transparent; + padding: 0; + margin: 0; +} + +.cuiCodeContent { + font-family: inherit; + color: inherit; +} + +.cuiButtonContainer { + position: absolute; + display: flex; + gap: 0.625rem; + top: var(--click-codeblock-space-y); + right: var(--click-codeblock-space-x); +} diff --git a/src/components/CodeBlock/CodeBlock.tsx b/src/components/CodeBlock/CodeBlock.tsx index e8c017af0..94992f8b5 100644 --- a/src/components/CodeBlock/CodeBlock.tsx +++ b/src/components/CodeBlock/CodeBlock.tsx @@ -1,13 +1,17 @@ +"use client"; + import { HTMLAttributes, useState } from "react"; import { Light as SyntaxHighlighter, createElement } from "react-syntax-highlighter"; +import clsx from "clsx"; import { IconButton } from "@/components"; -import { styled } from "styled-components"; import useColorStyle from "./useColorStyle"; -import { EmptyButton } from "../commonElement"; +import styles from "./CodeBlock.module.scss"; import sql from "react-syntax-highlighter/dist/cjs/languages/hljs/sql"; import bash from "react-syntax-highlighter/dist/cjs/languages/hljs/bash"; import json from "react-syntax-highlighter/dist/cjs/languages/hljs/json"; import tsx from "react-syntax-highlighter/dist/cjs/languages/hljs/typescript"; +import { useClickUITheme } from "@/theme"; +import { capitalize } from "@/utils/capitalize"; SyntaxHighlighter.registerLanguage("sql", sql); SyntaxHighlighter.registerLanguage("bash", bash); @@ -39,60 +43,6 @@ interface CustomRendererProps { useInlineStyles: boolean; } -const CodeBlockContainer = styled.div<{ $theme?: CodeThemeType }>` - width: 100%; - width: -webkit-fill-available; - width: fill-available; - width: stretch; - position: relative; - ${({ theme, $theme }) => { - const themeName = (theme.name !== "classic" ? theme.name : "light") as CodeThemeType; - - const codeTheme = theme.click.codeblock[`${!$theme ? themeName : $theme}Mode`].color; - return ` - color: ${codeTheme.numbers.default}; - .linenumber { - color: ${codeTheme.numbers.default} - } - `; - }} -`; - -const CodeButton = styled(EmptyButton)<{ $copied: boolean; $error: boolean }>` - ${({ $copied, $error, theme }) => ` - color: ${ - $copied - ? theme.click.alert.color.text.success - : $error - ? theme.click.alert.color.text.danger - : "inherit" - }; - padding: 0; - border: 0; - `} -`; - -const Highlighter = styled(SyntaxHighlighter)` - background: transparent; - padding: 0; - margin: 0; -`; - -const CodeContent = styled.code` - font-family: inherit; - color: inherit; -`; - -const ButtonContainer = styled.div` - position: absolute; - display: flex; - ${({ theme }) => ` - gap: 0.625rem; - top: ${theme.click.codeblock.space.y}; - right: ${theme.click.codeblock.space.x}; - `} -`; - export const CodeBlock = ({ children, language, @@ -102,12 +52,16 @@ export const CodeBlock = ({ wrapLines = false, onCopy, onCopyError, + className, ...props }: Props) => { const [copied, setCopied] = useState(false); const [errorCopy, setErrorCopy] = useState(false); const [wrap, setWrap] = useState(wrapLines); - const customStyle = useColorStyle(theme); + const { resolvedTheme } = useClickUITheme(); + const themeMode = theme ?? resolvedTheme; + + const customStyle = useColorStyle(themeMode); const copyCodeToClipboard = async () => { try { @@ -127,38 +81,50 @@ export const CodeBlock = ({ setTimeout(() => setErrorCopy(false), 2000); } }; + const wrapElement = () => { setWrap(wrap => !wrap); }; - const CodeWithRef = (props: HTMLAttributes) => ; + const CodeWithRef = (props: HTMLAttributes) => ( + + ); + return ( - - +
{showWrapButton && ( - )} - - - + { return rows.map((row, index) => { const children = row.children; @@ -195,7 +161,7 @@ export const CodeBlock = ({ wrapLongLines={wrap || wrapLines} > {children} - - + +
); }; diff --git a/src/components/CodeBlock/InlineCodeBlock.module.scss b/src/components/CodeBlock/InlineCodeBlock.module.scss new file mode 100644 index 000000000..8bd4c9f92 --- /dev/null +++ b/src/components/CodeBlock/InlineCodeBlock.module.scss @@ -0,0 +1,8 @@ +.cuiInlineContainer { + background: var(--click-codeInline-color-background-default); + color: var(--click-codeInline-color-text-default); + border: 1px solid var(--click-codeInline-color-stroke-default); + font: var(--click-codeInline-typography-text-default); + border-radius: var(--click-codeInline-radii-all); + padding: 0 var(--click-codeInline-space-x); +} \ No newline at end of file diff --git a/src/components/CodeBlock/InlineCodeBlock.tsx b/src/components/CodeBlock/InlineCodeBlock.tsx index d794aff3a..5d4c11b8b 100644 --- a/src/components/CodeBlock/InlineCodeBlock.tsx +++ b/src/components/CodeBlock/InlineCodeBlock.tsx @@ -1,16 +1,9 @@ import { HTMLAttributes } from "react"; -import { styled } from "styled-components"; +import styles from "./InlineCodeBlock.module.scss"; -const InlineContainer = styled.span` - ${({ theme }) => ` - background: ${theme.click.codeInline.color.background.default}; - color: ${theme.click.codeInline.color.text.default}; - border: 1px solid ${theme.click.codeInline.color.stroke.default}; - font: ${theme.click.codeInline.typography.text.default}; - border-radius: ${theme.click.codeInline.radii.all}; - padding: 0 ${theme.click.codeInline.space.x}; - `} -`; export const InlineCodeBlock = (props: HTMLAttributes) => ( - + ); diff --git a/src/components/CodeBlock/useColorStyle.ts b/src/components/CodeBlock/useColorStyle.ts index db4b1546b..5d80b84f5 100644 --- a/src/components/CodeBlock/useColorStyle.ts +++ b/src/components/CodeBlock/useColorStyle.ts @@ -1,23 +1,21 @@ -import { useTheme } from "styled-components"; import { CodeThemeType } from "./CodeBlock"; +import { useCUITheme } from "@/theme/ClickUIProvider"; -const useColorStyle = (defaultTheme?: CodeThemeType) => { - const theme = useTheme(); - const inheritedThemeName = ( - theme.name !== "classic" ? theme.name : "light" - ) as CodeThemeType; - const themeName = !defaultTheme ? inheritedThemeName : defaultTheme; - const codeTheme = theme.click.codeblock[`${themeName}Mode`].color; +const useColorStyle = (themeOverride?: CodeThemeType) => { + const { resolvedTheme } = useCUITheme(); + + // Use theme override if provided, otherwise use resolved theme from context + const themeName = themeOverride ?? (resolvedTheme === "dark" ? "dark" : "light"); return { hljs: { display: "block", overflowX: "auto", - padding: `${theme.click.codeblock.space.y} ${theme.click.codeblock.space.x}`, - color: codeTheme.text.default, - background: codeTheme.background.default, - borderRadius: theme.click.codeblock.radii.all, - font: theme.click.codeblock.typography.text.default, + padding: "var(--click-codeblock-space-y) var(--click-codeblock-space-x)", + color: "var(--cui-codeblock-pre-text)", + background: "var(--cui-codeblock-pre-background)", + borderRadius: "var(--click-codeblock-radii-all)", + font: "var(--click-codeblock-typography-text-default)", }, "hljs-comment": { color: themeName === "dark" ? "#999999" : "#656e77", diff --git a/src/components/Collapsible/Collapsible.module.scss b/src/components/Collapsible/Collapsible.module.scss new file mode 100644 index 000000000..87f261bf5 --- /dev/null +++ b/src/components/Collapsible/Collapsible.module.scss @@ -0,0 +1,50 @@ +@use "cui-mixins" as mixins; +@use "cui-variants" as variants; + +.cuiCollapsibleContainer { + @include mixins.cuiFullWidthStretch; + + [data-trigger-icon] { + visibility: hidden; + transition: transform 150ms cubic-bezier(0.87, 0, 0.13, 1); + + &[data-open="true"] { + transform: rotate(90deg); + } + } + + [data-collapsible-header]:hover [data-trigger-icon] { + visibility: visible; + } +} + +.cuiHeaderContainer { + display: flex; + align-items: center; + user-select: none; + + @include variants.variant('cuiIndicatorDirStart') { + margin-left: 0; + } + + @include variants.variant('cuiIndicatorDirEnd') { + margin-left: var(--click-image-sm-size-width); + } +} + +.cuiTriggerButton { + @include mixins.cuiEmptyButton; + display: flex; + align-items: center; + cursor: inherit; +} + +.cuiContentWrapper { + @include variants.variant('cuiIndicatorDirStart') { + margin-left: var(--click-image-sm-size-width); + } + + @include variants.variant('cuiIndicatorDirEnd') { + margin-right: var(--click-image-sm-size-width); + } +} diff --git a/src/components/Collapsible/Collapsible.tsx b/src/components/Collapsible/Collapsible.tsx index edf1c7759..2ef3748ae 100644 --- a/src/components/Collapsible/Collapsible.tsx +++ b/src/components/Collapsible/Collapsible.tsx @@ -1,3 +1,5 @@ +"use client"; + import { createContext, useState, @@ -7,10 +9,11 @@ import { useEffect, forwardRef, } from "react"; -import { styled } from "styled-components"; +import clsx from "clsx"; +import { capitalize } from "@/utils/capitalize"; import { Icon, HorizontalDirection, IconName } from "@/components"; -import { EmptyButton } from "../commonElement"; import { IconWrapper } from "./IconWrapper"; +import styles from "./Collapsible.module.scss"; export interface CollapsibleProps extends HTMLAttributes { open?: boolean; @@ -26,24 +29,12 @@ const NavContext = createContext({ open: false, onOpenChange: () => null, }); -const CollapsibleContainer = styled.div` - width: 100%; - [data-trigger-icon] { - visibility: hidden; - transition: transform 150ms cubic-bezier(0.87, 0, 0.13, 1); - &[data-open="true"] { - transform: rotate(90deg); - } - } - [data-collapsible-header]:hover [data-trigger-icon] { - visibility: visible; - } -`; export const Collapsible = ({ open: openProp, onOpenChange: onOpenChangeProp, children, + className, ...props }: CollapsibleProps) => { const [open, setOpen] = useState(openProp ?? false); @@ -65,18 +56,15 @@ export const Collapsible = ({ onOpenChange, }; return ( - +
{children} - +
); }; -const CollapsipleHeaderContainer = styled.div<{ $indicatorDir: HorizontalDirection }>` - margin-left: ${({ theme, $indicatorDir }) => - $indicatorDir === "start" ? 0 : theme.click.image.sm.size.width}; - user-select: none; -`; - interface CollapsipleHeaderProps extends HTMLAttributes { icon?: IconName; iconDir?: HorizontalDirection; @@ -93,14 +81,16 @@ const CollapsipleHeader = forwardRef( children, wrapInTrigger, onClick: onClickProp, + className, ...props }: CollapsipleHeaderProps, ref ) => { const { onOpenChange } = useContext(NavContext); + const indicatorDirClass = `cuiIndicatorDir${capitalize(indicatorDir)}`; + return ( - { if (wrapInTrigger && typeof onOpenChange === "function") { @@ -111,6 +101,8 @@ const CollapsipleHeader = forwardRef( } }} data-collapsible-header + className={clsx(styles.cuiHeaderContainer, styles[indicatorDirClass], className)} + data-cui-indicator-dir={indicatorDir} {...props} > {indicatorDir === "start" && } @@ -123,7 +115,7 @@ const CollapsipleHeader = forwardRef( )} {indicatorDir === "end" && } - + ); } ); @@ -131,15 +123,6 @@ const CollapsipleHeader = forwardRef( CollapsipleHeader.displayName = "CollapsibleHeader"; Collapsible.Header = CollapsipleHeader; -const CollapsipleTriggerButton = styled(EmptyButton)<{ - $indicatorDir: HorizontalDirection; -}>` - display: flex; - align-items: center; - font: inherit; - color: inherit; - cursor: inherit; -`; interface CollapsipleTriggerProps extends HTMLAttributes { icon?: IconName; iconDir?: HorizontalDirection; @@ -152,6 +135,7 @@ const CollapsipleTrigger = ({ indicatorDir = "start", icon, iconDir = "start", + className, ...props }: CollapsipleTriggerProps) => { const { open, onOpenChange } = useContext(NavContext); @@ -165,10 +149,10 @@ const CollapsipleTrigger = ({ }; return ( - {indicatorDir === "start" && ( @@ -195,33 +179,37 @@ const CollapsipleTrigger = ({ size="sm" /> )} - + ); }; CollapsipleTrigger.displayName = "CollapsibleTrigger"; Collapsible.Trigger = CollapsipleTrigger; -const CollapsibleContentWrapper = styled.div<{ $indicatorDir?: HorizontalDirection }>` - ${({ theme, $indicatorDir }) => - $indicatorDir - ? `${$indicatorDir === "start" ? "margin-left" : "margin-right"}: ${ - theme.click.image.sm.size.width - }` - : ""} -`; - const CollapsipleContent = ({ indicatorDir, + className, ...props }: HTMLAttributes & { indicatorDir?: HorizontalDirection }) => { const { open } = useContext(NavContext); if (!open) { return; } + + const indicatorDirClass = indicatorDir + ? `cuiIndicatorDir${capitalize(indicatorDir)}` + : undefined; + return ( - ); diff --git a/src/components/Collapsible/IconWrapper.module.scss b/src/components/Collapsible/IconWrapper.module.scss new file mode 100644 index 000000000..0c65bafdd --- /dev/null +++ b/src/components/Collapsible/IconWrapper.module.scss @@ -0,0 +1,28 @@ +@use "cui-mixins" as mixins; + +.cuiLabelContainer { + display: flex; + align-items: center; + justify-content: flex-start; + width: 100%; + width: -webkit-fill-available; + width: fill-available; + width: stretch; + flex: 1; + gap: var(--click-sidebar-navigation-item-default-space-gap); + overflow: hidden; +} + +.cuiEllipsisContainer { + display: flex; + white-space: nowrap; + overflow: hidden; + justify-content: flex-start; + gap: inherit; + flex: 1; + + & > *:not(button) { + overflow: hidden; + text-overflow: ellipsis; + } +} diff --git a/src/components/Collapsible/IconWrapper.tsx b/src/components/Collapsible/IconWrapper.tsx index 3af8de50a..f872100c3 100644 --- a/src/components/Collapsible/IconWrapper.tsx +++ b/src/components/Collapsible/IconWrapper.tsx @@ -1,32 +1,6 @@ import { ReactNode } from "react"; -import { styled } from "styled-components"; import { Icon, HorizontalDirection, IconName } from "@/components"; - -const LabelContainer = styled.span` - display: flex; - align-items: center; - justify-content: flex-start; - width: 100%; - width: -webkit-fill-available; - width: fill-available; - width: stretch; - flex: 1; - gap: ${({ theme }) => theme.click.sidebar.navigation.item.default.space.gap}; - overflow: hidden; -`; - -const EllipsisContainer = styled.span` - display: flex; - white-space: nowrap; - overflow: hidden; - justify-content: flex-start; - gap: inherit; - flex: 1; - & > *:not(button) { - overflow: hidden; - text-overflow: ellipsis; - } -`; +import styles from "./IconWrapper.module.scss"; export const IconWrapper = ({ icon, @@ -38,20 +12,20 @@ export const IconWrapper = ({ children: ReactNode; }) => { return ( - + {icon && iconDir === "start" && ( )} - {children} + {children} {icon && iconDir === "end" && ( )} - +
); }; diff --git a/src/components/ConfirmationDialog/ConfirmationDialog.module.scss b/src/components/ConfirmationDialog/ConfirmationDialog.module.scss new file mode 100644 index 000000000..6aa9e7fdb --- /dev/null +++ b/src/components/ConfirmationDialog/ConfirmationDialog.module.scss @@ -0,0 +1,17 @@ +@use "cui-mixins" as mixins; + +.cuiActionsWrapper { + display: flex; + justify-content: flex-end; + gap: var(--click-dialog-space-gap); + + @include mixins.cuiMobile { + flex-direction: column; + } +} + +.cuiDialogContent { + overflow: hidden; + display: flex; + flex-direction: column; +} diff --git a/src/components/ConfirmationDialog/ConfirmationDialog.stories.tsx b/src/components/ConfirmationDialog/ConfirmationDialog.stories.tsx index 080023d72..ffa6304c9 100644 --- a/src/components/ConfirmationDialog/ConfirmationDialog.stories.tsx +++ b/src/components/ConfirmationDialog/ConfirmationDialog.stories.tsx @@ -1,4 +1,4 @@ -import { GridCenter } from "../commonElement"; +import { GridCenter } from "@/components/commonElement"; import { ConfirmationDialog, ConfirmationDialogProps, diff --git a/src/components/ConfirmationDialog/ConfirmationDialog.tsx b/src/components/ConfirmationDialog/ConfirmationDialog.tsx index cb4c3acc5..998a55547 100644 --- a/src/components/ConfirmationDialog/ConfirmationDialog.tsx +++ b/src/components/ConfirmationDialog/ConfirmationDialog.tsx @@ -1,6 +1,9 @@ +"use client"; + import { Container, Dialog, Separator, Text } from "@/components"; import { HTMLAttributes, ReactElement, ReactNode } from "react"; -import { styled } from "styled-components"; +import clsx from "clsx"; +import styles from "./ConfirmationDialog.module.scss"; type DialogPrimaryAction = "primary" | "danger"; @@ -19,21 +22,6 @@ export interface ConfirmationDialogProps extends HTMLAttributes title: string; } -const ActionsWrapper = styled.div` - display: flex; - justify-content: flex-end; - gap: ${props => props.theme.click.dialog.space.gap}; - @media (max-width: ${({ theme }) => theme.breakpoint.sizes.sm}) { - flex-direction: column; - } -`; - -const DialogContent = styled.div` - overflow: hidden; - display: flex; - flex-direction: column; -`; - export const ConfirmationDialog = ({ children, disabled, @@ -60,11 +48,11 @@ export const ConfirmationDialog = ({ !open && onCancel && onCancel(); }} > - {message}} - +
- - +
+ ); }; diff --git a/src/components/Container/Container.module.scss b/src/components/Container/Container.module.scss new file mode 100644 index 000000000..4b1714d3a --- /dev/null +++ b/src/components/Container/Container.module.scss @@ -0,0 +1,228 @@ +@use "cui-mixins" as mixins; +@use "cui-variants" as variants; + +.cuiContainer { + @include mixins.cuiContainerBase; + + flex-wrap: nowrap; + box-sizing: border-box; + + // Orientation with variant mixins + @include variants.variant('cuiOrientationHorizontal') { + @include mixins.cuiContainerOrientation("horizontal"); + } + + @include variants.variant('cuiOrientationVertical') { + @include mixins.cuiContainerOrientation("vertical"); + } + + // Alignment with variant mixins + @include variants.variant('cuiAlignStart') { + align-items: flex-start; + } + + @include variants.variant('cuiAlignCenter') { + align-items: center; + } + + @include variants.variant('cuiAlignEnd') { + align-items: flex-end; + } + + @include variants.variant('cuiAlignStretch') { + align-items: stretch; + } + + // Justify content with variant mixins + @include variants.variant('cuiJustifyStart') { + justify-content: flex-start; + } + + @include variants.variant('cuiJustifyCenter') { + justify-content: center; + } + + @include variants.variant('cuiJustifyEnd') { + justify-content: flex-end; + } + + @include variants.variant('cuiJustifySpaceBetween') { + justify-content: space-between; + } + + @include variants.variant('cuiJustifySpaceAround') { + justify-content: space-around; + } + + @include variants.variant('cuiJustifySpaceEvenly') { + justify-content: space-evenly; + } + + @include variants.variant('cuiJustifyLeft') { + justify-content: left; + } + + @include variants.variant('cuiJustifyRight') { + justify-content: right; + } + +// Width classes +.cuiFillWidth { + width: 100%; +} + +.cuiAutoWidth { + width: auto; +} + +// Height classes +.cuiFillHeight { + height: 100%; +} + + // Flex grow with variant mixins + @include variants.variant('cuiGrow0') { + flex-grow: 0; + } + + @include variants.variant('cuiGrow1') { + flex-grow: 1; + } + + @include variants.variant('cuiGrow2') { + flex-grow: 2; + } + + @include variants.variant('cuiGrow3') { + flex-grow: 3; + } + + @include variants.variant('cuiGrow4') { + flex-grow: 4; + } + + @include variants.variant('cuiGrow5') { + flex-grow: 5; + } + + @include variants.variant('cuiGrow6') { + flex-grow: 6; + } + + // Flex shrink with variant mixins + @include variants.variant('cuiShrink0') { + flex-shrink: 0; + } + + @include variants.variant('cuiShrink1') { + flex-shrink: 1; + } + + @include variants.variant('cuiShrink2') { + flex-shrink: 2; + } + + @include variants.variant('cuiShrink3') { + flex-shrink: 3; + } + + @include variants.variant('cuiShrink4') { + flex-shrink: 4; + } + + @include variants.variant('cuiShrink5') { + flex-shrink: 5; + } + + @include variants.variant('cuiShrink6') { + flex-shrink: 6; + } + + // Wrap with variant mixins + @include variants.variant('cuiWrapNowrap') { + flex-wrap: nowrap; + } + + @include variants.variant('cuiWrapWrap') { + flex-wrap: wrap; + } + + @include variants.variant('cuiWrapWrapReverse') { + flex-wrap: wrap-reverse; + } + + // Gap with variant mixins + @include variants.variant('cuiGapNone') { + @include mixins.cuiContainerGap("none"); + } + + @include variants.variant('cuiGapXxs') { + @include mixins.cuiContainerGap("xxs"); + } + + @include variants.variant('cuiGapXs') { + @include mixins.cuiContainerGap("xs"); + } + + @include variants.variant('cuiGapSm') { + @include mixins.cuiContainerGap("sm"); + } + + @include variants.variant('cuiGapMd') { + @include mixins.cuiContainerGap("md"); + } + + @include variants.variant('cuiGapLg') { + @include mixins.cuiContainerGap("lg"); + } + + @include variants.variant('cuiGapXl') { + @include mixins.cuiContainerGap("xl"); + } + + @include variants.variant('cuiGapXxl') { + @include mixins.cuiContainerGap("xxl"); + } + + // Padding with variant mixins + @include variants.variant('cuiPaddingNone') { + padding: var(--click-container-space-none); + } + + @include variants.variant('cuiPaddingXxs') { + padding: var(--click-container-space-xxs); + } + + @include variants.variant('cuiPaddingXs') { + padding: var(--click-container-space-xs); + } + + @include variants.variant('cuiPaddingSm') { + padding: var(--click-container-space-sm); + } + + @include variants.variant('cuiPaddingMd') { + padding: var(--click-container-space-md); + } + + @include variants.variant('cuiPaddingLg') { + padding: var(--click-container-space-lg); + } + + @include variants.variant('cuiPaddingXl') { + padding: var(--click-container-space-xl); + } + + @include variants.variant('cuiPaddingXxl') { + padding: var(--click-container-space-xxl); + } +} + +// Responsive class +.cuiResponsive { + @include mixins.cuiMobile { + width: 100% !important; + max-width: none !important; + flex-direction: column !important; + } +} diff --git a/src/components/Container/Container.stories.module.scss b/src/components/Container/Container.stories.module.scss new file mode 100644 index 000000000..f3ceba5b5 --- /dev/null +++ b/src/components/Container/Container.stories.module.scss @@ -0,0 +1,6 @@ +.cuiGridCenter { + display: grid; + place-items: center; + width: 100%; + height: 100%; +} \ No newline at end of file diff --git a/src/components/Container/Container.stories.tsx b/src/components/Container/Container.stories.tsx index e1fc2dd71..b7ab3bdd5 100644 --- a/src/components/Container/Container.stories.tsx +++ b/src/components/Container/Container.stories.tsx @@ -1,17 +1,10 @@ import { Container } from "./Container"; -import { Text } from ".."; -import { styled } from "styled-components"; - -const GridCenter = styled.div` - display: grid; - place-items: center; - width: 100%; - height: 100%; -`; +import { Text } from "@/components"; +import styles from "./Container.stories.module.scss"; const ContainerExample = ({ ...props }) => { return ( - +
{ Child - +
); }; diff --git a/src/components/Container/Container.tsx b/src/components/Container/Container.tsx index bbd84095f..d80a28d5b 100644 --- a/src/components/Container/Container.tsx +++ b/src/components/Container/Container.tsx @@ -1,12 +1,14 @@ -import { styled } from "styled-components"; +import { ElementType, forwardRef } from "react"; +import clsx from "clsx"; +import { capitalize } from "@/utils/capitalize"; +import { Orientation } from "@/components/types"; import { - ComponentProps, - ComponentPropsWithRef, - ElementType, - ReactNode, - forwardRef, -} from "react"; -import { Orientation } from "@/components"; + PolymorphicComponent, + PolymorphicComponentProps, + PolymorphicProps, + PolymorphicRef, +} from "@/utils/polymorphic"; +import styles from "./Container.module.scss"; type AlignItemsOptions = "start" | "center" | "end" | "stretch"; export type GapOptions = "none" | "xxs" | "xs" | "sm" | "md" | "lg" | "xl" | "xxl"; @@ -23,8 +25,8 @@ type JustifyContentOptions = export type PaddingOptions = "none" | "xxs" | "xs" | "sm" | "md" | "lg" | "xl" | "xxl"; type WrapOptions = "nowrap" | "wrap" | "wrap-reverse"; -export interface ContainerProps { - component?: T; +export interface ContainerProps + extends PolymorphicComponentProps { alignItems?: AlignItemsOptions; children?: React.ReactNode; fillWidth?: boolean; @@ -44,10 +46,6 @@ export interface ContainerProps { overflow?: string; } -type ContainerPolymorphicComponent = ( - props: Omit, keyof T> & ContainerProps -) => ReactNode; - const _Container = ( { component, @@ -68,88 +66,72 @@ const _Container = ( maxHeight, minHeight, overflow, + className, + style, ...props - }: Omit, keyof T> & ContainerProps, - ref: ComponentPropsWithRef["ref"] + }: PolymorphicProps>, + ref: PolymorphicRef ) => { + const Component = component ?? "div"; + const defaultAlignItems = + alignItems ?? (orientation === "vertical" ? "start" : "center"); + + const orientationClass = `cuiOrientation${capitalize(orientation)}`; + const alignClass = `cuiAlign${capitalize(defaultAlignItems)}`; + const justifyClass = `cuiJustify${capitalize(justifyContent)}`; + const wrapClass = `cuiWrap${capitalize(wrap)}`; + const gapClass = `cuiGap${capitalize(gap)}`; + const paddingClass = `cuiPadding${capitalize(padding)}`; + const growClass = grow ? `cuiGrow${grow}` : undefined; + const shrinkClass = shrink ? `cuiShrink${shrink}` : undefined; + + const containerClasses = clsx( + styles.cuiContainer, + styles[orientationClass], + styles[alignClass], + styles[justifyClass], + styles[wrapClass], + styles[gapClass], + styles[paddingClass], + { + [styles.cuiFillWidth]: fillWidth, + [styles.cuiAutoWidth]: !fillWidth, + [styles.cuiFillHeight]: fillHeight, + [styles.cuiResponsive]: isResponsive, + [styles[growClass!]]: growClass, + [styles[shrinkClass!]]: shrinkClass, + }, + className + ); + + const inlineStyles = { + maxWidth: maxWidth ?? undefined, + minWidth: minWidth ?? undefined, + maxHeight: maxHeight ?? undefined, + minHeight: minHeight ?? undefined, + overflow: overflow ?? undefined, + ...style, + }; + return ( - {children} - + ); }; -const Wrapper = styled.div<{ - $alignItems: AlignItemsOptions; - $fillWidth?: boolean; - $gapSize: GapOptions; - $grow?: GrowShrinkOptions; - $shrink?: GrowShrinkOptions; - $isResponsive?: boolean; - $justifyContent: JustifyContentOptions; - $maxWidth?: string; - $minWidth?: string; - $orientation: Orientation; - $paddingSize: PaddingOptions; - $wrap: WrapOptions; - $fillHeight?: boolean; - $minHeight?: string; - $maxHeight?: string; - $overflow?: string; -}>` - display: flex; - ${({ $grow, $shrink }) => ` - ${$grow && `flex: ${$grow}`}; - ${$shrink && `flex-shrink: ${$shrink}`}; - `} - ${({ $fillHeight, $maxHeight, $minHeight }) => ` - ${$fillHeight && "height: 100%"}; - ${$maxHeight && `max-height: ${$maxHeight}`}; - ${$minHeight && `min-height: ${$minHeight}`}; - `} - ${({ $overflow }) => ` - ${$overflow && `overflow: ${$overflow}`}; - `} - flex-wrap: ${({ $wrap = "nowrap" }) => $wrap}; - gap: ${({ theme, $gapSize }) => theme.click.container.gap[$gapSize]}; - max-width: ${({ $maxWidth }) => $maxWidth ?? "none"}; - min-width: ${({ $minWidth }) => $minWidth ?? "auto"}; - padding: ${({ theme, $paddingSize }) => theme.click.container.space[$paddingSize]}; - width: ${({ $fillWidth = true }) => ($fillWidth === true ? "100%" : "auto")}; - flex-direction: ${({ $orientation = "horizontal" }) => - $orientation === "horizontal" ? "row" : "column"}; - align-items: ${({ $alignItems = "center" }) => $alignItems}; - justify-content: ${({ $justifyContent = "left" }) => - $justifyContent === "start" ? "start" : `${$justifyContent}`}; - - @media (max-width: ${({ theme }) => theme.breakpoint.sizes.md}) { - width: ${({ $isResponsive = true, $fillWidth = true }) => - $isResponsive === true ? "100%" : $fillWidth === true ? "100%" : "auto"}; - max-width: ${({ $isResponsive = true }) => - $isResponsive === true ? "none" : "auto"}; - flex-direction: ${({ $isResponsive = true }) => - $isResponsive === true ? "column" : "auto"}; - } -`; -export const Container: ContainerPolymorphicComponent = forwardRef(_Container); +export const Container: PolymorphicComponent = forwardRef(_Container); diff --git a/src/components/ContextMenu/ContextMenu.module.scss b/src/components/ContextMenu/ContextMenu.module.scss new file mode 100644 index 000000000..06fef39e7 --- /dev/null +++ b/src/components/ContextMenu/ContextMenu.module.scss @@ -0,0 +1,63 @@ +@use "cui-mixins" as mixins; +@use "cui-variants" as variants; + +.cuiRightMenuContent { + flex-direction: column; + z-index: 1; + + @include variants.variant('cuiShowArrow') { + &[data-side="bottom"] { + margin-top: -1px; + } + + &[data-side="top"] { + margin-bottom: -1px; + } + + &[data-side="left"] { + margin-right: -1px; + + .cuiPopoverArrow { + margin-right: 1rem; + } + } + + &[data-side="right"] { + margin-left: -1px; + + .cuiPopoverArrow { + margin-left: 1rem; + } + } + } +} + +.cuiRightMenuGroup { + width: 100%; + border-bottom: 1px solid var(--click-genericMenu-item-color-stroke-default); +} + +.cuiRightMenuSub { + border-bottom: 1px solid var(--click-genericMenu-item-color-stroke-default); +} + +.cuiGenericMenuItem { + @include mixins.cuiGenericMenuItem; +} + +.cuiGenericMenuPanel { + outline: none; + overflow: hidden; + display: flex; + align-items: flex-start; + pointer-events: auto; + border: 1px solid var(--click-genericMenu-panel-color-stroke-default); + background: var(--click-genericMenu-panel-color-background-default); + box-shadow: var(--click-genericMenu-panel-shadow-default); + border-radius: var(--click-genericMenu-panel-radii-all); +} + +.cuiContextMenu { + max-width: var(--radix-context-menu-content-available-width); + max-height: var(--radix-context-menu-content-available-height); +} diff --git a/src/components/ContextMenu/ContextMenu.stories.module.scss b/src/components/ContextMenu/ContextMenu.stories.module.scss new file mode 100644 index 000000000..838c3d3d9 --- /dev/null +++ b/src/components/ContextMenu/ContextMenu.stories.module.scss @@ -0,0 +1,11 @@ +.cuiGridCenter { + display: grid; + place-items: center; + width: 100%; + height: 100%; +} + +.cuiTrigger { + @extend .cuiGridCenter; + border: 2px currentColor dashed; +} \ No newline at end of file diff --git a/src/components/ContextMenu/ContextMenu.stories.tsx b/src/components/ContextMenu/ContextMenu.stories.tsx index 9ac3a01c1..aa965fbe7 100644 --- a/src/components/ContextMenu/ContextMenu.stories.tsx +++ b/src/components/ContextMenu/ContextMenu.stories.tsx @@ -1,28 +1,19 @@ import { ContextMenuProps } from "@radix-ui/react-context-menu"; import { ContextMenu } from "./ContextMenu"; -import { styled } from "styled-components"; +import styles from "./ContextMenu.stories.module.scss"; interface Props extends ContextMenuProps { disabled?: boolean; showArrow?: boolean; side: "top" | "right" | "left" | "bottom"; } -const GridCenter = styled.div` - display: grid; - place-items: center; - width: 100%; - height: 100%; -`; -const Trigger = styled(GridCenter)` - border: 2px currentColor dashed; -`; const ContextMenuExample = ({ showArrow, disabled, side, ...props }: Props) => { return ( - +
- ContextMenu Trigger +
ContextMenu Trigger
{ Content3
- +
); }; export default { diff --git a/src/components/ContextMenu/ContextMenu.tsx b/src/components/ContextMenu/ContextMenu.tsx index 8e8ab9294..b6d7a9313 100644 --- a/src/components/ContextMenu/ContextMenu.tsx +++ b/src/components/ContextMenu/ContextMenu.tsx @@ -1,10 +1,11 @@ +"use client"; + import * as RightMenu from "@radix-ui/react-context-menu"; -import { styled } from "styled-components"; import { HorizontalDirection, Icon, IconName } from "@/components"; -import { Arrow, GenericMenuItem, GenericMenuPanel } from "../GenericMenu"; -import PopoverArrow from "../icons/PopoverArrow"; -import IconWrapper from "../IconWrapper/IconWrapper"; +import { IconWrapper } from "@/components"; import { forwardRef } from "react"; +import clsx from "clsx"; +import styles from "./ContextMenu.module.scss"; export const ContextMenu = (props: RightMenu.ContextMenuProps) => ( @@ -40,8 +41,8 @@ const ContextMenuSubTrigger = ({ ...props }: ContextMenuSubTriggerProps) => { return ( - - + ); }; @@ -72,35 +73,6 @@ type ContextMenuSubContentProps = RightMenu.MenuSubContentProps & { sub?: never; } & ArrowProps; -const RightMenuContent = styled(GenericMenuPanel)<{ $showArrow?: boolean }>` - flex-direction: column; - z-index: 1; - ${({ $showArrow }) => - $showArrow - ? ` - &[data-side="bottom"] { - margin-top: -1px; - } - &[data-side="top"] { - margin-bottom: -1px; - } - &[data-side="left"] { - margin-right: -1px; - .popover-arrow { - margin-right: 1rem; - } - } - } - &[data-side="right"] { - margin-left: -1px; - .popover-arrow { - margin-left: 1rem; - } - } - ` - : ""}; -`; - const ContextMenuContent = ({ sub, children, @@ -110,24 +82,26 @@ const ContextMenuContent = ({ const ContentElement = sub ? RightMenu.SubContent : RightMenu.Content; return ( - {showArrow && ( - - - + /> )} {children} - + ); }; @@ -135,26 +109,25 @@ const ContextMenuContent = ({ ContextMenuContent.displayName = "ContextMenuContent"; ContextMenu.Content = ContextMenuContent; -const RightMenuGroup = styled(RightMenu.Group)` - width: 100%; - border-bottom: 1px solid - ${({ theme }) => theme.click.genericMenu.item.color.stroke.default}; -`; - const ContextMenuGroup = (props: RightMenu.ContextMenuGroupProps) => { - return ; + return ( + + ); }; ContextMenuGroup.displayName = "ContextMenuGroup"; ContextMenu.Group = ContextMenuGroup; -const RightMenuSub = styled(RightMenu.Sub)` - border-bottom: 1px solid - ${({ theme }) => theme.click.genericMenu.item.color.stroke.default}; -`; - const ContextMenuSub = ({ ...props }: RightMenu.ContextMenuGroupProps) => { - return ; + return ( + + ); }; ContextMenuSub.displayName = "ContextMenuSub"; @@ -166,8 +139,8 @@ export interface ContextMenuItemProps extends RightMenu.ContextMenuItemProps { const ContextMenuItem = ({ icon, iconDir, children, ...props }: ContextMenuItemProps) => { return ( - {children} - + ); }; diff --git a/src/components/DateDetails/DateDetails.module.scss b/src/components/DateDetails/DateDetails.module.scss new file mode 100644 index 000000000..b3493c145 --- /dev/null +++ b/src/components/DateDetails/DateDetails.module.scss @@ -0,0 +1,26 @@ +@use "cui-mixins" as mixins; + +.cuiUnderlinedTrigger { + // Match linkStyles from main branch + // Using sm size values since main hardcodes $size="sm" + color: var(--click-global-color-text-link-default); + margin: 0; + text-decoration: none; + display: inline-flex; + gap: var(--click-link-space-sm-gap); + margin-right: var(--click-link-space-sm-gap); + align-items: center; + cursor: pointer; + + &:hover, + &:focus { + color: var(--click-global-color-text-link-hover); + transition: var(--transition-default); + text-decoration: underline; + outline: none; + } + + &:visited { + color: var(--click-global-color-text-link-default); + } +} diff --git a/src/components/DateDetails/DateDetails.tsx b/src/components/DateDetails/DateDetails.tsx index ef8d85197..a78ce200e 100644 --- a/src/components/DateDetails/DateDetails.tsx +++ b/src/components/DateDetails/DateDetails.tsx @@ -1,19 +1,20 @@ +"use client"; + import dayjs, { Dayjs } from "dayjs"; -import advancedFormat from "dayjs/plugin/advancedFormat"; -import duration from "dayjs/plugin/duration"; -import localizedFormat from "dayjs/plugin/localizedFormat"; -import relativeTime from "dayjs/plugin/relativeTime"; -import timezone from "dayjs/plugin/timezone"; -import updateLocale from "dayjs/plugin/updateLocale"; -import utc from "dayjs/plugin/utc"; -import { styled } from "styled-components"; +import advancedFormat from "dayjs/plugin/advancedFormat.js"; +import duration from "dayjs/plugin/duration.js"; +import localizedFormat from "dayjs/plugin/localizedFormat.js"; +import relativeTime from "dayjs/plugin/relativeTime.js"; +import timezone from "dayjs/plugin/timezone.js"; +import updateLocale from "dayjs/plugin/updateLocale.js"; +import utc from "dayjs/plugin/utc.js"; import { Popover } from "@/components/Popover/Popover"; import { Text } from "@/components/Typography/Text/Text"; -import { linkStyles, StyledLinkProps } from "@/components/Link/common"; import { GridContainer } from "@/components/GridContainer/GridContainer"; import { Container } from "@/components/Container/Container"; -import { TextSize, TextWeight } from "../commonTypes"; +import { TextSize, TextWeight } from "@/components/commonTypes"; +import styles from "./DateDetails.module.scss"; dayjs.extend(advancedFormat); dayjs.extend(duration); @@ -60,10 +61,6 @@ dayjs.updateLocale("en", { }, }); -const UnderlinedTrigger = styled(Popover.Trigger)` - ${linkStyles} -`; - const formatDateDetails = (date: Dayjs, timezone?: string): string => { const isCurrentYear = dayjs().year() === date.year(); const formatForCurrentYear = "MMM D, h:mm a"; @@ -132,17 +129,14 @@ export const DateDetails = ({ return ( - + {dayjs.utc(date).fromNow()} - + ` - ${({ $isActive, theme }) => { - return `border: ${theme.click.datePicker.dateOption.stroke} solid ${ - $isActive - ? theme.click.datePicker.dateOption.color.stroke.active - : theme.click.field.color.stroke.default - };`; - }} - - width: ${explicitWidth}; -}`; +import styles from "./Common.module.scss"; interface DatePickerInputProps { isActive: boolean; @@ -42,8 +33,11 @@ export const DatePickerInput = ({ selectedDate instanceof Date ? selectedDateFormatter.format(selectedDate) : ""; return ( - @@ -51,13 +45,13 @@ export const DatePickerInput = ({ - + ); }; @@ -112,114 +106,66 @@ export const DateRangePickerInput = ({ } return ( - - {formattedValue} - - + + ); }; -const DatePickerContainer = styled(Container)` - background: ${({ theme }) => - theme.click.datePicker.dateOption.color.background.default}; -`; - -const UnselectableTitle = styled.h2` - ${({ theme }) => ` - color: ${theme.click.datePicker.color.title.default}; - font: ${theme.click.datePicker.typography.title.default}; - `} - - user-select: none; -`; - -const DateTable = styled.table` - border-collapse: separate; - border-spacing: 0; - font: ${({ theme }) => theme.typography.styles.product.text.normal.md}; - table-layout: fixed; - user-select: none; - width: ${explicitWidth}; - - thead tr { - height: ${({ theme }) => theme.click.datePicker.dateOption.size.height}; - } - - tbody { - cursor: pointer; - } - - td, - th { - padding: 4px; - } -`; - -const DateTableHeader = styled.th` - ${({ theme }) => ` - color: ${theme.click.datePicker.color.daytitle.default}; - font: ${theme.click.datePicker.typography.daytitle.default}; - `} - - width: 14%; -`; - -export const DateTableCell = styled.td<{ +interface DateTableCellProps { $isCurrentMonth?: boolean; $isDisabled?: boolean; $isSelected?: boolean; $isToday?: boolean; -}>` - ${({ theme }) => ` - border: ${theme.click.datePicker.dateOption.stroke} solid ${theme.click.datePicker.dateOption.color.stroke.default}; - border-radius: ${theme.click.datePicker.dateOption.radii.default}; - font: ${theme.click.datePicker.dateOption.typography.label.default}; - `} - - ${({ $isCurrentMonth, $isDisabled, theme }) => - (!$isCurrentMonth || $isDisabled) && - ` - color: ${theme.click.datePicker.dateOption.color.label.disabled}; - font: ${theme.click.datePicker.dateOption.typography.label.disabled}; - `} - - ${({ $isSelected, theme }) => - $isSelected && - ` - background: ${theme.click.datePicker.dateOption.color.background.active}; - color: ${theme.click.datePicker.dateOption.color.label.active}; - `} - - - text-align: center; - - ${({ $isToday, theme }) => - $isToday && `font: ${theme.click.datePicker.dateOption.typography.label.active};`} - - &:hover { - ${({ $isDisabled, theme }) => - `border: ${theme.click.datePicker.dateOption.stroke} solid ${ - $isDisabled - ? theme.click.datePicker.dateOption.color.stroke.disabled - : theme.click.datePicker.dateOption.color.stroke.hover - }; - + children?: React.ReactNode; + onClick?: () => void; + onMouseEnter?: () => void; + onMouseLeave?: () => void; +} - border-radius: ${theme.click.datePicker.dateOption.radii.default};`}; - } -`; +export const DateTableCell = ({ + $isCurrentMonth, + $isDisabled, + $isSelected, + $isToday, + children, + onClick, + onMouseEnter, + onMouseLeave, + ...props +}: DateTableCellProps) => { + return ( + + {children} + + ); +}; export type Body = ReturnType["body"]; @@ -251,7 +197,8 @@ export const CalendarRenderer = ({ headerDate.setFullYear(year); return ( - - {headerDateFormatter.format(headerDate)} +

+ {headerDateFormatter.format(headerDate)} +

- + - {headers.weekDays.map(({ key, value: date }) => { + {headers?.weekDays?.map(({ key, value: date }) => { return ( - + ); })} {children(body)} - - +
{weekdayFormatter.format(date)} - +
+ ); }; diff --git a/src/components/DatePicker/DatePicker.tsx b/src/components/DatePicker/DatePicker.tsx index 24d061c8c..3b01d919d 100644 --- a/src/components/DatePicker/DatePicker.tsx +++ b/src/components/DatePicker/DatePicker.tsx @@ -1,6 +1,8 @@ +"use client"; + import { useEffect, useState } from "react"; import { isSameDate, UseCalendarOptions } from "@h6s/calendar"; -import { Dropdown } from "../Dropdown/Dropdown"; +import { Dropdown } from "@/components"; import { Body, CalendarRenderer, DatePickerInput, DateTableCell } from "./Common"; interface CalendarProps { diff --git a/src/components/DatePicker/DateRangePicker.module.scss b/src/components/DatePicker/DateRangePicker.module.scss new file mode 100644 index 000000000..b7a843629 --- /dev/null +++ b/src/components/DatePicker/DateRangePicker.module.scss @@ -0,0 +1,47 @@ +@use "cui-mixins" as mixins; +@use "cui-variants" as variants; + +.cuiPredefinedCalendarContainer { + align-items: start; + background: var(--click-panel-color-background-muted); +} + +.cuiPredefinedDatesContainer { + width: 275px; +} + +// left value of 276px is the width of the PredefinedDatesContainer + 1 pixel for border +.cuiCalendarRendererContainer { + border: var(--click-datePicker-dateOption-stroke) solid var(--click-datePicker-dateOption-color-background-range); + border-radius: var(--click-datePicker-dateOption-radii-default); + box-shadow: + lch(6.77 0 0 / 0.15) 4px 4px 6px -1px, + lch(6.77 0 0 / 0.15) 2px 2px 4px -1px; + left: 276px; + position: absolute; + top: 0; +} + +// Height of 221px is height the height the calendar needs to match the PredefinedDatesContainer +.cuiStyledCalendarRenderer { + border-radius: var(--click-datePicker-dateOption-radii-default); + min-height: 221px; +} + +.cuiStyledDropdownItem { + min-height: 24px; +} + +// max-height of 210px allows the scrollable container to be a reasonble height that matches the calendar +.cuiScrollableContainer { + max-height: 210px; + overflow-y: auto; +} + +.cuiDateRangeTableCell { + @include variants.variant('cuiShowRangeIndicator') { + background: var(--click-datePicker-dateOption-color-background-range); + border: var(--click-datePicker-dateOption-stroke) solid var(--click-datePicker-dateOption-color-background-range); + border-radius: 0; + } +} diff --git a/src/components/DatePicker/DateRangePicker.tsx b/src/components/DatePicker/DateRangePicker.tsx index f42e5cff0..ea3f8e17b 100644 --- a/src/components/DatePicker/DateRangePicker.tsx +++ b/src/components/DatePicker/DateRangePicker.tsx @@ -7,68 +7,18 @@ import { useState, } from "react"; import { isSameDate, UseCalendarOptions } from "@h6s/calendar"; -import { styled } from "styled-components"; -import { Dropdown } from "../Dropdown/Dropdown"; +import { Dropdown } from "@/components"; import { Body, CalendarRenderer, DateRangePickerInput, DateTableCell } from "./Common"; -import { Container } from "../Container/Container"; -import { Panel } from "../Panel/Panel"; -import { Icon } from "../Icon/Icon"; +import { Container } from "@/components"; +import { Panel } from "@/components"; +import { Icon } from "@/components"; import { DateRange, datesAreWithinMaxRange, isDateRangeTheWholeMonth, selectedDateFormatter, } from "./utils"; - -const PredefinedCalendarContainer = styled(Panel)` - align-items: start; - background: ${({ theme }) => theme.click.panel.color.background.muted}; -`; - -const PredefinedDatesContainer = styled(Container)` - width: 275px; -`; - -// left value of 276px is the width of the PredefinedDatesContainer + 1 pixel for border -const CalendarRendererContainer = styled.div` - border: ${({ theme }) => - `${theme.click.datePicker.dateOption.stroke} solid ${theme.click.datePicker.dateOption.color.background.range}`}; - border-radius: ${({ theme }) => theme.click.datePicker.dateOption.radii.default}; - box-shadow: - lch(6.77 0 0 / 0.15) 4px 4px 6px -1px, - lch(6.77 0 0 / 0.15) 2px 2px 4px -1px; - left: 276px; - position: absolute; - top: 0; -`; - -// Height of 221px is height the height the calendar needs to match the PredefinedDatesContainer -const StyledCalendarRenderer = styled(CalendarRenderer)` - border-radius: ${({ theme }) => theme.click.datePicker.dateOption.radii.default}; - min-height: 221px; -`; - -const StyledDropdownItem = styled(Dropdown.Item)` - min-height: 24px; -`; - -// max-height of 210px allows the scrollable container to be a reasonble height that matches the calendar -const ScrollableContainer = styled(Container)` - max-height: 210px; - overflow-y: auto; -`; - -const DateRangeTableCell = styled(DateTableCell)<{ - $shouldShowRangeIndicator?: boolean; -}>` - ${({ $shouldShowRangeIndicator, theme }) => - $shouldShowRangeIndicator && - ` - background: ${theme.click.datePicker.dateOption.color.background.range}; - border: ${theme.click.datePicker.dateOption.stroke} solid ${theme.click.datePicker.dateOption.color.background.range}; - border-radius: 0; - `} -`; +import styles from "./DateRangePicker.module.scss"; interface CalendarProps { calendarBody: Body; @@ -91,12 +41,6 @@ const Calendar = ({ startDate, endDate, }: CalendarProps) => { - const [hoveredDate, setHoveredDate] = useState(); - - const handleMouseOut = (): void => { - setHoveredDate(undefined); - }; - return calendarBody.value.map(({ key: weekKey, value: week }) => { return ( @@ -108,10 +52,6 @@ const Calendar = ({ const today = new Date(); const isCurrentDate = isSameDate(today, fullDate); - const isBetweenStartAndEndDates = Boolean( - startDate && endDate && fullDate > startDate && fullDate < endDate - ); - let isDisabled = false; if (futureDatesDisabled && fullDate > today) { isDisabled = true; @@ -129,15 +69,8 @@ const Calendar = ({ isDisabled = true; } - const shouldShowRangeIndicator = - !endDate && - Boolean( - startDate && hoveredDate && fullDate > startDate && fullDate < hoveredDate - ); - - const handleMouseEnter = () => { - setHoveredDate(fullDate); - }; + const handleMouseEnter = () => {}; + const handleMouseLeave = () => {}; const handleClick = () => { if (isDisabled) { @@ -145,8 +78,6 @@ const Calendar = ({ } setSelectedDate(fullDate); - // User has a date range selected and clicked a new date. - // This will cause the selected date to be reset, thus do not close the datepicker. if (startDate && endDate) { return; } @@ -163,10 +94,7 @@ const Calendar = ({ } }; return ( - {date} - + ); })} @@ -218,12 +146,16 @@ const PredefinedDates = ({ }; return ( - - + {predefinedDatesList.map(({ startDate, endDate }) => { const handleItemClick = () => { setStartDate(startDate); @@ -246,7 +178,8 @@ const PredefinedDates = ({ )} - ${selectedDateFormatter.format(endDate)}`.trim(); return ( - } - + ); })} - - + + Custom time period - - + + ); }; @@ -338,8 +274,6 @@ export const DateRangePicker = ({ const handleSelectDate = useCallback( (selectedDate: Date): void => { - // Start date and end date are selected, user clicks any date. - // Set start date to the selected date, clear the end date. if (selectedStartDate && selectedEndDate) { // If futureStartDatesDisabled is true, only set the selected date to the date clicked if it's before today if (futureStartDatesDisabled && selectedDate > new Date()) { @@ -353,14 +287,10 @@ export const DateRangePicker = ({ if (selectedStartDate) { if (isSameDate(selectedStartDate, selectedDate)) { - // Start date is selected, user clicks start date. - // Reset the start date. setSelectedStartDate(undefined); return; } - // Start date is selected, user clicks an earlier date. - // Set the earlier date to the new start date. if (selectedDate < selectedStartDate) { setSelectedStartDate(selectedDate); return; @@ -398,7 +328,8 @@ export const DateRangePicker = ({ {shouldShowPredefinedDates ? ( - {shouldShowCustomRange && ( - - +
+ {(body: Body) => ( )} - - + +
)} -
+ ) : ( {(body: Body) => ( diff --git a/src/components/Dialog/Dialog.module.scss b/src/components/Dialog/Dialog.module.scss new file mode 100644 index 000000000..a9e79856b --- /dev/null +++ b/src/components/Dialog/Dialog.module.scss @@ -0,0 +1,89 @@ +@use "cui-mixins" as mixins; +@use "cui-variants" as variants; + +@keyframes overlayShow { + 0% { + opacity: 0; + } + 100% { + opacity: 1; + } +} + +@keyframes contentShow { + 0% { + opacity: 0; + transform: translate(-50%, -48%) scale(0.96); + } + 100% { + opacity: 1; + transform: translate(-50%, -50%) scale(1); + } +} + +.cuiTrigger { + width: fit-content; + background: transparent; + border: none; + cursor: pointer; +} + +.cuiDialogOverlay { + background-color: var(--click-dialog-color-opaqueBackground-default); + position: fixed; + inset: 0; + animation: overlayShow 150ms cubic-bezier(0.16, 1, 0.3, 1); +} + +.cuiContentArea { + background: var(--click-dialog-color-background-default); + border-radius: var(--click-dialog-radii-all); + box-shadow: var(--click-dialog-shadow-default); + border: 1px solid var(--global-color-stroke-default); + width: 75%; + max-width: 670px; + position: fixed; + top: 50%; + left: 50%; + max-height: 75%; + overflow: auto; + transform: translate(-50%, -50%); + animation: contentShow 150ms cubic-bezier(0.16, 1, 0.3, 1); + outline: none; + + @include variants.variant('cuiRegularPadding') { + padding-block: var(--click-dialog-space-y); + padding-inline: var(--click-dialog-space-x); + } + + @include variants.variant('cuiReducedPadding') { + padding-block: var(--sizes-4); + padding-inline: var(--sizes-4); + } + + @include mixins.cuiMobile { + max-height: 100%; + border-radius: 0; + width: 100%; + } +} + +.cuiTitleArea { + display: flex; + align-items: center; + min-height: var(--sizes-9); /* 32px */ + + @include variants.variant('cuiSpaceBetween') { + justify-content: space-between; + } + + @include variants.variant('cuiFlexEnd') { + justify-content: flex-end; + } +} + +.cuiTitle { + font: var(--click-dialog-typography-title-default); + padding: 0; + margin: 0; +} diff --git a/src/components/Dialog/Dialog.stories.module.scss b/src/components/Dialog/Dialog.stories.module.scss new file mode 100644 index 000000000..e155b5af5 --- /dev/null +++ b/src/components/Dialog/Dialog.stories.module.scss @@ -0,0 +1,17 @@ +.cuiActionArea { + display: flex; + justify-content: flex-end; + gap: var(--click-dialog-space-gap); +} + +.cuiTopNav { + position: absolute; + top: 0; + left: 0; + width: 100%; + padding-bottom: 12px; + display: flex; + align-items: center; + justify-content: flex-end; + border-bottom: 1px solid var(--click-separator-color-stroke-default); +} \ No newline at end of file diff --git a/src/components/Dialog/Dialog.stories.tsx b/src/components/Dialog/Dialog.stories.tsx index 074ef0c95..5f42f6c33 100644 --- a/src/components/Dialog/Dialog.stories.tsx +++ b/src/components/Dialog/Dialog.stories.tsx @@ -1,12 +1,12 @@ import { useState } from "react"; -import { GridCenter } from "../commonElement"; -import { Text } from "../Typography/Text/Text"; +import { GridCenter } from "@/components/commonElement"; +import styles from "./Dialog.stories.module.scss"; +import { Text } from "@/components/Typography/Text/Text"; import { Dialog } from "./Dialog"; -import Separator from "../Separator/Separator"; -import { Spacer } from "../Spacer/Spacer"; -import { Button } from "../Button/Button"; -import { styled } from "styled-components"; -import { Link } from "../Link/Link"; +import Separator from "@/components/Separator/Separator"; +import { Spacer } from "@/components/Spacer/Spacer"; +import { Button } from "@/components/Button/Button"; +import { Link } from "@/components/Link/Link"; import { Container } from "@/components/Container/Container"; import { TextField } from "@/components/Input/TextField"; import { Icon } from "@/components/Icon/Icon"; @@ -55,11 +55,9 @@ const DialogComponent = ({ ); -const ActionArea = styled.div` - display: flex; - justify-content: flex-end; - gap: ${({ theme }) => theme.click.dialog.space.gap}; -`; +const ActionArea = ({ children }: { children: React.ReactNode }) => ( +
{children}
+); export default { component: DialogComponent, @@ -93,17 +91,9 @@ export const ModalDialog = { }, }; -const TopNav = styled.div` - position: absolute; - top: 0; - left: 0; - width: 100%; - padding-bottom: 12px; - display: flex; - align-items: center; - justify-content: flex-end; - border-bottom: 1px solid ${({ theme }) => theme.click.separator.color.stroke.default}; -`; +const TopNav = ({ children }: { children: React.ReactNode }) => ( +
{children}
+); export const ChatDialog = { args: { diff --git a/src/components/Dialog/Dialog.tsx b/src/components/Dialog/Dialog.tsx index eb820da8f..0f6c3ba0b 100644 --- a/src/components/Dialog/Dialog.tsx +++ b/src/components/Dialog/Dialog.tsx @@ -1,25 +1,21 @@ +"use client"; + import { ReactNode } from "react"; import * as RadixDialog from "@radix-ui/react-dialog"; -import { keyframes, styled } from "styled-components"; +import clsx from "clsx"; import { Button, Icon, Spacer } from "@/components"; -import { CrossButton } from "../commonElement"; +import { CrossButton } from "@/components/commonElement"; import { ButtonProps } from "@/components/Button/Button"; +import styles from "./Dialog.module.scss"; export const Dialog = ({ children, ...props }: RadixDialog.DialogProps) => { return {children}; }; -// Dialog Trigger -const Trigger = styled(RadixDialog.Trigger)` - width: fit-content; - background: transparent; - border: none; - cursor: pointer; -`; - const DialogTrigger = ({ children, asChild, + className, ...props }: RadixDialog.DialogTriggerProps) => { if (asChild) { @@ -34,7 +30,14 @@ const DialogTrigger = ({ ); } // Use styled Trigger if not asChild - return {children}; + return ( + + {children} + + ); }; DialogTrigger.displayName = "DialogTrigger"; @@ -54,62 +57,6 @@ DialogClose.displayName = "DialogClose"; Dialog.Close = DialogClose; // Dialog Content -const overlayShow = keyframes({ - "0%": { opacity: 0 }, - "100%": { opacity: 1 }, -}); - -const contentShow = keyframes({ - "0%": { opacity: 0, transform: "translate(-50%, -48%) scale(.96)" }, - "100%": { opacity: 1, transform: "translate(-50%, -50%) scale(1)" }, -}); - -const DialogOverlay = styled(RadixDialog.Overlay)` - background-color: ${({ theme }) => theme.click.dialog.color.opaqueBackground.default}; - position: fixed; - inset: 0; - animation: ${overlayShow} 150ms cubic-bezier(0.16, 1, 0.3, 1); -`; - -const ContentArea = styled(RadixDialog.Content)<{ $reducePadding?: boolean }>` - background: ${({ theme }) => theme.click.dialog.color.background.default}; - border-radius: ${({ theme }) => theme.click.dialog.radii.all}; - padding-block: ${({ theme, $reducePadding = false }) => - $reducePadding ? theme.sizes[4] : theme.click.dialog.space.y}; - padding-inline: ${({ theme, $reducePadding = false }) => - $reducePadding ? theme.sizes[4] : theme.click.dialog.space.x}; - box-shadow: ${({ theme }) => theme.click.dialog.shadow.default}; - border: 1px solid ${({ theme }) => theme.click.global.color.stroke.default}; - width: 75%; - max-width: 670px; - position: fixed; - top: 50%; - left: 50%; - max-height: 75%; - overflow: auto; - transform: translate(-50%, -50%); - animation: ${contentShow} 150ms cubic-bezier(0.16, 1, 0.3, 1); - outline: none; - - @media (max-width: ${({ theme }) => theme.breakpoint.sizes.sm}) { - max-height: 100%; - border-radius: 0; - width: 100%; - } -`; - -const TitleArea = styled.div<{ $onlyClose?: boolean }>` - display: flex; - justify-content: ${({ $onlyClose }) => ($onlyClose ? "flex-end" : "space-between")}; - align-items: center; - min-height: ${({ theme }) => theme.sizes[9]}; // 32px -`; - -const Title = styled.h2` - font: ${({ theme }) => theme.click.dialog.typography.title.default}; - padding: 0; - margin: 0; -`; const CloseButton = ({ onClose }: { onClose?: () => void }) => ( @@ -142,6 +89,7 @@ const DialogContent = ({ container, showOverlay = true, reducePadding = false, + className, ...props }: DialogContentProps) => { return ( @@ -149,29 +97,48 @@ const DialogContent = ({ forceMount={forceMount} container={container} > - {showOverlay && } - } + {(title || showClose) && ( <> - - {title && {title}} +
+ {title && ( +

+ {title} +

+ )} {showClose && ( )} - +
)} {children} -
+ ); }; diff --git a/src/components/Dropdown/Dropdown.module.scss b/src/components/Dropdown/Dropdown.module.scss new file mode 100644 index 000000000..61b417e71 --- /dev/null +++ b/src/components/Dropdown/Dropdown.module.scss @@ -0,0 +1,118 @@ +@use "cui-mixins" as mixins; +@use "cui-variants" as variants; + +/* Dropdown menu trigger */ +.cuiTrigger { + cursor: pointer; + width: fit-content; + + &:disabled { + cursor: not-allowed; + } +} + +/* Dropdown menu item (base for all items) */ +.cuiMenuItem { + @include mixins.cuiGenericMenuItem; + min-height: 32px; +} + +/* Sub trigger specific styles (extends menu item) */ +.cuiSubTrigger { + @extend .cuiMenuItem; + + &[data-state="open"] { + font: var(--click-genericMenu-item-typography-label-hover); + background: var(--click-genericMenu-item-color-background-hover); + color: var(--click-genericMenu-item-color-text-hover); + cursor: pointer; + } +} + +/* Dropdown menu content panel */ +.cuiMenuContent { + outline: none; + overflow: hidden; + display: flex; + align-items: flex-start; + pointer-events: auto; + min-width: var(--click-genericMenu-item-size-minWidth); + flex-direction: column; + z-index: 1; + overflow-y: auto; + + /* Panel styles */ + border: 1px solid var(--click-genericMenu-panel-color-stroke-default); + background: var(--click-genericMenu-panel-color-background-default); + box-shadow: var(--click-genericMenu-panel-shadow-default); + border-radius: var(--click-genericMenu-panel-radii-all); + + /* Max width/height based on type */ + @include variants.variant('cuiDropdownMenu') { + max-width: var(--radix-dropdown-menu-content-available-width); + max-height: var(--radix-dropdown-menu-content-available-height); + } + + @include variants.variant('cuiContextMenu') { + max-width: var(--radix-context-menu-content-available-width); + max-height: var(--radix-context-menu-content-available-height); + } + + @include variants.variant('cuiPopover') { + max-width: var(--radix-popover-content-available-width); + max-height: var(--radix-popover-content-available-height); + } +} + +/* Arrow margin adjustments when showArrow is true */ +.cuiMenuContentWithArrow { + &[data-side="bottom"] { + margin-top: -1px; + } + + &[data-side="top"] { + margin-bottom: 1px; + } + + &[data-side="left"] { + margin-right: -1px; + } + + &[data-side="right"] { + margin-left: -1px; + } +} + +/* Menu group */ +.cuiMenuGroup { + width: 100%; + border-bottom: 1px solid var(--click-genericMenu-item-color-stroke-default); +} + +/* Menu sub */ +.cuiMenuSub { + border-bottom: 1px solid var(--click-genericMenu-item-color-stroke-default); +} + +/* Arrow styling */ +.cuiArrow { + fill: var(--click-genericMenu-panel-color-background-default); + stroke: var(--click-genericMenu-panel-color-stroke-default); + + /* Default shadow for bottom placement (arrow points up) */ + filter: drop-shadow(0px 4px 6px rgba(0, 0, 0, 0.1)); + + /* Invert shadow when menu is on top (arrow points down) */ + [data-side="top"] & { + filter: drop-shadow(0px -4px 6px rgba(0, 0, 0, 0.1)); + } + + /* Adjust shadow for left/right placements */ + [data-side="left"] & { + filter: drop-shadow(-4px 0px 6px rgba(0, 0, 0, 0.1)); + } + + [data-side="right"] & { + filter: drop-shadow(4px 0px 6px rgba(0, 0, 0, 0.1)); + } +} diff --git a/src/components/Dropdown/Dropdown.stories.tsx b/src/components/Dropdown/Dropdown.stories.tsx index 3580d58e2..f8b8e6e5f 100644 --- a/src/components/Dropdown/Dropdown.stories.tsx +++ b/src/components/Dropdown/Dropdown.stories.tsx @@ -1,7 +1,7 @@ import { DropdownMenuProps } from "@radix-ui/react-dropdown-menu"; import { Dropdown } from "./Dropdown"; -import { GridCenter } from "../commonElement"; -import { Button } from ".."; +import { GridCenter } from "@/components/commonElement"; +import { Button } from "@/components"; import { Key } from "react"; interface Props extends DropdownMenuProps { diff --git a/src/components/Dropdown/Dropdown.tsx b/src/components/Dropdown/Dropdown.tsx index 85b26b0ba..18fd161af 100644 --- a/src/components/Dropdown/Dropdown.tsx +++ b/src/components/Dropdown/Dropdown.tsx @@ -1,29 +1,17 @@ +"use client"; + import * as DropdownMenu from "@radix-ui/react-dropdown-menu"; -import { styled } from "styled-components"; -import { Arrow, GenericMenuItem, GenericMenuPanel } from "../GenericMenu"; -import PopoverArrow from "../icons/PopoverArrow"; -import IconWrapper from "../IconWrapper/IconWrapper"; -import { HorizontalDirection, IconName } from "../types"; -import { Icon } from "../Icon/Icon"; +import clsx from "clsx"; +import PopoverArrow from "@/components/icons/PopoverArrow"; +import { IconWrapper } from "@/components"; +import { HorizontalDirection, IconName } from "@/components/types"; +import { Icon } from "@/components"; +import styles from "./Dropdown.module.scss"; export const Dropdown = (props: DropdownMenu.DropdownMenuProps) => ( ); -const DropdownMenuItem = styled(GenericMenuItem)` - position: relative; - display: flex; - min-height: 32px; - &[data-state="open"] { - ${({ theme }) => ` - font: ${theme.click.genericMenu.item.typography.label.hover}; - background: ${theme.click.genericMenu.item.color.background.hover}; - color: ${theme.click.genericMenu.item.color.text.hover}; - cursor: pointer; - `} - } -`; - interface SubDropdownProps { sub?: true; icon?: IconName; @@ -38,13 +26,6 @@ type DropdownSubTriggerProps = DropdownMenu.DropdownMenuSubTriggerProps & SubDropdownProps; type DropdownTriggerProps = DropdownMenu.DropdownMenuTriggerProps & MainDropdownProps; -const Trigger = styled(DropdownMenu.Trigger)` - cursor: pointer; - width: fit-content; - &[disabled] { - cursor: not-allowed; - } -`; const DropdownTrigger = ({ sub, @@ -54,8 +35,8 @@ const DropdownTrigger = ({ if (sub) { const { icon, iconDir, ...menuProps } = props as DropdownSubTriggerProps; return ( - - + ); } return ( - -
{children}
-
+
{children}
+ ); }; @@ -91,13 +72,6 @@ type DropdownSubContentProps = DropdownMenu.MenuSubContentProps & MainDropdownProps & ArrowProps; -const DropdownMenuContent = styled(GenericMenuPanel)` - min-width: ${({ theme }) => theme.click.genericMenu.item.size.minWidth}; - flex-direction: column; - z-index: 1; - overflow-y: auto; -`; - const DropdownContent = ({ sub, children, @@ -105,29 +79,31 @@ const DropdownContent = ({ ...props }: DropdownContentProps | DropdownSubContentProps) => { const ContentElement = sub ? DropdownMenu.SubContent : DropdownMenu.Content; + const contentClasses = clsx(styles.cuiMenuContent, { + [styles.cuiMenuContentWithArrow]: showArrow, + [styles.cuiDropdownMenu]: !sub, + }); + return ( - {showArrow && ( - - - + + )} {children} - + ); }; @@ -135,26 +111,25 @@ const DropdownContent = ({ DropdownContent.displayName = "DropdownContent"; Dropdown.Content = DropdownContent; -const DropdownMenuGroup = styled(DropdownMenu.Group)` - width: 100%; - border-bottom: 1px solid - ${({ theme }) => theme.click.genericMenu.item.color.stroke.default}; -`; - const DropdownGroup = (props: DropdownMenu.DropdownMenuGroupProps) => { - return ; + return ( + + ); }; DropdownGroup.displayName = "DropdownGroup"; Dropdown.Group = DropdownGroup; -const DropdownMenuSub = styled(DropdownMenu.Sub)` - border-bottom: 1px solid - ${({ theme }) => theme.click.genericMenu.item.color.stroke.default}; -`; - const DropdownSub = ({ ...props }: DropdownMenu.DropdownMenuGroupProps) => { - return ; + return ( + + ); }; DropdownSub.displayName = "DropdownSub"; @@ -166,8 +141,8 @@ interface DropdownItemProps extends DropdownMenu.DropdownMenuItemProps { } const DropdownItem = ({ icon, iconDir, children, ...props }: DropdownItemProps) => { return ( - {children} - + ); }; diff --git a/src/components/EllipsisContent/EllipsisContent.module.scss b/src/components/EllipsisContent/EllipsisContent.module.scss new file mode 100644 index 000000000..445f26e41 --- /dev/null +++ b/src/components/EllipsisContent/EllipsisContent.module.scss @@ -0,0 +1,16 @@ +@use "cui-mixins" as mixins; + +.cuiEllipsisContainer { + display: inline-block; + white-space: nowrap; + text-overflow: ellipsis; + vertical-align: text-bottom; + overflow: hidden; + justify-content: flex-start; + @include mixins.cuiFullWidthStretch; + + & > *:not(button) { + overflow: hidden; + text-overflow: ellipsis; + } +} diff --git a/src/components/EllipsisContent/EllipsisContent.tsx b/src/components/EllipsisContent/EllipsisContent.tsx index 83b71af1d..99f20f8a9 100644 --- a/src/components/EllipsisContent/EllipsisContent.tsx +++ b/src/components/EllipsisContent/EllipsisContent.tsx @@ -1,44 +1,26 @@ -import { - ComponentProps, - ComponentPropsWithRef, - ElementType, - ReactNode, - forwardRef, -} from "react"; +import { ElementType, forwardRef } from "react"; import { mergeRefs } from "@/utils/mergeRefs"; -import { styled } from "styled-components"; - -const EllipsisContainer = styled.div` - display: inline-block; - white-space: nowrap; - text-overflow: ellipsis; - vertical-align: text-bottom; - overflow: hidden; - justify-content: flex-start; - width: 100%; - width: -webkit-fill-available; - width: fill-available; - width: stretch; - & > *:not(button) { - overflow: hidden; - text-overflow: ellipsis; - } -`; -export interface EllipsisContentProps { - component?: T; -} +import clsx from "clsx"; +import { + PolymorphicComponent, + PolymorphicComponentProps, + PolymorphicProps, + PolymorphicRef, +} from "@/utils/polymorphic"; +import styles from "./EllipsisContent.module.scss"; -type EllipsisPolymorphicComponent = ( - props: Omit, keyof T> & EllipsisContentProps -) => ReactNode; +export interface EllipsisContentProps + extends PolymorphicComponentProps {} const _EllipsisContent = ( - { component, ...props }: Omit, keyof T> & EllipsisContentProps, - ref: ComponentPropsWithRef["ref"] + { component, className, ...props }: PolymorphicProps>, + ref: PolymorphicRef ) => { + const Component = component ?? "div"; + return ( - { @@ -52,4 +34,5 @@ const _EllipsisContent = ( ); }; -export const EllipsisContent: EllipsisPolymorphicComponent = forwardRef(_EllipsisContent); +export const EllipsisContent: PolymorphicComponent = + forwardRef(_EllipsisContent); diff --git a/src/components/FileTabs/FileTabs.module.scss b/src/components/FileTabs/FileTabs.module.scss new file mode 100644 index 000000000..4ebf90449 --- /dev/null +++ b/src/components/FileTabs/FileTabs.module.scss @@ -0,0 +1,164 @@ +@use "cui-mixins" as mixins; +@use "cui-variants" as variants; + +.cuiTabsContainer { + display: flex; + position: relative; + overflow: auto; + overscroll-behavior: none; + scrollbar-width: 0; + max-width: var(--dynamic-max-width); + + &::-webkit-scrollbar { + height: 0; + } +} + +.cuiTabsSortableContainer { + display: flex; + + & > div { + height: 100%; + outline: none; + min-width: 100px; + width: clamp(100px, 100%, 200px); + + &.sortable-ghost { + opacity: 0; + } + } +} + +.cuiTabElement { + display: grid; + justify-content: flex-start; + align-items: center; + outline: none; + @include mixins.cuiFullMaxWidthStretch; + border: none; + cursor: pointer; + height: 100%; + max-height: 100%; + box-sizing: border-box; + width: 100%; + padding: var(--click-tabs-fileTabs-space-y) var(--click-tabs-fileTabs-space-x); + gap: var(--click-tabs-fileTabs-space-gap); + border-radius: var(--click-tabs-fileTabs-radii-all); + border-right: 1px solid var(--click-tabs-fileTabs-color-stroke-default); + background: var(--click-tabs-fileTabs-color-background-default); + color: var(--click-tabs-fileTabs-color-text-default); + font: var(--click-tabs-fileTabs-typography-label-default); + + svg, + [data-indicator] { + height: var(--click-tabs-fileTabs-icon-size-height); + width: var(--click-tabs-fileTabs-icon-size-width); + } + + [data-type="close"] { + display: none; + } + + [data-indicator] { + display: block; + } + + &:hover { + [data-type="close"] { + display: block; + } + + [data-indicator] { + display: none; + } + } + + @include variants.variant('cuiActive') { + background: var(--click-tabs-fileTabs-color-background-active); + color: var(--click-tabs-fileTabs-color-text-active); + font: var(--click-tabs-fileTabs-typography-label-active); + border-right: 1px solid var(--click-tabs-fileTabs-color-stroke-active); + } + + &:not(.cuiActive):hover { + background: var(--click-tabs-fileTabs-color-background-hover); + color: var(--click-tabs-fileTabs-color-text-hover); + font: var(--click-tabs-fileTabs-typography-label-hover); + border-right: 1px solid var(--click-tabs-fileTabs-color-stroke-hover); + } + + @include variants.variant('cuiPreview') { + font-style: italic; + } + + @include variants.variant('cuiDismissable') { + grid-template-columns: 1fr var(--click-tabs-fileTabs-icon-size-width); + } + + @include variants.variant('cuiFixedTabElement') { + width: auto; + } +} + +.cuiIndicator { + position: relative; + + &::after { + position: absolute; + left: 0.25rem; + top: 0.25rem; + content: ""; + width: 0.5rem; + height: 0.5rem; + border-radius: 50%; + } + + &.default::after { + background: transparent; + } + + &.success::after { + background: var(--click-alert-color-text-success); + } + + &.neutral::after { + background: var(--click-alert-color-text-neutral); + } + + &.danger::after { + background: var(--click-alert-color-text-danger); + } + + &.warning::after { + background: var(--click-alert-color-text-warning); + } + + &.info::after { + background: var(--click-alert-color-text-info); + } +} + +.cuiTabContent { + display: flex; + justify-content: flex-start; + align-items: center; + flex-wrap: nowrap; + overflow: hidden; + gap: var(--click-tabs-fileTabs-space-gap); +} + +.cuiTabContentText { + display: inline-block; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; +} + +.cuiEmptyButton { + @include mixins.cuiEmptyButton; + background: var(--click-tabs-fileTabs-color-closeButton-background-default); + + &:hover { + background: var(--click-tabs-fileTabs-color-closeButton-background-hover); + } +} diff --git a/src/components/FileTabs/FileTabs.tsx b/src/components/FileTabs/FileTabs.tsx index 292413ccb..37750d8ea 100644 --- a/src/components/FileTabs/FileTabs.tsx +++ b/src/components/FileTabs/FileTabs.tsx @@ -1,3 +1,5 @@ +"use client"; + import { HTMLAttributes, createContext, @@ -11,9 +13,9 @@ import { WheelEvent, useRef, } from "react"; -import { styled } from "styled-components"; +import clsx from "clsx"; import { Icon, IconButton } from "@/components"; -import { IconName } from "../Icon/types"; +import { IconName } from "@/components/Icon/types"; import { ItemInterface, ReactSortable, @@ -21,6 +23,7 @@ import { Sortable, Store, } from "react-sortablejs"; +import styles from "./FileTabs.module.scss"; export type FileTabStatusType = | "default" @@ -30,30 +33,6 @@ export type FileTabStatusType = | "warning" | "info"; -const TabsContainer = styled.div<{ $count: number }>` - display: flex; - position: relative; - overflow: auto; - overscroll-behavior: none; - scrollbar-width: 0; - max-width: ${({ $count }) => `${$count * 200}px`}; - &::-webkit-scrollbar { - height: 0; - } -`; -const TabsSortableContainer = styled.div` - display: flex; - & > div { - height: 100%; - outline: none; - min-width: 100px; - width: clamp(100px, 100%, 200px); - &.sortable-ghost { - opacity: 0; - } - } -`; - interface ContextProps { selectedIndex?: number; onClose: (index: number) => void; @@ -147,7 +126,7 @@ export const FileTabs = ({ }; return ( - - ))} - - + + ); }; -const TabElement = styled.div<{ - $active: boolean; - $preview?: boolean; - $dismissable: boolean; - $fixedTabElement?: boolean; -}>` - display: grid; - justify-content: flex-start; - align-items: center; - outline: none; - max-width: 100%; - max-width: -webkit-fill-available; - max-width: fill-available; - max-width: stretch; - border: none; - cursor: pointer; - height: 100%; - max-height: 100%; - box-sizing: border-box; - ${({ theme, $active, $preview, $dismissable, $fixedTabElement }) => ` - width:${$fixedTabElement ? "auto" : "100%"}; - grid-template-columns: 1fr ${ - $dismissable ? theme.click.tabs.fileTabs.icon.size.width : "" - }; - padding: ${theme.click.tabs.fileTabs.space.y} ${theme.click.tabs.fileTabs.space.x}; - gap: ${theme.click.tabs.fileTabs.space.gap}; - border-radius: ${theme.click.tabs.fileTabs.radii.all}; - border-right: 1px solid ${theme.click.tabs.fileTabs.color.stroke.default}; - background: ${theme.click.tabs.fileTabs.color.background.default}; - color: ${theme.click.tabs.fileTabs.color.text.default}; - font: ${theme.click.tabs.fileTabs.typography.label.default}; - svg, - [data-indicator] { - height: ${theme.click.tabs.fileTabs.icon.size.height}; - width: ${theme.click.tabs.fileTabs.icon.size.width}; - } - ${ - $active - ? ` - background: ${theme.click.tabs.fileTabs.color.background.active}; - color: ${theme.click.tabs.fileTabs.color.text.active}; - font: ${theme.click.tabs.fileTabs.typography.label.active}; - border-right: 1px solid ${theme.click.tabs.fileTabs.color.stroke.active}; - ` - : ` - &:hover { - background: ${theme.click.tabs.fileTabs.color.background.hover}; - color: ${theme.click.tabs.fileTabs.color.text.hover}; - font: ${theme.click.tabs.fileTabs.typography.label.hover}; - border-right: 1px solid ${theme.click.tabs.fileTabs.color.stroke.hover}; - } - ` - } - ${$preview === true ? "font-style: italic;" : ""} - `} - [data-type="close"] { - display: none; - } - [data-indicator] { - display: block; - } - &:hover { - [data-type="close"] { - display: block; - } - [data-indicator] { - display: none; - } - } -`; - -const Indicator = styled.div<{ $status: FileTabStatusType }>` - position: relative; - &::after { - position: absolute; - left: 0.25rem; - top: 0.25rem; - content: ""; - width: 0.5rem; - height: 0.5rem; - ${({ theme, $status }) => ` - background: ${ - $status === "default" ? "transparent" : theme.click.alert.color.text[$status] - }; - border-radius: 50%; - `} - } -`; - -const TabContent = styled.div` - display: flex; - justify-content: flex-start; - align-items: center; - flex-wrap: nowrap; - overflow: hidden; - gap: ${({ theme }) => theme.click.tabs.fileTabs.space.gap}; -`; - -const TabContentText = styled.span` - display: inline-block; - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; -`; - -const EmptyButton = styled.button` - padding: 0; - ${({ theme }) => theme.click.tabs.fileTabs.color.closeButton.background.default}; - &:hover { - background: ${({ theme }) => - theme.click.tabs.fileTabs.color.closeButton.background.hover}; - } -`; - const Tab = ({ text, index, @@ -317,6 +187,7 @@ const Tab = ({ status = "default", testId, preview, + className, ...props }: FileTabProps) => { const { selectedIndex, onClose: onCloseProp } = useSelect(); @@ -338,31 +209,37 @@ const Tab = ({ }; return ( - - +
{typeof icon === "string" ? : icon} - {text} - - {text} +
+ - -
+ ); }; @@ -380,18 +257,24 @@ export const FileTabElement = ({ children, active = false, preview, + className, ...props }: FileTabElementProps) => { return ( - {typeof icon === "string" ? : icon} - {children && {children}} - + {children && {children}} + ); }; diff --git a/src/components/FileUpload/FileMultiUpload.module.scss b/src/components/FileUpload/FileMultiUpload.module.scss new file mode 100644 index 000000000..5565fe7fb --- /dev/null +++ b/src/components/FileUpload/FileMultiUpload.module.scss @@ -0,0 +1,36 @@ +@use "cui-mixins" as mixins; +@use "cui-variants" as variants; + +/* Component-specific styles only - common styles imported in TSX */ +.cuiUploadArea { + flex-direction: column; + justify-content: center; + cursor: pointer; +} + +.cuiFileUploadTitle { + @include variants.variant('cuiNotSupported') { + color: var(--click-fileUpload-color-title-error); + } +} + +.cuiUploadText { + text-align: center; + display: flex; + flex-direction: column; + align-items: center; + width: 100%; +} + +.cuiFilesList { + display: flex; + flex-direction: column; + gap: var(--click-fileUpload-sm-space-gap); + width: 100%; + margin-top: var(--click-fileUpload-md-space-gap); +} + +.cuiFileItem { + flex-direction: row; + justify-content: space-between; +} diff --git a/src/components/FileUpload/FileMultiUpload.tsx b/src/components/FileUpload/FileMultiUpload.tsx index 93891c8ae..37e037863 100644 --- a/src/components/FileUpload/FileMultiUpload.tsx +++ b/src/components/FileUpload/FileMultiUpload.tsx @@ -1,12 +1,13 @@ import React, { useEffect } from "react"; -import styled from "styled-components"; -import { css } from "styled-components"; import { useState, useRef, useCallback } from "react"; +import clsx from "clsx"; import { truncateFilename } from "@/utils/truncate.ts"; import { Text } from "@/components/Typography/Text/Text"; import { Title } from "@/components/Typography/Title/Title"; import { Button, Icon, IconButton, ProgressBar } from "@/components"; +import styles from "./FileMultiUpload.module.scss"; +import commonStyles from "./FileUploadCommon.module.scss"; export interface FileUploadItem { id: string; @@ -27,131 +28,6 @@ interface FileMultiUploadProps { onFileFailure?: () => void; } -const UploadArea = styled.div<{ - $isDragging: boolean; -}>` - background-color: ${({ theme }) => theme.click.fileUpload.color.background.default}; - border: ${({ theme }) => `1px solid ${theme.click.fileUpload.color.stroke.default}`}; - border-radius: ${({ theme }) => theme.click.fileUpload.md.radii.all}; - padding: ${({ theme }) => - `${theme.click.fileUpload.md.space.y} ${theme.click.fileUpload.md.space.x}`}; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - gap: ${({ theme }) => theme.click.fileUpload.md.space.gap}; - cursor: pointer; - transition: ${({ theme }) => theme.click.fileUpload.transitions.all}; - border-style: dashed; - border-color: ${({ theme }) => theme.click.fileUpload.color.stroke.default}; - - ${props => - props.$isDragging && - css` - background-color: ${({ theme }) => theme.click.fileUpload.color.background.active}; - border-color: ${({ theme }) => theme.click.fileUpload.color.stroke.active}; - `} -`; - -const FileUploadTitle = styled(Title)<{ $isNotSupported: boolean }>` - font: ${({ theme }) => theme.click.fileUpload.typography.title.default}; - color: ${({ theme, $isNotSupported }) => - $isNotSupported - ? theme.click.fileUpload.color.title.error - : theme.click.fileUpload.color.title.default}; -`; - -const FileName = styled(Text)` - font: ${({ theme }) => theme.click.fileUpload.typography.description.default}; - color: ${({ theme }) => theme.click.fileUpload.color.title.default}; -`; - -const FileUploadDescription = styled(Text)<{ $isError?: boolean }>` - font: ${({ theme }) => theme.click.fileUpload.typography.description.default}; - color: ${({ theme, $isError }) => - $isError - ? theme.click.fileUpload.color.title.error - : theme.click.fileUpload.color.description.default}; -`; - -const UploadIcon = styled(Icon)` - svg { - width: ${({ theme }) => theme.click.fileUpload.md.icon.size.width}; - height: ${({ theme }) => theme.click.fileUpload.md.icon.size.height}; - color: ${({ theme }) => theme.click.fileUpload.md.color.icon.default}; - } -`; - -const UploadText = styled.div` - text-align: center; - display: flex; - flex-direction: column; - align-items: center; - width: 100%; -`; - -const FilesList = styled.div` - display: flex; - flex-direction: column; - gap: ${({ theme }) => theme.click.fileUpload.sm.space.gap}; - width: 100%; - margin-top: ${({ theme }) => theme.click.fileUpload.md.space.gap}; -`; - -const FileItem = styled.div<{ $isError?: boolean }>` - background-color: ${({ theme }) => theme.click.fileUpload.color.background.default}; - border: ${({ theme }) => `1px solid ${theme.click.fileUpload.color.stroke.default}`}; - border-radius: ${({ theme }) => theme.click.fileUpload.sm.radii.all}; - padding: ${({ theme }) => - `${theme.click.fileUpload.sm.space.y} ${theme.click.fileUpload.sm.space.x}`}; - display: flex; - flex-direction: row; - align-items: center; - justify-content: space-between; - gap: ${({ theme }) => theme.click.fileUpload.sm.space.gap}; - - ${props => - props.$isError && - css` - background-color: ${({ theme }) => theme.click.fileUpload.color.background.error}; - border-color: transparent; - `} -`; - -const DocumentIcon = styled(Icon)` - svg { - width: ${({ theme }) => theme.click.fileUpload.sm.icon.size.width}; - height: ${({ theme }) => theme.click.fileUpload.sm.icon.size.height}; - color: ${({ theme }) => theme.click.fileUpload.sm.color.icon.default}; - } -`; - -const FileDetails = styled.div` - display: flex; - gap: ${({ theme }) => theme.click.fileUpload.md.space.gap}; - border: none; -`; - -const FileActions = styled.div` - display: flex; - align-items: center; - margin-left: auto; - gap: 0; -`; - -const FileContentContainer = styled.div` - flex: 1; - display: flex; - flex-direction: column; - justify-content: center; - min-height: 24px; -`; - -const ProgressBarWrapper = styled.div` - margin-top: ${({ theme }) => theme.click.fileUpload.md.space.gap}; - margin-bottom: 9px; -`; - const formatFileSize = (sizeInBytes: number): string => { if (sizeInBytes < 1024) { return `${sizeInBytes.toFixed(1)} B`; @@ -317,35 +193,52 @@ export const FileMultiUpload = ({ return ( <> - - - + +
{!isSupported ? ( - Unsupported file type - + ) : ( - {title} - + )} - + Files supported: {supportedFileTypes.join(", ")} - - + +
-
+ {files.length > 0 && ( - +
{files.map(file => ( - - - - - {truncateFilename(file.name)} + +
+
+ {truncateFilename(file.name)} {file.status === "uploading" && ( - {file.progress}% + + {file.progress}% + )} {file.status === "error" && ( - + {file.errorMessage || "Upload failed"} - + )} {file.status === "success" && ( )} - +
+ {file.status === "uploading" && ( - +
- +
)} {(file.status === "success" || file.status === "error") && ( - + {formatFileSize(file.size)} - + )} - - +
+ +
{file.status === "error" && ( handleRemoveFile(file.id)} /> - - +
+
))} -
+ )} void; } -const UploadArea = styled.div<{ - $isDragging: boolean; - $size: "sm" | "md"; - $hasFile: boolean; - $isError?: boolean; -}>` - background-color: ${({ theme }) => theme.click.fileUpload.color.background.default}; - border: ${({ theme }) => `1px solid ${theme.click.fileUpload.color.stroke.default}`}; - border-radius: ${({ theme, $hasFile }) => - $hasFile - ? `${theme.click.fileUpload.sm.radii.all}` - : `${theme.click.fileUpload.md.radii.all}`}; - padding: ${({ theme, $hasFile, $size }) => - $hasFile || $size === "sm" - ? `${theme.click.fileUpload.sm.space.y} ${theme.click.fileUpload.sm.space.x}` - : `${theme.click.fileUpload.md.space.y} ${theme.click.fileUpload.md.space.x}`}; - min-height: ${({ theme, $size }) => - $size === "sm" - ? `calc(${theme.click.fileUpload.sm.space.y} * 2 + ${theme.sizes[6]})` - : "auto"}; - display: flex; - flex-direction: ${props => - props.$hasFile ? "row" : props.$size === "sm" ? "row" : "column"}; - align-items: center; - justify-content: ${props => - props.$hasFile ? "space-between" : props.$size === "sm" ? "space-between" : "center"}; - gap: ${({ theme, $size }) => - $size === "sm" - ? theme.click.fileUpload.sm.space.gap - : theme.click.fileUpload.md.space.gap}; - cursor: ${props => (props.$hasFile ? "default" : "pointer")}; - transition: ${({ theme }) => theme.click.fileUpload.transitions.all}; - - ${props => - !props.$hasFile && - css` - border-style: dashed; - border-color: ${({ theme }) => theme.click.fileUpload.color.stroke.default}; - - ${props.$isDragging && - css` - background-color: ${({ theme }) => - theme.click.fileUpload.color.background.active}; - border-color: ${({ theme }) => theme.click.fileUpload.color.stroke.active}; - `} - `} - - ${props => - props.$isError && - css` - background-color: ${({ theme }) => theme.click.fileUpload.color.background.error}; - border-color: transparent; - `} -`; - -const FileUploadTitle = styled(Title)<{ $isNotSupported: boolean }>` - font: ${({ theme }) => theme.click.fileUpload.typography.title.default}; - color: ${({ theme, $isNotSupported }) => - $isNotSupported - ? theme.click.fileUpload.color.title.error - : theme.click.fileUpload.color.title.default}; -`; - -const FileName = styled(Text)` - font: ${({ theme }) => theme.click.fileUpload.typography.description.default}; - color: ${({ theme }) => theme.click.fileUpload.color.title.default}; -`; - -const FileUploadDescription = styled(Text)<{ $isError?: boolean }>` - font: ${({ theme }) => theme.click.fileUpload.typography.description.default}; - color: ${({ theme, $isError }) => - $isError - ? theme.click.fileUpload.color.title.error - : theme.click.fileUpload.color.description.default}; -`; - -const DocumentIcon = styled(Icon)` - svg { - width: ${({ theme }) => theme.click.fileUpload.md.icon.size.width}; - height: ${({ theme }) => theme.click.fileUpload.md.icon.size.height}; - color: ${({ theme }) => theme.click.fileUpload.md.color.icon.default}; - } -`; - -const UploadIcon = styled(Icon)` - svg { - width: ${({ theme }) => theme.click.fileUpload.md.icon.size.width}; - height: ${({ theme }) => theme.click.fileUpload.md.icon.size.height}; - color: ${({ theme }) => theme.click.fileUpload.md.color.icon.default}; - } -`; - -const UploadText = styled.div<{ $size: "sm" | "md"; $hasFile: boolean }>` - text-align: ${props => (props.$hasFile || props.$size === "sm" ? "left" : "center")}; - ${props => - (props.$hasFile || props.$size === "sm") && - css` - flex: 1; - `} - - ${props => - !props.$hasFile && - props.$size === "md" && - css` - display: flex; - flex-direction: column; - align-items: center; - width: 100%; - `} -`; - -const FileInfo = styled.div` - display: flex; - flex-direction: row; - gap: ${({ theme }) => theme.click.fileUpload.hasFile.header.space.gap}; -`; - -const FileDetails = styled.div` - display: flex; - gap: ${({ theme }) => theme.click.fileUpload.md.space.gap}; - border: none; -`; - -const FileActions = styled.div` - display: flex; - align-items: center; - margin-left: auto; - gap: 0; -`; - -const FileContentContainer = styled.div<{ $size: "sm" | "md" }>` - flex: 1; - display: flex; - flex-direction: column; - justify-content: center; - min-height: ${({ $size }) => ($size === "sm" ? "24px" : "auto")}; -`; - -const ProgressBarWrapper = styled.div` - margin-top: ${({ theme }) => theme.click.fileUpload.md.space.gap}; - margin-bottom: 9px; -`; - const formatFileSize = (sizeInBytes: number): string => { if (sizeInBytes < 1024) { return `${sizeInBytes.toFixed(1)} B`; @@ -235,7 +95,6 @@ export const FileUpload = ({ dragCounterRef.current -= 1; - // Only set to false when left the container if (dragCounterRef.current <= 0) { setIsDragging(false); dragCounterRef.current = 0; @@ -247,7 +106,6 @@ export const FileUpload = ({ e.stopPropagation(); }, []); - // Reset state when drag ends anywhere in the document useEffect(() => { const handleDragEnd = () => { setIsDragging(false); @@ -343,11 +201,20 @@ export const FileUpload = ({ return ( <> - {!file ? ( <> - - +
{isNotSupported ? ( - Unsupported file type - + ) : ( - {title} - + )} - + Files supported: {supportedFileTypes.join(", ")} - - + +
+ ); } ); IconButton.displayName = "IconButton"; - -const Button = styled.button<{ - $styleType?: "primary" | "secondary" | "ghost" | "danger" | "info"; - $size?: "default" | "sm" | "xs"; -}>` - ${({ theme, $size, $styleType = "primary" }) => ` - border-radius: ${theme.click.button.iconButton.radii.all}; - border: ${theme.click.button.stroke} solid ${ - theme.click.button.iconButton.color[$styleType].stroke.default - }; - cursor: pointer; - padding: ${ - $size - ? `${theme.click.button.iconButton[$size].space.y} ${theme.click.button.iconButton[$size].space.x}` - : `${theme.click.button.iconButton.default.space.y} ${theme.click.button.iconButton.default.space.x}` - }; - - background-color: ${theme.click.button.iconButton.color[$styleType].background.default}; - - color: ${theme.click.button.iconButton.color[$styleType].text.default}; - - &:hover { - background-color: ${theme.click.button.iconButton.color[$styleType].background.hover}; - color: ${theme.click.button.iconButton.color[$styleType].text.hover}; - border-color: ${theme.click.button.iconButton.color[$styleType].stroke.hover}; - } - - &:focus, &:active, &:focus-within { - background-color: ${ - theme.click.button.iconButton.color[$styleType].background.active - }; - color: ${theme.click.button.iconButton.color[$styleType].text.active}; - border-color: ${theme.click.button.iconButton.color[$styleType].stroke.active}; - } - - &:visited { - background-color: ${ - theme.click.button.iconButton.color[$styleType].background.default - }; - } - - &[disabled] { - background-color: ${theme.click.button.iconButton.color.disabled.background.default}; - color: ${theme.click.button.iconButton.color.disabled.text.default}; - cursor: not-allowed; - } - `} -`; diff --git a/src/components/IconWrapper/IconWrapper.tsx b/src/components/IconWrapper/IconWrapper.tsx index 138113e2f..f8a30aeeb 100644 --- a/src/components/IconWrapper/IconWrapper.tsx +++ b/src/components/IconWrapper/IconWrapper.tsx @@ -1,4 +1,4 @@ -import { ReactNode } from "react"; +import React, { ReactNode } from "react"; import { HorizontalDirection, IconName } from "@/components"; import { Container, GapOptions } from "@/components/Container/Container"; @@ -6,7 +6,7 @@ import { EllipsisContent } from "@/components/EllipsisContent/EllipsisContent"; import { Icon } from "@/components/Icon/Icon"; import { IconSize } from "@/components/Icon/types"; -interface IconWrapperProps { +interface IconWrapperProps extends React.HTMLAttributes { icon?: IconName; iconDir?: HorizontalDirection; size?: IconSize; diff --git a/src/components/Input/InputWrapper.module.scss b/src/components/Input/InputWrapper.module.scss new file mode 100644 index 000000000..58997e21f --- /dev/null +++ b/src/components/Input/InputWrapper.module.scss @@ -0,0 +1,295 @@ +@use "cui-mixins" as mixins; +@use "cui-variants" as variants; + +// Wrapper for input fields with various states +.cuiWrapper { + width: inherit; + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--click-field-space-gap); + border-radius: var(--click-field-radii-all); + font: var(--click-field-typography-fieldText-default); + color: var(--click-field-color-text-default); + border: 1px solid var(--click-field-color-stroke-default); + background: var(--click-field-color-background-default); + + span:first-of-type { + max-width: 100%; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + + // Autofill styles + :global(*:autofill), + :global(*:-webkit-autofill) { + -webkit-box-shadow: 0 0 0px 50vh var(--click-field-color-background-default) inset; + -webkit-text-fill-color: var(--click-field-color-text-default); + caret-color: var(--click-field-color-text-default); + } + + &:hover { + border: 1px solid var(--click-field-color-stroke-hover); + background: var(--click-field-color-background-hover); + color: var(--click-field-color-text-hover); + + :global(*:autofill), + :global(*:-webkit-autofill) { + -webkit-box-shadow: 0 0 0px 50vh var(--click-field-color-background-hover) inset; + -webkit-text-fill-color: var(--click-field-color-text-hover); + caret-color: var(--click-field-color-text-hover); + } + } + + &:focus-within, + &[data-state="open"] { + font: var(--click-field-typography-fieldText-active); + border: 1px solid var(--click-field-color-stroke-active); + background: var(--click-field-color-background-active); + color: var(--click-field-color-text-active); + + :global(*:autofill), + :global(*:-webkit-autofill) { + -webkit-box-shadow: 0 0 0px 50vh var(--click-field-color-background-active) inset; + -webkit-text-fill-color: var(--click-field-color-text-active); + caret-color: var(--click-field-color-text-active); + } + } + + &:disabled, + &.disabled { + font: var(--click-field-typography-fieldText-disabled); + border: 1px solid var(--click-field-color-stroke-disabled); + background: var(--click-field-color-background-disabled); + color: var(--click-field-color-text-disabled); + + :global(*:autofill), + :global(*:-webkit-autofill) { + -webkit-box-shadow: 0 0 0px 50vh var(--click-field-color-background-disabled) inset; + -webkit-text-fill-color: var(--click-field-color-text-disabled); + caret-color: var(--click-field-color-text-disabled); + } + } +} + +// Error state styling +.cuiError { + font: var(--click-field-typography-fieldText-error); + border: 1px solid var(--click-field-color-stroke-error); + background: var(--click-field-color-background-active); + color: var(--click-field-color-text-error); + + :global(*:autofill), + :global(*:-webkit-autofill) { + -webkit-box-shadow: 0 0 0px 50vh var(--click-field-color-background-error) inset; + -webkit-text-fill-color: var(--click-field-color-text-error); + caret-color: var(--click-field-color-text-error); + } + + &:hover { + border: 1px solid var(--click-field-color-stroke-error); + color: var(--click-field-color-text-error); + + :global(*:autofill), + :global(*:-webkit-autofill) { + -webkit-box-shadow: 0 0 0px 50vh var(--click-field-color-background-error) inset; + -webkit-text-fill-color: var(--click-field-color-text-error); + caret-color: var(--click-field-color-text-error); + } + } + + // Override focus styles for error state + &:focus-within, + &[data-state="open"] { + font: var(--click-field-typography-fieldText-error); + border: 1px solid var(--click-field-color-stroke-error); + background: var(--click-field-color-background-active); + color: var(--click-field-color-text-error); + } +} + +// Resize variants +.cuiWrapper { + @include variants.variant('cuiResizeVertical') { + resize: vertical; + overflow: auto; + } + + @include variants.variant('cuiResizeHorizontal') { + resize: horizontal; + overflow: auto; + } + + @include variants.variant('cuiResizeBoth') { + resize: both; + overflow: auto; + } +} + +// Form root container +.cuiFormRoot { + @include mixins.cuiFormRoot(); + + // Orientation variants + @include variants.variant('cuiOrientationVertical') { + @include variants.variant('cuiDirEnd') { + flex-direction: column; + } + + @include variants.variant('cuiDirStart') { + flex-direction: column-reverse; + } + } + + @include variants.variant('cuiOrientationHorizontal') { + @include variants.variant('cuiDirEnd') { + flex-direction: row; + } + + @include variants.variant('cuiDirStart') { + flex-direction: row-reverse; + } + } +} + +// Label padding for horizontal layout +.cuiLabelPadding { + label { + padding-top: calc(var(--click-field-space-y) + 1px); + line-height: 1lh; + } +} + +// Form element container +.cuiFormElementContainer { + display: flex; + flex-direction: column; + align-items: flex-start; + @include mixins.cuiFullWidthStretch; + gap: var(--click-field-space-gap); +} + +// Error message styling +.cuiErrorMessage { + font: var(--click-field-typography-label-error); + color: var(--click-field-color-label-error); +} + +// Label styling +.cuiLabel { + // Base label styles will be inherited from Label component +} + +.cuiLabelWithColor { + // Custom color will be applied via style prop +} + +// Input element base +.cuiInputElement { + @include mixins.cuiInputElement; +} + +// Input padding variants +.cuiInputElement { + @include variants.variant('cuiPaddingEnd') { + padding-left: 0; + } + + @include variants.variant('cuiPaddingStart') { + padding-right: 0; + } + + @include variants.variant('cuiPaddingNone') { + padding-left: 0; + padding-right: 0; + } + + @include variants.variant('cuiPaddingBoth') { + padding-left: var(--click-field-space-x); + padding-right: var(--click-field-space-x); + } +} + +// Number input element +.cuiNumberInputElement { + @extend .cuiInputElement; +} + +// Hide number input controls +.cuiHideNumberControls { + &::-webkit-outer-spin-button, + &::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; + } + + -moz-appearance: textfield; +} + +// Textarea element +.cuiTextareaElement { + background: transparent; + border: none; + outline: none; + width: 100%; + color: inherit; + font: inherit; + resize: none; + padding: var(--click-field-space-y) var(--click-field-space-x); + align-self: stretch; + + &::placeholder { + color: var(--click-field-color-placeholder-default); + } + + &:disabled, + &.disabled { + &::placeholder { + color: var(--click-field-color-placeholder-disabled); + } + } +} + +// Icon button +.cuiIconButton { + background: transparent; + color: inherit; + border: none; + padding: var(--click-field-space-y) 0; + outline: none; + + &:not(:disabled) { + cursor: pointer; + } +} + +// Icon wrapper +.cuiIconWrapper { + &:first-of-type { + padding-left: var(--click-field-space-gap); + } + + &:last-of-type { + padding-right: var(--click-field-space-x); + } +} + +// Input start content +.cuiInputStartContent { + padding-left: var(--click-field-space-x); + cursor: text; + gap: var(--click-field-space-gap); + display: flex; + align-self: stretch; + align-items: center; +} + +// Input end content +.cuiInputEndContent { + padding-right: var(--click-field-space-x); + gap: var(--click-field-space-gap); + display: flex; + align-self: stretch; + align-items: center; +} diff --git a/src/components/Input/InputWrapper.tsx b/src/components/Input/InputWrapper.tsx index 86ec10ad7..24376e5eb 100644 --- a/src/components/Input/InputWrapper.tsx +++ b/src/components/Input/InputWrapper.tsx @@ -1,130 +1,51 @@ -import { Error, FormElementContainer, FormRoot } from "../commonElement"; import { Label } from "@/components"; -import { styled } from "styled-components"; -import { ReactNode } from "react"; +import React, { ReactNode } from "react"; +import clsx from "clsx"; +import { capitalize } from "@/utils/capitalize"; +import styles from "./InputWrapper.module.scss"; -const Wrapper = styled.div<{ - $error: boolean; - $resize: "none" | "vertical" | "horizontal" | "both"; -}>` - width: inherit; - display: flex; - align-items: center; - justify-content: space-between; - align-items: center; - - span:first-of-type { - max-width: 100%; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - } - - ${({ theme, $error, $resize }) => ` - gap: ${theme.click.field.space.gap}; - border-radius: ${theme.click.field.radii.all}; - font: ${theme.click.field.typography.fieldText.default}; - color: ${theme.click.field.color.text.default}; - border: 1px solid ${theme.click.field.color.stroke.default}; - background: ${theme.click.field.color.background.default}; - - *:autofill, - *:-webkit-autofill { - -webkit-box-shadow: 0 0 0px 50vh ${ - theme.click.field.color.background.default - } inset; - -webkit-text-fill-color: ${theme.click.field.color.text.default}; - caret-color: ${theme.click.field.color.text.default}; - } - - &:hover { - border: 1px solid ${theme.click.field.color.stroke.hover}; - background: ${theme.click.field.color.background.hover}; - color: ${theme.click.field.color.text.hover}; - - *:autofill, - *:-webkit-autofill { - -webkit-box-shadow: 0 0 0px 50vh ${ - theme.click.field.color.background.hover - } inset; - -webkit-text-fill-color: ${theme.click.field.color.text.hover}; - caret-color: ${theme.click.field.color.text.hover}; - } - } - ${ - $resize === "none" - ? "" - : ` - resize: ${$resize}; - overflow: auto; - ` +const FormRoot = ({ + children, + orientation = "vertical", + dir = "start", + addLabelPadding, +}: { + children: ReactNode; + orientation?: "horizontal" | "vertical"; + dir?: "start" | "end"; + addLabelPadding?: boolean; +}) => { + const getFlexDirection = () => { + if (orientation === "horizontal") { + return dir === "start" ? "row-reverse" : "row"; } - ${ - $error - ? ` - font: ${theme.click.field.typography.fieldText.error}; - border: 1px solid ${theme.click.field.color.stroke.error}; - background: ${theme.click.field.color.background.active}; - color: ${theme.click.field.color.text.error}; + return dir === "start" ? "column-reverse" : "column"; + }; - *:autofill, - *:-webkit-autofill { - -webkit-box-shadow: 0 0 0px 50vh ${theme.click.field.color.background.error} inset; - -webkit-text-fill-color: ${theme.click.field.color.text.error}; - caret-color: ${theme.click.field.color.text.error}; - } + const orientationClass = `cuiOrientation${capitalize(orientation)}`; + const dirClass = `cuiDir${capitalize(dir)}`; - &:hover { - border: 1px solid ${theme.click.field.color.stroke.error}; - color: ${theme.click.field.color.text.error}; - *:autofill, - *:-webkit-autofill { - -webkit-box-shadow: 0 0 0px 50vh ${theme.click.field.color.background.error} inset; - -webkit-text-fill-color: ${theme.click.field.color.text.error}; - caret-color: ${theme.click.field.color.text.error}; - } - } - ` - : ` - &:focus-within, - &[data-state="open"] { - font: ${theme.click.field.typography.fieldText.active}; - border: 1px solid ${theme.click.field.color.stroke.active}; - background: ${theme.click.field.color.background.active}; - color: ${theme.click.field.color.text.active}; + return ( +
+ {children} +
+ ); +}; - *:autofill, - *:-webkit-autofill { - -webkit-box-shadow: 0 0 0px 50vh ${theme.click.field.color.background.active} inset; - -webkit-text-fill-color: ${theme.click.field.color.text.active}; - caret-color: ${theme.click.field.color.text.active}; - } - } - ` - }; - &:disabled, &.disabled { - font: ${theme.click.field.typography.fieldText.disabled}; - border: 1px solid ${theme.click.field.color.stroke.disabled}; - background: ${theme.click.field.color.background.disabled}; - color: ${theme.click.field.color.text.disabled}; +const FormElementContainer = ({ children }: { children: ReactNode }) => ( +
{children}
+); - *:autofill, - *:-webkit-autofill { - -webkit-box-shadow: 0 0 0px 50vh ${ - theme.click.field.color.background.disabled - } inset; - -webkit-text-fill-color: ${theme.click.field.color.text.disabled}; - caret-color: ${theme.click.field.color.text.disabled}; - } - } - `} -`; - -const StyledLabel = styled(Label)<{ $labelColor?: string }>` - ${({ $labelColor }) => ` - ${$labelColor ? `color: ${$labelColor};` : ""} - `} -`; +const Error = ({ children }: { children: ReactNode }) => ( +
{children}
+); export interface WrapperProps { className?: string; @@ -151,139 +72,139 @@ export const InputWrapper = ({ dir, resize = "none", }: WrapperProps) => { + const resizeClass = resize !== "none" ? `cuiResize${capitalize(resize)}` : undefined; + return ( - {children} - + {!!error && error !== true && {error}} {label && ( - {label} - + )} ); }; -export const InputElement = styled.input<{ - $hasStartContent?: boolean; - $hasEndContent?: boolean; -}>` - background: transparent; - border: none; - outline: none; - width: 100%; - color: inherit; - font: inherit; - ${({ theme, $hasStartContent, $hasEndContent }) => ` - padding: ${theme.click.field.space.y} 0; - padding-left: ${$hasStartContent ? "0" : theme.click.field.space.x}; - padding-right: ${$hasEndContent ? "0" : theme.click.field.space.x}; - &::placeholder { - color: ${theme.click.field.color.placeholder.default}; - } - - &:disabled, &.disabled { - &::placeholder { - color: ${theme.click.field.color.placeholder.disabled}; - } - `} -`; - -export const NumberInputElement = styled(InputElement)<{ $hideControls?: boolean }>` - ${({ $hideControls }) => ` - ${ - $hideControls - ? ` - &::-webkit-outer-spin-button, - &::-webkit-inner-spin-button { - -webkit-appearance: none; - margin: 0; - } - - -moz-appearance: textfield; - ` - : "" - } - `} -`; - -export const TextAreaElement = styled.textarea` - background: transparent; - border: none; - outline: none; - width: 100%; - color: inherit; - font: inherit; - resize: none; - ${({ theme }) => ` - padding: ${theme.click.field.space.y} ${theme.click.field.space.x}; - align-self: stretch; - &::placeholder { - color: ${theme.click.field.color.placeholder.default}; - } - `} -`; +export interface InputElementProps extends React.InputHTMLAttributes { + hasStartContent?: boolean; + hasEndContent?: boolean; +} -export const IconButton = styled.button` - background: transparent; - color: inherit; - border: none; - padding: 0; - outline: none; - &:not(:disabled) { - cursor: pointer; +export const InputElement = React.forwardRef( + ({ hasStartContent, hasEndContent, className, ...props }, ref) => { + const paddingType = + hasStartContent && hasEndContent + ? "none" + : hasStartContent + ? "end" + : hasEndContent + ? "start" + : "both"; + + const paddingClass = `cuiPadding${capitalize(paddingType)}`; + + return ( + + ); } - ${({ theme }) => ` - padding: ${theme.click.field.space.y} 0; - `} -`; +); -export const IconWrapper = styled.svg` - ${({ theme }) => ` - &:first-of-type { - padding-left: ${theme.click.field.space.gap}; - } - &:last-of-type { - padding-right: ${theme.click.field.space.x}; - } - `} -`; - -export const InputStartContent = styled.div` - ${({ theme }) => ` - padding-left: ${theme.click.field.space.x}; - cursor: text; - gap: ${theme.click.field.space.gap}; - display: flex; - align-self: stretch; - align-items: center; - `} -`; +export interface NumberInputElementProps extends InputElementProps { + hideControls?: boolean; +} -export const InputEndContent = styled.div` - ${({ theme }) => ` - padding-right: ${theme.click.field.space.x}; - gap: ${theme.click.field.space.gap}; - display: flex; - align-self: stretch; - align-items: center; - `} -`; +export const NumberInputElement = React.forwardRef< + HTMLInputElement, + NumberInputElementProps +>(({ hideControls, className, ...props }, ref) => ( + +)); + +export interface TextAreaElementProps + extends React.TextareaHTMLAttributes {} + +export const TextAreaElement = React.forwardRef< + HTMLTextAreaElement, + TextAreaElementProps +>(({ className, ...props }, ref) => ( +