diff --git a/.github/workflows/deploy-storybook.yml b/.github/workflows/deploy-storybook.yml index 5cfdb3f..4813a9d 100644 --- a/.github/workflows/deploy-storybook.yml +++ b/.github/workflows/deploy-storybook.yml @@ -6,25 +6,24 @@ on: workflow_dispatch: permissions: - contents: read + contents: write pages: write id-token: write + packages: write concurrency: - group: pages + group: pages-${{ github.ref }} cancel-in-progress: true jobs: - build-and-deploy: + release: runs-on: ubuntu-latest - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} steps: - name: Checkout uses: actions/checkout@v4 with: - fetch-depth: 0 # semantic-release needs full history + fetch-depth: 0 + token: ${{ secrets.GH_TOKEN || secrets.GITHUB_TOKEN }} - name: Setup Node.js uses: actions/setup-node@v4 @@ -45,6 +44,35 @@ jobs: - name: Build package run: npm run build + - name: Release (semantic-release) + env: + GITHUB_TOKEN: ${{ secrets.GH_TOKEN || secrets.GITHUB_TOKEN }} + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + GIT_AUTHOR_NAME: github-actions[bot] + GIT_AUTHOR_EMAIL: github-actions[bot]@users.noreply.github.com + GIT_COMMITTER_NAME: github-actions[bot] + GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com + run: npx semantic-release + + deploy-storybook: + runs-on: ubuntu-latest + needs: release + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + + - name: Install dependencies + run: npm ci + - name: Build Storybook with base path run: npm run build-storybook:gh-pages env: @@ -64,9 +92,3 @@ jobs: - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v4 - - - name: Release (semantic-release) - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - run: npx semantic-release diff --git a/.storybook/preview.ts b/.storybook/preview.ts deleted file mode 100644 index 1bdfbb6..0000000 --- a/.storybook/preview.ts +++ /dev/null @@ -1,23 +0,0 @@ -import type { Preview } from '@storybook/react'; -import '../src/styles/tailwind.css'; - -const preview: Preview = { - parameters: { - actions: { argTypesRegex: '^on[A-Z].*' }, - backgrounds: { - default: 'light', - values: [ - { - name: 'light', - value: '#f9fafb', - }, - { - name: 'dark', - value: '#1f2937', - }, - ], - }, - }, -}; - -export default preview; \ No newline at end of file diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx new file mode 100644 index 0000000..ab175a7 --- /dev/null +++ b/.storybook/preview.tsx @@ -0,0 +1,56 @@ +import type { Decorator, Preview } from '@storybook/react' +import React, { useEffect } from 'react' +import '../src/styles/tailwind.css' +/** + * Decorator for dynamic change theme (light/dark) + */ +const ThemeDecorator: Decorator = (Story, context) => { + const selectedBackground = context.globals.backgrounds?.value + const backgroundValues = context.parameters.backgrounds?.values || [] + const defaultBackground = context.parameters.backgrounds?.default + + useEffect(() => { + let themeName: string + + if (!selectedBackground) { + themeName = defaultBackground + } else if (selectedBackground.startsWith('#')) { + const currentBg = backgroundValues.find( + (bg: any) => bg.value === selectedBackground + ) + themeName = currentBg?.name || defaultBackground + } else { + themeName = selectedBackground + } + + if (themeName === 'dark') { + document.documentElement.classList.add('dark') + } else { + document.documentElement.classList.remove('dark') + } + }, [selectedBackground, backgroundValues, defaultBackground]) + + return +} + +const preview: Preview = { + decorators: [ThemeDecorator], + parameters: { + actions: { argTypesRegex: '^on[A-Z].*' }, + backgrounds: { + default: 'light', + values: [ + { + name: 'light', + value: '#f9fafb', + }, + { + name: 'dark', + value: '#1f2937', + }, + ], + }, + }, +} + +export default preview diff --git a/README.md b/README.md index 716da64..6344fb2 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ A comprehensive React design system kit with color picker, specialized input com - 🎨 **Multiple Color Formats**: Support for RGB, HSL, HSV, CMYK, and HEX - 🌈 **Gradient Support**: Create and edit linear and radial gradients - 🎯 **Eye Dropper**: Pick colors directly from the screen -- 🔢 **13 Specialized Input Components**: Drag-to-change numeric inputs for design properties +- 🔢 **Universal Input Component**: Drag-to-change numeric input for all design properties - 🎛️ **Multiple Progression Types**: Linear, arithmetic, geometric, paraboloid, exponential - 🌓 **Dark/Light Mode**: Automatic theme detection with manual override - 📦 **Modular Architecture**: Import only what you need for optimal bundle size @@ -63,31 +63,56 @@ function App() { } ``` -[📖 Full ColorPicker Documentation](./src/color-picker/README.md) - ### InputNumberSelect (3 KB) +Universal drag-to-change numeric input component for all design properties. Supports custom icons, units, precision, and multiple progression types. + ```tsx import { useState } from 'react' import { InputNumberSelect } from '@flowscape-ui/design-system-kit/input-number-select' +import { RotateCw } from 'lucide-react' function App() { - const [value, setValue] = useState(50) + const [opacity, setOpacity] = useState(75) + const [angle, setAngle] = useState(45) return ( - + <> + {/* Opacity control */} + + + {/* Angle control with custom icon */} + } + /> + ) } ``` -[📖 Full InputNumberSelect Documentation](./src/input-number-select/README.md) +**Key Features:** + +- 🎯 Drag-to-change with mouse/keyboard support +- 🎨 Custom icons (string or React components) +- 📊 5 progression types (linear, arithmetic, geometric, paraboloid, exponential) +- 🌓 Automatic dark/light theme support via Tailwind CSS +- 📝 Unit display (px, %, rem, em, deg, etc.) +- 🔄 Horizontal/vertical orientation +- ⌨️ Keyboard navigation (Arrow keys, Page Up/Down, Home/End) ### InputColorPicker (62 KB) @@ -108,137 +133,124 @@ function App() { } ``` -[📖 Full InputColorPicker Documentation](./src/input-color-picker/README.md) - -### Specialized Input Components +### InputNumberSelect - Usage Examples -A collection of 13 specialized input components for design properties: +One universal component for all design properties. Configure it through props: ```tsx -import { - OpacityInput, - AngleInput, - BorderRadiusInput, - BorderRadiusMultiInput, - WidthInput, - HeightInput, - SpacingInput, - FontSizeInput, - LineHeightInput, - LetterSpacingInput, - ZIndexInput, - ScaleInput, - BlurInput, -} from '@flowscape-ui/design-system-kit/inputs' - -// Or import individual components -import { OpacityInput } from '@flowscape-ui/design-system-kit/inputs/opacity-input' -``` - -#### Usage Examples: - -**OpacityInput** - Control opacity (0-100%) - -```tsx - -``` - -**AngleInput** - Control rotation angle (0-360°) - -```tsx - -``` - -**BorderRadiusInput** - Border radius control - -```tsx - -``` - -**BorderRadiusMultiInput** - Control all 4 corners - -```tsx - -``` - -**WidthInput / HeightInput** - Element dimensions - -```tsx - - -``` - -**SpacingInput** - Spacing and gaps +import { InputNumberSelect } from '@flowscape-ui/design-system-kit/input-number-select' +import { RotateCw, SquareRoundCorner, Type, Blend } from 'lucide-react' + +// Opacity (0-100%) +} +/> -```tsx - -``` +// Angle (0-360°) +} +/> -**FontSizeInput** - Font size control +// Border Radius with units +} +/> -```tsx - -``` +// Font Size +} +/> -**LineHeightInput** - Line height control +// Line Height (unitless) + -```tsx - +// Vertical orientation + ``` -**LetterSpacingInput** - Letter spacing control +### InputNumberSelect Props ```tsx - -``` - -**ZIndexInput** - Z-index layering +interface InputNumberSelectProps { + // Value control + value: number + onChange?: (value: number) => void -```tsx - -``` + // Range configuration + min?: number // Default: 0 + max?: number // Default: 100 + step?: number // Default: 1 + precision?: number // Decimal places, default: 0 -**ScaleInput** - Element scaling + // Progression type for dragging + progression?: 'linear' | 'arithmetic' | 'geometric' | 'paraboloid' | 'exponential' -```tsx - -``` + // Visual configuration + orientation?: 'horizontal' | 'vertical' // Default: 'horizontal' + icon?: React.ReactNode | string // Custom icon or text -**BlurInput** - Blur effect + // Unit display + unit?: 'px' | '%' | 'rem' | 'em' | 'deg' | 'none' + showUnit?: boolean // Show unit after value -```tsx - -``` + // Styling + className?: string // Container classes + classNameInput?: string // Input field classes + classNameIcon?: string // Icon container classes -#### Common Input Props: + // State + disabled?: boolean // Disable component + isDisabledMouseEvent?: boolean // Disable drag functionality -```tsx -interface BaseInputProps { - value: number - onChange?: (value: number) => void - min?: number - max?: number - step?: number - precision?: number - progression?: - | 'linear' - | 'arithmetic' - | 'geometric' - | 'paraboloid' - | 'exponential' - orientation?: 'horizontal' | 'vertical' - unit?: 'px' | '%' | 'rem' | 'em' | 'deg' | 'pt' | 'none' - showUnit?: boolean - icon?: React.ReactNode | string - className?: string - classNameInput?: string - classNameIcon?: string - theme?: 'light' | 'dark' | 'auto' - disabled?: boolean + // HTML input props + ...HTMLInputElement // All standard input props } ``` -#### Progression Types: +### Progression Types - **linear** - Linear change (default) - **arithmetic** - Arithmetic progression (×2) @@ -565,11 +577,14 @@ See [CHANGELOG.md](CHANGELOG.md) for a detailed list of changes. - 🐛 **Bug fixes**: NaN% in gradients, opacity validation - 📚 **Enhanced documentation** and Storybook examples -**New Components:** +**Key Features:** -- OpacityInput, AngleInput, BorderRadiusInput, BorderRadiusMultiInput -- WidthInput, HeightInput, SpacingInput, FontSizeInput -- LineHeightInput, LetterSpacingInput, ZIndexInput, ScaleInput, BlurInput +- Universal InputNumberSelect component for all design properties +- Drag-to-change functionality with 5 progression types +- Custom icons support (string or React components) +- Automatic dark/light theme via Tailwind CSS +- Horizontal and vertical orientation +- Full keyboard navigation support ### v1.0.0 diff --git a/src/index.ts b/src/index.ts index 1e02c1d..1b1fba4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,44 +5,5 @@ export * from './input-color-picker' export * from './input-number-select' export * from './shared' -// Re-export inputs with types -export { - AngleInput, - BlurInput, - BorderRadiusInput, - BorderRadiusMultiInput, - FontSizeInput, - HeightInput, - LetterSpacingInput, - LineHeightInput, - OpacityInput, - ScaleInput, - SpacingInput, - WidthInput, - ZIndexInput, -} from './inputs' - -export type { - AngleInputProps, - BaseInputProps, - BaseInputPropsWithoutUnit, - BlurInputProps, - BorderRadiusInputProps, - BorderRadiusMultiInputProps, - CommonInputProps, - FontSizeInputProps, - HeightInputProps, - LetterSpacingInputProps, - LineHeightInputProps, - OpacityInputProps, - Orientation, - Progression, - ScaleInputProps, - SpacingInputProps, - Unit, - WidthInputProps, - ZIndexInputProps, -} from './inputs' - // Default export for backward compatibility export { ColorPicker as default } from './color-picker' diff --git a/src/input-number-select/index.tsx b/src/input-number-select/index.tsx index 6af0a0e..2fff55a 100644 --- a/src/input-number-select/index.tsx +++ b/src/input-number-select/index.tsx @@ -1,2 +1,7 @@ export { InputNumberSelect } from './input-number-select' -export type { InputNumberSelectProps, Progression } from './input-number-select' +export type { + InputNumberSelectProps, + Orientation, + Progression, + Unit, +} from './input-number-select' diff --git a/src/input-number-select/input-number-select.tsx b/src/input-number-select/input-number-select.tsx index 770c4c5..e28738d 100644 --- a/src/input-number-select/input-number-select.tsx +++ b/src/input-number-select/input-number-select.tsx @@ -1,30 +1,58 @@ -import React, { useRef } from 'react' +import React, { useEffect, useMemo, useRef, useState } from 'react' import { FaRegDotCircle } from 'react-icons/fa' import { Input } from '../input' import { cn } from '../shared/utils/cn' import { removeTrailingZeros } from '../shared/utils/remove-trailing-zeros' +export type Unit = 'px' | '%' | 'rem' | 'em' | 'deg' | 'none' + +export type Orientation = 'horizontal' | 'vertical' + export type Progression = | 'linear' | 'arithmetic' | 'geometric' | 'paraboloid' | 'exponential' - | 'logarithmic' export interface InputNumberSelectProps - extends Omit, 'defaultValue' | 'onChange'> { - icon?: React.JSX.Element | string - orientation?: 'horizontal' | 'vertical' + extends Omit< + React.ComponentProps<'input'>, + 'defaultValue' | 'onChange' | 'value' | 'type' | 'min' | 'max' | 'step' + > { + value: number + onChange?: (value: number) => void + min?: number + max?: number step?: number precision?: number progression?: Progression + orientation?: Orientation + unit?: Unit + showUnit?: boolean + icon?: React.JSX.Element | string + className?: string classNameInput?: string - value: number | string - onChange?: (value: number | string) => void + classNameIcon?: string isDisabledMouseEvent?: boolean } +const THEME_CLASSES = { + light: { + container: 'bg-white border-gray-300 focus-within:ring-blue-500', + input: 'text-gray-900', + icon: 'text-gray-600', + dragArea: 'bg-gray-100 hover:bg-gray-200', + }, + dark: { + container: + 'dark:bg-gray-800 dark:border-gray-600 dark:focus-within:ring-blue-400', + input: 'dark:text-gray-100', + icon: 'dark:text-gray-300', + dragArea: 'dark:bg-gray-700 dark:hover:bg-gray-600', + }, +} as const + export const InputNumberSelect = React.forwardRef< HTMLInputElement, InputNumberSelectProps @@ -34,52 +62,250 @@ export const InputNumberSelect = React.forwardRef< min, max, icon, - orientation = 'horizontal', className, classNameInput, + classNameIcon, progression = 'linear', step = 0, precision = 0, - value, + value: propsValue, onChange, + orientation = 'horizontal', + unit = 'none', + showUnit = true, + disabled, + isDisabledMouseEvent = false, ...props }, ref ) => { - const { disabled } = props - const dragRef = useRef(null) - const startValueRef = useRef(value === '' ? 0 : Number(value)) - - const handleMouseDown = () => { - startValueRef.current = value === '' ? 0 : Number(value) - if (!disabled) { - document.addEventListener('mousemove', handleMouseMove) - document.addEventListener('mouseup', handleMouseUp) + const dragRef = useRef(null) + const [inputValue, setInputValue] = useState(propsValue) + const animationFrameId = useRef(null) + + useEffect(() => { + setInputValue(propsValue) + }, [propsValue]) + + const clampValue = (value: number): number => { + const minNum = min !== undefined ? +min : -Infinity + const maxNum = max !== undefined ? +max : Infinity + return Math.max(minNum, Math.min(maxNum, value)) + } + + const effectiveStep = step === 0 ? 1 : step + + const handleChange = (newValue: number | string) => { + const numericValue = Number(newValue) + setInputValue(newValue) + if (!isNaN(numericValue) && String(newValue).slice(-1) !== '.') { + onChange?.(clampValue(numericValue)) } } - const handleMouseUp = () => { - if (!disabled) { - document.removeEventListener('mousemove', handleMouseMove) - document.removeEventListener('mouseup', handleMouseUp) + const handlePointerDown = (e: React.PointerEvent) => { + if (disabled || isDisabledMouseEvent) return + e.preventDefault() + + const target = e.currentTarget + target.setPointerCapture(e.pointerId) + + const styleElement = document.createElement('style') + styleElement.id = 'dragging-cursor-style' + const cursorType = orientation === 'vertical' ? 'ns-resize' : 'ew-resize' + styleElement.innerHTML = ` + body, body * { + cursor: ${cursorType} !important; + user-select: none !important; + } + ` + document.head.appendChild(styleElement) + + const startX = e.clientX + const startY = e.clientY + const startValue = inputValue === '' ? 0 : Number(inputValue) + + const autoIncrement = (direction: number) => { + setInputValue((currentVal: number | string) => { + const numValue = + currentVal === '' || Number.isNaN(Number(currentVal)) + ? 0 + : Number(currentVal) + const newValue = clampValue(numValue + direction * effectiveStep) + + const formattedNewValue = removeTrailingZeros(newValue, precision) + onChange?.(Number(formattedNewValue)) + return formattedNewValue + }) + + animationFrameId.current = requestAnimationFrame(() => + autoIncrement(direction) + ) + } + + const stopAutoIncrement = () => { + if (animationFrameId.current) { + cancelAnimationFrame(animationFrameId.current) + animationFrameId.current = null + } + } + + const handlePointerMove = (event: PointerEvent) => { + if (orientation === 'vertical') { + // Вертикальная ориентация - движение по оси Y + const atTopEdge = event.clientY <= 1 + const atBottomEdge = event.clientY >= window.innerHeight - 1 + + if (atTopEdge || atBottomEdge) { + if (!animationFrameId.current) { + autoIncrement(atTopEdge ? 1 : -1) + } + } else { + stopAutoIncrement() + const totalDeltaY = startY - event.clientY // Инвертируем: вверх = + + const delta = totalDeltaY * effectiveStep + let newValue = calculateByProgression( + startValue, + delta, + progression + ) + + newValue = clampValue(newValue) + + handleChange(removeTrailingZeros(newValue, precision)) + } + } else { + // Горизонтальная ориентация - движение по оси X + const atLeftEdge = event.clientX <= 1 + const atRightEdge = event.clientX >= window.innerWidth - 1 + + if (atLeftEdge || atRightEdge) { + if (!animationFrameId.current) { + autoIncrement(atLeftEdge ? -1 : 1) + } + } else { + stopAutoIncrement() + const totalDeltaX = event.clientX - startX + const delta = totalDeltaX * effectiveStep + let newValue = calculateByProgression( + startValue, + delta, + progression + ) + + newValue = clampValue(newValue) + + handleChange(removeTrailingZeros(newValue, precision)) + } + } + } + + const handlePointerUp = (event: PointerEvent) => { + target.releasePointerCapture(event.pointerId) + stopAutoIncrement() + + const styleToRemove = document.getElementById('dragging-cursor-style') + if (styleToRemove) { + styleToRemove.remove() + } + target.removeEventListener('pointermove', handlePointerMove) + document.removeEventListener('pointerup', handlePointerUp) } + + target.addEventListener('pointermove', handlePointerMove) + document.addEventListener('pointerup', handlePointerUp) } - const handleMouseMove = (event: MouseEvent) => { - const movement = - orientation === 'vertical' ? -event.movementY : event.movementX - const delta: number = movement * step - let newValue = calculateByProgression( - startValueRef.current, - delta, - progression - ) + const handleKeyDown = (e: React.KeyboardEvent) => { + const { key, currentTarget } = e + const { value, selectionStart, selectionEnd } = currentTarget + + // Allow functional keys and shortcuts + if ( + e.metaKey || + e.ctrlKey || + [ + 'Backspace', + 'Delete', + 'Tab', + 'Escape', + 'Enter', + 'ArrowLeft', + 'ArrowRight', + 'Home', + 'End', + ].includes(key) + ) { + return + } - if (min !== undefined && newValue < +min) newValue = +min - if (max !== undefined && newValue > +max) newValue = +max + // Handle precision for dot: prevent if precision is 0 or dot already exists + if (key === '.' && (precision === 0 || value.includes('.'))) { + e.preventDefault() + return + } + + // Handle precision for digits: prevent if decimal part length exceeds precision + const dotIndex = value.indexOf('.') + if ( + dotIndex > -1 && + /\d/.test(key) && + selectionStart !== null && + selectionEnd !== null && + selectionStart > dotIndex && // Cursor is after the dot + selectionStart === selectionEnd && // No text is selected + value.substring(dotIndex + 1).length >= precision // Decimal part is already at max length + ) { + e.preventDefault() + return + } + + // Block subsequent minus signs or minus signs not at the start + if ( + key === '-' && + (value.includes('-') || (selectionStart !== null && selectionStart > 0)) + ) { + e.preventDefault() + return + } + + // Block any other non-digit characters + if (!/[\d.-]/.test(key)) { + e.preventDefault() + } + } + + const handleInputChange = (e: React.ChangeEvent) => { + let inputValue = e.target.value + const numberRegex = /^-?(\d+(\.\d*)?|\.\d*|)$/ + + if (!numberRegex.test(inputValue)) { + return + } + + // Enforce precision on paste + const dotIndex = inputValue.indexOf('.') + if (dotIndex > -1) { + if (precision <= 0) { + inputValue = inputValue.split('.')[0] ?? '' + } else { + const decimalPart = inputValue.substring(dotIndex + 1) + if (decimalPart.length > precision) { + inputValue = inputValue.substring(0, dotIndex + 1 + precision) + } + } + } + + // Validation min/max + const numValue = Number(inputValue) + if (!isNaN(numValue) && inputValue !== '' && !inputValue.endsWith('.')) { + const clampedValue = clampValue(numValue) + if (clampedValue !== numValue) { + inputValue = String(clampedValue) + } + } - startValueRef.current = newValue - onChange?.(removeTrailingZeros(newValue, precision)) + handleChange(inputValue) } const calculateByProgression = ( @@ -96,11 +322,6 @@ export const InputNumberSelect = React.forwardRef< return value + delta * Math.abs(delta) * 0.1 case 'exponential': return value * (1 + delta * 0.01) - case 'logarithmic': { - // Logarithmic progression: change is proportional to log of current value - const logFactor = Math.log10(Math.max(Math.abs(value), 1) + 1) - return value + delta * logFactor - } case 'geometric': { const factor = 1.05 if (delta > 0) { @@ -116,44 +337,33 @@ export const InputNumberSelect = React.forwardRef< } } - const handleInputChange = (e: React.ChangeEvent) => { - const rawValue = e.target.value - if (rawValue === '') { - onChange?.('') - return - } - let numericValue = +rawValue - if (max !== undefined && numericValue > +max) { - numericValue = +max - } - onChange?.(numericValue) - } - - const handleBlur = (e: React.FocusEvent) => { - const rawValue = e.target.value - let numericValue: number | string = rawValue === '' ? '' : +rawValue - - if (numericValue === '' || isNaN(numericValue as number)) { - if (min !== undefined) { - onChange?.(+min) - } else { - onChange?.('') - } - return - } - - if (typeof numericValue === 'number') { - if (min !== undefined && numericValue < +min) { - numericValue = +min - } - onChange?.(numericValue) + const renderIcon = useMemo(() => { + if (icon) { + return ( + + {icon} + + ) } - } + return ( + + ) + }, [icon]) return (
-
- {typeof icon === 'string' && icon.length > 0 ? ( - - {icon.charAt(0)} - - ) : React.isValidElement(icon) ? ( - icon - ) : ( - - )} -
+ {renderIcon} + + + {showUnit && unit !== 'none' && ( +
+ {unit} +
+ )}
) } diff --git a/src/inputs/angle-input/index.tsx b/src/inputs/angle-input/index.tsx deleted file mode 100644 index b7a89fe..0000000 --- a/src/inputs/angle-input/index.tsx +++ /dev/null @@ -1,131 +0,0 @@ -import { RotateCw } from 'lucide-react' -import React, { useMemo } from 'react' -import { Input } from '../../input' -import { cn } from '../../shared/utils/cn' -import { - THEME_CLASSES, - useNumberInput, - type BaseInputPropsWithoutUnit, -} from '../shared' - -export type AngleInputProps = BaseInputPropsWithoutUnit - -export const AngleInput = React.memo( - React.forwardRef( - ( - { - value, - onChange, - min = 0, - max = 360, - step = 1, - precision = 0, - progression = 'linear', - orientation = 'horizontal', - icon, - className, - classNameInput, - classNameIcon, - theme = 'auto', - disabled, - ...props - }, - ref - ) => { - const { handleMouseDown, handleInputChange, handleBlur } = useNumberInput( - { - value, - onChange, - min, - max, - step, - precision, - progression, - orientation, - disabled, - } - ) - - const isDark = useMemo(() => { - if (theme === 'dark') return true - if (theme === 'light') return false - return ( - typeof window !== 'undefined' && - window.matchMedia('(prefers-color-scheme: dark)').matches - ) - }, [theme]) - - const themeClasses = isDark ? THEME_CLASSES.dark : THEME_CLASSES.light - - const renderIcon = useMemo(() => { - if (icon) { - return typeof icon === 'string' ? ( - - {icon} - - ) : ( - icon - ) - } - return - }, [icon, themeClasses.icon]) - - return ( -
-
- {renderIcon} -
- - -
- ) - } - ) -) - -AngleInput.displayName = 'AngleInput' diff --git a/src/inputs/blur-input/index.tsx b/src/inputs/blur-input/index.tsx deleted file mode 100644 index fc0779c..0000000 --- a/src/inputs/blur-input/index.tsx +++ /dev/null @@ -1,140 +0,0 @@ -import { Droplets } from 'lucide-react' -import React, { useMemo } from 'react' -import { Input } from '../../input' -import { cn } from '../../shared/utils/cn' -import { THEME_CLASSES, useNumberInput, type BaseInputProps } from '../shared' - -export type BlurInputProps = BaseInputProps - -export const BlurInput = React.memo( - React.forwardRef( - ( - { - value, - onChange, - min = 0, - max = 100, - step = 1, - precision = 0, - progression = 'linear', - orientation = 'horizontal', - unit = 'px', - showUnit = true, - icon, - className, - classNameInput, - classNameIcon, - theme = 'auto', - disabled, - ...props - }, - ref - ) => { - const { handleMouseDown, handleInputChange, handleBlur } = useNumberInput( - { - value, - onChange, - min, - max, - step, - precision, - progression, - orientation, - disabled, - } - ) - - const isDark = useMemo(() => { - if (theme === 'dark') return true - if (theme === 'light') return false - return ( - typeof window !== 'undefined' && - window.matchMedia('(prefers-color-scheme: dark)').matches - ) - }, [theme]) - - const themeClasses = isDark ? THEME_CLASSES.dark : THEME_CLASSES.light - - const renderIcon = useMemo(() => { - if (icon) { - return typeof icon === 'string' ? ( - - {icon} - - ) : ( - icon - ) - } - return - }, [icon, themeClasses.icon]) - - return ( -
-
- {renderIcon} -
- - - - {showUnit && unit !== 'none' && ( -
- {unit} -
- )} -
- ) - } - ) -) - -BlurInput.displayName = 'BlurInput' diff --git a/src/inputs/border-radius-input/index.tsx b/src/inputs/border-radius-input/index.tsx deleted file mode 100644 index c6cb000..0000000 --- a/src/inputs/border-radius-input/index.tsx +++ /dev/null @@ -1,140 +0,0 @@ -import { Circle } from 'lucide-react' -import React, { useMemo } from 'react' -import { Input } from '../../input' -import { cn } from '../../shared/utils/cn' -import { THEME_CLASSES, useNumberInput, type BaseInputProps } from '../shared' - -export type BorderRadiusInputProps = BaseInputProps - -export const BorderRadiusInput = React.memo( - React.forwardRef( - ( - { - value, - onChange, - min = 0, - max = 999, - step = 1, - precision = 0, - progression = 'linear', - orientation = 'horizontal', - unit = 'px', - showUnit = true, - icon, - className, - classNameInput, - classNameIcon, - theme = 'auto', - disabled, - ...props - }, - ref - ) => { - const { handleMouseDown, handleInputChange, handleBlur } = useNumberInput( - { - value, - onChange, - min, - max, - step, - precision, - progression, - orientation, - disabled, - } - ) - - const isDark = useMemo(() => { - if (theme === 'dark') return true - if (theme === 'light') return false - return ( - typeof window !== 'undefined' && - window.matchMedia('(prefers-color-scheme: dark)').matches - ) - }, [theme]) - - const themeClasses = isDark ? THEME_CLASSES.dark : THEME_CLASSES.light - - const renderIcon = useMemo(() => { - if (icon) { - return typeof icon === 'string' ? ( - - {icon} - - ) : ( - icon - ) - } - return - }, [icon, themeClasses.icon]) - - return ( -
-
- {renderIcon} -
- - - - {showUnit && unit !== 'none' && ( -
- {unit} -
- )} -
- ) - } - ) -) - -BorderRadiusInput.displayName = 'BorderRadiusInput' diff --git a/src/inputs/border-radius-multi-input/index.tsx b/src/inputs/border-radius-multi-input/index.tsx deleted file mode 100644 index afdd610..0000000 --- a/src/inputs/border-radius-multi-input/index.tsx +++ /dev/null @@ -1,143 +0,0 @@ -import { Link, Unlink } from 'lucide-react' -import React, { useCallback, useMemo, useState } from 'react' -import { cn } from '../../shared/utils/cn' -import { BorderRadiusInput } from '../border-radius-input' -import { THEME_CLASSES } from '../shared' - -export interface BorderRadiusMultiInputProps { - value?: [number, number, number, number] - onChange?: (value: [number, number, number, number]) => void - min?: number - max?: number - step?: number - unit?: 'px' | '%' | 'rem' - className?: string - theme?: 'light' | 'dark' | 'auto' - disabled?: boolean -} - -export const BorderRadiusMultiInput = React.memo( - ({ - value = [0, 0, 0, 0], - onChange, - min = 0, - max = 999, - step = 1, - unit = 'px', - className, - theme = 'auto', - disabled = false, - }) => { - const [isLinked, setIsLinked] = useState(true) - - const isDark = useMemo(() => { - if (theme === 'dark') return true - if (theme === 'light') return false - return ( - typeof window !== 'undefined' && - window.matchMedia('(prefers-color-scheme: dark)').matches - ) - }, [theme]) - - const themeClasses = isDark ? THEME_CLASSES.dark : THEME_CLASSES.light - - const handleChange = useCallback( - (index: number) => (newValue: number) => { - if (isNaN(newValue)) return - - if (isLinked) { - onChange?.([newValue, newValue, newValue, newValue]) - } else { - const newValues: [number, number, number, number] = [...value] - newValues[index] = newValue - onChange?.(newValues) - } - }, - [isLinked, value, onChange] - ) - - return ( -
-
-
- - - - -
- - -
- -
- {unit} -
-
- ) - } -) - -BorderRadiusMultiInput.displayName = 'BorderRadiusMultiInput' diff --git a/src/inputs/font-size-input/index.tsx b/src/inputs/font-size-input/index.tsx deleted file mode 100644 index b82c832..0000000 --- a/src/inputs/font-size-input/index.tsx +++ /dev/null @@ -1,140 +0,0 @@ -import { Type } from 'lucide-react' -import React, { useMemo } from 'react' -import { Input } from '../../input' -import { cn } from '../../shared/utils/cn' -import { THEME_CLASSES, useNumberInput, type BaseInputProps } from '../shared' - -export type FontSizeInputProps = BaseInputProps - -export const FontSizeInput = React.memo( - React.forwardRef( - ( - { - value, - onChange, - min = 8, - max = 200, - step = 1, - precision = 0, - progression = 'linear', - orientation = 'horizontal', - unit = 'px', - showUnit = true, - icon, - className, - classNameInput, - classNameIcon, - theme = 'auto', - disabled, - ...props - }, - ref - ) => { - const { handleMouseDown, handleInputChange, handleBlur } = useNumberInput( - { - value, - onChange, - min, - max, - step, - precision, - progression, - orientation, - disabled, - } - ) - - const isDark = useMemo(() => { - if (theme === 'dark') return true - if (theme === 'light') return false - return ( - typeof window !== 'undefined' && - window.matchMedia('(prefers-color-scheme: dark)').matches - ) - }, [theme]) - - const themeClasses = isDark ? THEME_CLASSES.dark : THEME_CLASSES.light - - const renderIcon = useMemo(() => { - if (icon) { - return typeof icon === 'string' ? ( - - {icon} - - ) : ( - icon - ) - } - return - }, [icon, themeClasses.icon]) - - return ( -
-
- {renderIcon} -
- - - - {showUnit && unit !== 'none' && ( -
- {unit} -
- )} -
- ) - } - ) -) - -FontSizeInput.displayName = 'FontSizeInput' diff --git a/src/inputs/height-input/index.tsx b/src/inputs/height-input/index.tsx deleted file mode 100644 index a730ffa..0000000 --- a/src/inputs/height-input/index.tsx +++ /dev/null @@ -1,140 +0,0 @@ -import { ArrowUpDown } from 'lucide-react' -import React, { useMemo } from 'react' -import { Input } from '../../input' -import { cn } from '../../shared/utils/cn' -import { THEME_CLASSES, useNumberInput, type BaseInputProps } from '../shared' - -export type HeightInputProps = BaseInputProps - -export const HeightInput = React.memo( - React.forwardRef( - ( - { - value, - onChange, - min = 0, - max = 9999, - step = 1, - precision = 0, - progression = 'linear', - orientation = 'horizontal', - unit = 'px', - showUnit = true, - icon, - className, - classNameInput, - classNameIcon, - theme = 'auto', - disabled, - ...props - }, - ref - ) => { - const { handleMouseDown, handleInputChange, handleBlur } = useNumberInput( - { - value, - onChange, - min, - max, - step, - precision, - progression, - orientation, - disabled, - } - ) - - const isDark = useMemo(() => { - if (theme === 'dark') return true - if (theme === 'light') return false - return ( - typeof window !== 'undefined' && - window.matchMedia('(prefers-color-scheme: dark)').matches - ) - }, [theme]) - - const themeClasses = isDark ? THEME_CLASSES.dark : THEME_CLASSES.light - - const renderIcon = useMemo(() => { - if (icon) { - return typeof icon === 'string' ? ( - - {icon} - - ) : ( - icon - ) - } - return - }, [icon, themeClasses.icon]) - - return ( -
-
- {renderIcon} -
- - - - {showUnit && unit !== 'none' && ( -
- {unit} -
- )} -
- ) - } - ) -) - -HeightInput.displayName = 'HeightInput' diff --git a/src/inputs/index.ts b/src/inputs/index.ts deleted file mode 100644 index 587d08b..0000000 --- a/src/inputs/index.ts +++ /dev/null @@ -1,49 +0,0 @@ -// Shared types and utilities -export type { - Unit, - Orientation, - Progression, - CommonInputProps, - BaseInputProps, - BaseInputPropsWithoutUnit, -} from './shared' - -// Input components -export { OpacityInput } from './opacity-input' -export type { OpacityInputProps } from './opacity-input' - -export { AngleInput } from './angle-input' -export type { AngleInputProps } from './angle-input' - -export { BorderRadiusInput } from './border-radius-input' -export type { BorderRadiusInputProps } from './border-radius-input' - -export { BorderRadiusMultiInput } from './border-radius-multi-input' -export type { BorderRadiusMultiInputProps } from './border-radius-multi-input' - -export { WidthInput } from './width-input' -export type { WidthInputProps } from './width-input' - -export { HeightInput } from './height-input' -export type { HeightInputProps } from './height-input' - -export { SpacingInput } from './spacing-input' -export type { SpacingInputProps } from './spacing-input' - -export { FontSizeInput } from './font-size-input' -export type { FontSizeInputProps } from './font-size-input' - -export { LineHeightInput } from './line-height-input' -export type { LineHeightInputProps } from './line-height-input' - -export { LetterSpacingInput } from './letter-spacing-input' -export type { LetterSpacingInputProps } from './letter-spacing-input' - -export { ZIndexInput } from './z-index-input' -export type { ZIndexInputProps } from './z-index-input' - -export { ScaleInput } from './scale-input' -export type { ScaleInputProps } from './scale-input' - -export { BlurInput } from './blur-input' -export type { BlurInputProps } from './blur-input' diff --git a/src/inputs/letter-spacing-input/index.tsx b/src/inputs/letter-spacing-input/index.tsx deleted file mode 100644 index 93a9f96..0000000 --- a/src/inputs/letter-spacing-input/index.tsx +++ /dev/null @@ -1,140 +0,0 @@ -import { CaseSensitive } from 'lucide-react' -import React, { useMemo } from 'react' -import { Input } from '../../input' -import { cn } from '../../shared/utils/cn' -import { THEME_CLASSES, useNumberInput, type BaseInputProps } from '../shared' - -export type LetterSpacingInputProps = BaseInputProps - -export const LetterSpacingInput = React.memo( - React.forwardRef( - ( - { - value, - onChange, - min = -10, - max = 50, - step = 0.1, - precision = 1, - progression = 'linear', - orientation = 'horizontal', - unit = 'px', - showUnit = true, - icon, - className, - classNameInput, - classNameIcon, - theme = 'auto', - disabled, - ...props - }, - ref - ) => { - const { handleMouseDown, handleInputChange, handleBlur } = useNumberInput( - { - value, - onChange, - min, - max, - step, - precision, - progression, - orientation, - disabled, - } - ) - - const isDark = useMemo(() => { - if (theme === 'dark') return true - if (theme === 'light') return false - return ( - typeof window !== 'undefined' && - window.matchMedia('(prefers-color-scheme: dark)').matches - ) - }, [theme]) - - const themeClasses = isDark ? THEME_CLASSES.dark : THEME_CLASSES.light - - const renderIcon = useMemo(() => { - if (icon) { - return typeof icon === 'string' ? ( - - {icon} - - ) : ( - icon - ) - } - return - }, [icon, themeClasses.icon]) - - return ( -
-
- {renderIcon} -
- - - - {showUnit && unit !== 'none' && ( -
- {unit} -
- )} -
- ) - } - ) -) - -LetterSpacingInput.displayName = 'LetterSpacingInput' diff --git a/src/inputs/line-height-input/index.tsx b/src/inputs/line-height-input/index.tsx deleted file mode 100644 index 4887cac..0000000 --- a/src/inputs/line-height-input/index.tsx +++ /dev/null @@ -1,133 +0,0 @@ -import { AlignVerticalSpaceAround } from 'lucide-react' -import React, { useMemo } from 'react' -import { Input } from '../../input' -import { cn } from '../../shared/utils/cn' -import { - THEME_CLASSES, - useNumberInput, - type BaseInputPropsWithoutUnit, -} from '../shared' - -export type LineHeightInputProps = BaseInputPropsWithoutUnit - -export const LineHeightInput = React.memo( - React.forwardRef( - ( - { - value, - onChange, - min = 0, - max = 10, - step = 0.1, - precision = 1, - progression = 'linear', - orientation = 'horizontal', - icon, - className, - classNameInput, - classNameIcon, - theme = 'auto', - disabled, - ...props - }, - ref - ) => { - const { handleMouseDown, handleInputChange, handleBlur } = useNumberInput( - { - value, - onChange, - min, - max, - step, - precision, - progression, - orientation, - disabled, - } - ) - - const isDark = useMemo(() => { - if (theme === 'dark') return true - if (theme === 'light') return false - return ( - typeof window !== 'undefined' && - window.matchMedia('(prefers-color-scheme: dark)').matches - ) - }, [theme]) - - const themeClasses = isDark ? THEME_CLASSES.dark : THEME_CLASSES.light - - const renderIcon = useMemo(() => { - if (icon) { - return typeof icon === 'string' ? ( - - {icon} - - ) : ( - icon - ) - } - return ( - - ) - }, [icon, themeClasses.icon]) - - return ( -
-
- {renderIcon} -
- - -
- ) - } - ) -) - -LineHeightInput.displayName = 'LineHeightInput' diff --git a/src/inputs/opacity-input/index.tsx b/src/inputs/opacity-input/index.tsx deleted file mode 100644 index 8d16c42..0000000 --- a/src/inputs/opacity-input/index.tsx +++ /dev/null @@ -1,118 +0,0 @@ -import React, { useMemo } from 'react' -import { Input } from '../../input' -import { cn } from '../../shared/utils/cn' -import { - THEME_CLASSES, - useNumberInput, - type BaseInputPropsWithoutUnit, -} from '../shared' - -export type OpacityInputProps = BaseInputPropsWithoutUnit - -export const OpacityInput = React.memo( - React.forwardRef( - ( - { - value, - onChange, - min = 0, - max = 100, - step = 1, - precision = 0, - progression = 'linear', - orientation = 'horizontal', - className, - classNameInput, - classNameIcon, - theme = 'auto', - disabled, - ...props - }, - ref - ) => { - const { handleMouseDown, handleInputChange, handleBlur } = useNumberInput( - { - value, - onChange, - min, - max, - step, - precision, - progression, - orientation, - disabled, - } - ) - - const isDark = useMemo(() => { - if (theme === 'dark') return true - if (theme === 'light') return false - return ( - typeof window !== 'undefined' && - window.matchMedia('(prefers-color-scheme: dark)').matches - ) - }, [theme]) - - const themeClasses = isDark ? THEME_CLASSES.dark : THEME_CLASSES.light - - return ( -
-
- - % - -
- - -
- ) - } - ) -) - -OpacityInput.displayName = 'OpacityInput' diff --git a/src/inputs/scale-input/index.tsx b/src/inputs/scale-input/index.tsx deleted file mode 100644 index ad3c359..0000000 --- a/src/inputs/scale-input/index.tsx +++ /dev/null @@ -1,131 +0,0 @@ -import { Maximize2 } from 'lucide-react' -import React, { useMemo } from 'react' -import { Input } from '../../input' -import { cn } from '../../shared/utils/cn' -import { - THEME_CLASSES, - useNumberInput, - type BaseInputPropsWithoutUnit, -} from '../shared' - -export type ScaleInputProps = BaseInputPropsWithoutUnit - -export const ScaleInput = React.memo( - React.forwardRef( - ( - { - value, - onChange, - min = 0, - max = 10, - step = 0.1, - precision = 1, - progression = 'linear', - orientation = 'horizontal', - icon, - className, - classNameInput, - classNameIcon, - theme = 'auto', - disabled, - ...props - }, - ref - ) => { - const { handleMouseDown, handleInputChange, handleBlur } = useNumberInput( - { - value, - onChange, - min, - max, - step, - precision, - progression, - orientation, - disabled, - } - ) - - const isDark = useMemo(() => { - if (theme === 'dark') return true - if (theme === 'light') return false - return ( - typeof window !== 'undefined' && - window.matchMedia('(prefers-color-scheme: dark)').matches - ) - }, [theme]) - - const themeClasses = isDark ? THEME_CLASSES.dark : THEME_CLASSES.light - - const renderIcon = useMemo(() => { - if (icon) { - return typeof icon === 'string' ? ( - - {icon} - - ) : ( - icon - ) - } - return - }, [icon, themeClasses.icon]) - - return ( -
-
- {renderIcon} -
- - -
- ) - } - ) -) - -ScaleInput.displayName = 'ScaleInput' diff --git a/src/inputs/shared/constants.ts b/src/inputs/shared/constants.ts deleted file mode 100644 index 4e09d49..0000000 --- a/src/inputs/shared/constants.ts +++ /dev/null @@ -1,45 +0,0 @@ -import type { Progression } from './types' - -export const THEME_CLASSES = { - light: { - container: 'bg-white border-gray-300 focus-within:ring-blue-500', - input: 'text-gray-900', - icon: 'text-gray-600', - dragArea: 'bg-gray-100 hover:bg-gray-200', - }, - dark: { - container: 'bg-gray-800 border-gray-600 focus-within:ring-blue-400', - input: 'text-gray-100', - icon: 'text-gray-300', - dragArea: 'bg-gray-700 hover:bg-gray-600', - }, -} as const - -export const calculateByProgression = ( - value: number, - delta: number, - progression?: Progression -): number => { - switch (progression) { - case 'linear': - return value + delta - case 'arithmetic': - return value + delta * 2 - case 'paraboloid': - return value + delta * Math.abs(delta) * 0.1 - case 'exponential': - return value * (1 + delta * 0.01) - case 'geometric': { - const factor = 1.05 - if (delta > 0) { - return (value + delta) * factor - } else if (delta < 0) { - return (value + delta) / factor - } else { - return value + delta - } - } - default: - return value + delta - } -} diff --git a/src/inputs/shared/index.ts b/src/inputs/shared/index.ts deleted file mode 100644 index 3113214..0000000 --- a/src/inputs/shared/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './types' -export * from './constants' -export * from './useNumberInput' diff --git a/src/inputs/shared/types.ts b/src/inputs/shared/types.ts deleted file mode 100644 index f489509..0000000 --- a/src/inputs/shared/types.ts +++ /dev/null @@ -1,44 +0,0 @@ -export type Unit = 'px' | '%' | 'rem' | 'em' | 'deg' | 'pt' | 'none' - -export type Orientation = 'horizontal' | 'vertical' - -export type Progression = - | 'linear' - | 'arithmetic' - | 'geometric' - | 'paraboloid' - | 'exponential' - -export interface CommonInputProps - extends Omit< - React.ComponentProps<'input'>, - | 'defaultValue' - | 'onChange' - | 'value' - | 'type' - | 'min' - | 'max' - | 'step' - | 'className' - > { - value: number - onChange?: (value: number) => void - min?: number - max?: number - step?: number - precision?: number - progression?: Progression - orientation?: Orientation - icon?: React.JSX.Element | string - className?: string - classNameInput?: string - classNameIcon?: string - theme?: 'light' | 'dark' | 'auto' -} - -export interface BaseInputProps extends CommonInputProps { - unit?: Unit - showUnit?: boolean -} - -export type BaseInputPropsWithoutUnit = CommonInputProps diff --git a/src/inputs/shared/useNumberInput.ts b/src/inputs/shared/useNumberInput.ts deleted file mode 100644 index 94b3188..0000000 --- a/src/inputs/shared/useNumberInput.ts +++ /dev/null @@ -1,121 +0,0 @@ -import { useCallback, useEffect, useRef, useState } from 'react' -import { removeTrailingZeros } from '../../shared/utils/remove-trailing-zeros' -import { calculateByProgression } from './constants' -import type { Orientation, Progression } from './types' - -interface UseNumberInputProps { - value: number - onChange?: (value: number) => void - min?: number - max?: number - step?: number - precision?: number - progression?: Progression - orientation?: Orientation - disabled?: boolean -} - -export const useNumberInput = ({ - value, - onChange, - min, - max, - step = 0, - precision = 0, - progression = 'linear', - orientation = 'horizontal', - disabled = false, -}: UseNumberInputProps) => { - const startValueRef = useRef(value) - const onChangeRef = useRef(onChange) - const [isDragging, setIsDragging] = useState(false) - - // Обновляем ref при каждом рендере - useEffect(() => { - onChangeRef.current = onChange - }, [onChange]) - - const handleMouseMove = useCallback( - (event: MouseEvent) => { - const movement = - orientation === 'vertical' ? -event.movementY : event.movementX - const delta: number = movement * step - let newValue = calculateByProgression( - startValueRef.current, - delta, - progression - ) - - if (min !== undefined && newValue < +min) newValue = +min - if (max !== undefined && newValue > +max) newValue = +max - - startValueRef.current = newValue - onChangeRef.current?.(removeTrailingZeros(newValue, precision)) - }, - [orientation, step, progression, min, max, precision] - ) - - const handleMouseUp = useCallback(() => { - setIsDragging(false) - document.removeEventListener('mousemove', handleMouseMove) - document.removeEventListener('mouseup', handleMouseUp) - }, [handleMouseMove]) - - const handleMouseDown = useCallback(() => { - if (disabled) return - startValueRef.current = value - setIsDragging(true) - document.addEventListener('mousemove', handleMouseMove) - document.addEventListener('mouseup', handleMouseUp) - }, [disabled, value, handleMouseMove, handleMouseUp]) - - const handleInputChange = useCallback( - (e: React.ChangeEvent) => { - const rawValue = e.target.value - if (rawValue === '') { - // Пустое поле - устанавливаем min или 0 - onChangeRef.current?.(min !== undefined ? +min : 0) - return - } - let numericValue = +rawValue - if (max !== undefined && numericValue > +max) { - numericValue = +max - } - onChangeRef.current?.(numericValue) - }, - [max, min] - ) - - const handleBlur = useCallback( - (e: React.FocusEvent) => { - const rawValue = e.target.value - let numericValue = - rawValue === '' ? (min !== undefined ? +min : 0) : +rawValue - - if (isNaN(numericValue)) { - onChangeRef.current?.(min !== undefined ? +min : 0) - return - } - - if (min !== undefined && numericValue < +min) { - numericValue = +min - } - onChangeRef.current?.(numericValue) - }, - [min] - ) - - useEffect(() => { - return () => { - document.removeEventListener('mousemove', handleMouseMove) - document.removeEventListener('mouseup', handleMouseUp) - } - }, [handleMouseMove, handleMouseUp]) - - return { - isDragging, - handleMouseDown, - handleInputChange, - handleBlur, - } -} diff --git a/src/inputs/spacing-input/index.tsx b/src/inputs/spacing-input/index.tsx deleted file mode 100644 index c894084..0000000 --- a/src/inputs/spacing-input/index.tsx +++ /dev/null @@ -1,140 +0,0 @@ -import { Space } from 'lucide-react' -import React, { useMemo } from 'react' -import { Input } from '../../input' -import { cn } from '../../shared/utils/cn' -import { THEME_CLASSES, useNumberInput, type BaseInputProps } from '../shared' - -export type SpacingInputProps = BaseInputProps - -export const SpacingInput = React.memo( - React.forwardRef( - ( - { - value, - onChange, - min = 0, - max = 999, - step = 1, - precision = 0, - progression = 'linear', - orientation = 'horizontal', - unit = 'px', - showUnit = true, - icon, - className, - classNameInput, - classNameIcon, - theme = 'auto', - disabled, - ...props - }, - ref - ) => { - const { handleMouseDown, handleInputChange, handleBlur } = useNumberInput( - { - value, - onChange, - min, - max, - step, - precision, - progression, - orientation, - disabled, - } - ) - - const isDark = useMemo(() => { - if (theme === 'dark') return true - if (theme === 'light') return false - return ( - typeof window !== 'undefined' && - window.matchMedia('(prefers-color-scheme: dark)').matches - ) - }, [theme]) - - const themeClasses = isDark ? THEME_CLASSES.dark : THEME_CLASSES.light - - const renderIcon = useMemo(() => { - if (icon) { - return typeof icon === 'string' ? ( - - {icon} - - ) : ( - icon - ) - } - return - }, [icon, themeClasses.icon]) - - return ( -
-
- {renderIcon} -
- - - - {showUnit && unit !== 'none' && ( -
- {unit} -
- )} -
- ) - } - ) -) - -SpacingInput.displayName = 'SpacingInput' diff --git a/src/inputs/width-input/index.tsx b/src/inputs/width-input/index.tsx deleted file mode 100644 index d0ff88e..0000000 --- a/src/inputs/width-input/index.tsx +++ /dev/null @@ -1,140 +0,0 @@ -import { ArrowLeftRight } from 'lucide-react' -import React, { useMemo } from 'react' -import { Input } from '../../input' -import { cn } from '../../shared/utils/cn' -import { THEME_CLASSES, useNumberInput, type BaseInputProps } from '../shared' - -export type WidthInputProps = BaseInputProps - -export const WidthInput = React.memo( - React.forwardRef( - ( - { - value, - onChange, - min = 0, - max = 9999, - step = 1, - precision = 0, - progression = 'linear', - orientation = 'horizontal', - unit = 'px', - showUnit = true, - icon, - className, - classNameInput, - classNameIcon, - theme = 'auto', - disabled, - ...props - }, - ref - ) => { - const { handleMouseDown, handleInputChange, handleBlur } = useNumberInput( - { - value, - onChange, - min, - max, - step, - precision, - progression, - orientation, - disabled, - } - ) - - const isDark = useMemo(() => { - if (theme === 'dark') return true - if (theme === 'light') return false - return ( - typeof window !== 'undefined' && - window.matchMedia('(prefers-color-scheme: dark)').matches - ) - }, [theme]) - - const themeClasses = isDark ? THEME_CLASSES.dark : THEME_CLASSES.light - - const renderIcon = useMemo(() => { - if (icon) { - return typeof icon === 'string' ? ( - - {icon} - - ) : ( - icon - ) - } - return - }, [icon, themeClasses.icon]) - - return ( -
-
- {renderIcon} -
- - - - {showUnit && unit !== 'none' && ( -
- {unit} -
- )} -
- ) - } - ) -) - -WidthInput.displayName = 'WidthInput' diff --git a/src/inputs/z-index-input/index.tsx b/src/inputs/z-index-input/index.tsx deleted file mode 100644 index 44aa07f..0000000 --- a/src/inputs/z-index-input/index.tsx +++ /dev/null @@ -1,131 +0,0 @@ -import { Layers } from 'lucide-react' -import React, { useMemo } from 'react' -import { Input } from '../../input' -import { cn } from '../../shared/utils/cn' -import { - THEME_CLASSES, - useNumberInput, - type BaseInputPropsWithoutUnit, -} from '../shared' - -export type ZIndexInputProps = BaseInputPropsWithoutUnit - -export const ZIndexInput = React.memo( - React.forwardRef( - ( - { - value, - onChange, - min = -999, - max = 9999, - step = 1, - precision = 0, - progression = 'linear', - orientation = 'horizontal', - icon, - className, - classNameInput, - classNameIcon, - theme = 'auto', - disabled, - ...props - }, - ref - ) => { - const { handleMouseDown, handleInputChange, handleBlur } = useNumberInput( - { - value, - onChange, - min, - max, - step, - precision, - progression, - orientation, - disabled, - } - ) - - const isDark = useMemo(() => { - if (theme === 'dark') return true - if (theme === 'light') return false - return ( - typeof window !== 'undefined' && - window.matchMedia('(prefers-color-scheme: dark)').matches - ) - }, [theme]) - - const themeClasses = isDark ? THEME_CLASSES.dark : THEME_CLASSES.light - - const renderIcon = useMemo(() => { - if (icon) { - return typeof icon === 'string' ? ( - - {icon} - - ) : ( - icon - ) - } - return - }, [icon, themeClasses.icon]) - - return ( -
-
- {renderIcon} -
- - -
- ) - } - ) -) - -ZIndexInput.displayName = 'ZIndexInput' diff --git a/src/stories/InputNumberSelect.stories.tsx b/src/stories/InputNumberSelect.stories.tsx new file mode 100644 index 0000000..2377807 --- /dev/null +++ b/src/stories/InputNumberSelect.stories.tsx @@ -0,0 +1,882 @@ +import type { Meta, StoryObj } from '@storybook/react' +import { + AlignVerticalSpaceAround, + ArrowLeftRight, + ArrowUpDown, + Blend, + Droplets, + Layers, + Link, + Maximize2, + RotateCw, + Space, + SquareRoundCorner, + Type, + Unlink, +} from 'lucide-react' +import React, { useState } from 'react' +import { InputNumberSelect } from '../input-number-select' + +const meta = { + title: 'Components/InputNumberSelect', + component: InputNumberSelect, + parameters: { + layout: 'centered', + docs: { + toc: { + title: 'Table of Contents', + }, + }, + backgrounds: { + default: 'light', + values: [ + { name: 'light', value: '#ffffff' }, + { name: 'dark', value: '#1a1a1a' }, + ], + }, + }, + tags: ['autodocs'], + argTypes: { + value: { + control: 'number', + description: 'Current value', + }, + min: { + control: 'number', + description: 'Minimum value', + }, + max: { + control: 'number', + description: 'Maximum value', + }, + step: { + control: 'number', + description: 'Step increment', + }, + precision: { + control: 'number', + description: 'Number of decimal places', + }, + progression: { + control: 'select', + options: [ + 'linear', + 'arithmetic', + 'geometric', + 'paraboloid', + 'exponential', + ], + description: 'Progression type when dragging', + }, + orientation: { + control: 'select', + options: ['horizontal', 'vertical'], + description: 'Component orientation', + }, + unit: { + control: 'select', + options: ['px', '%', 'rem', 'em', 'deg', 'none'], + description: 'Unit of measurement', + }, + showUnit: { + control: 'boolean', + description: 'Show unit of measurement', + }, + disabled: { + control: 'boolean', + description: 'Disable component', + }, + isDisabledMouseEvent: { + control: 'boolean', + description: 'Disable mouse dragging', + }, + }, +} satisfies Meta + +export default meta +type Story = StoryObj + +/** + * Basic component example. Use Controls to change parameters. + * Switch background in toolbar to change theme (light/dark). + */ +export const Default: Story = { + args: { + value: 50, + }, +} + +/** + * ## Opacity + * Component for controlling element opacity (0-100%). + */ +export const Opacity: Story = { + args: { + value: 75, + }, + render: () => { + const [opacity, setOpacity] = useState(75) + return ( +
+
+ } + /> +
+
+
+ ) + }, +} + +/** + * ## Angle + * Component for controlling rotation angle (0-360°). + */ +export const Angle: Story = { + args: { + value: 45, + }, + render: () => { + const [angle, setAngle] = useState(45) + return ( +
+
+ } + /> +
+
+ {angle}° +
+
+ ) + }, +} + +/** + * ## Border Radius + * Component for controlling element border radius. + */ +export const BorderRadius: Story = { + args: { + value: 16, + }, + render: () => { + const [radius, setRadius] = useState(16) + return ( +
+
+ } + /> +
+
+
+ ) + }, +} + +/** + * ## Border Radius Multi + * Control radius of each corner separately with linking option. + */ +export const BorderRadiusMulti: Story = { + args: { + value: 0, + }, + render: () => { + const [borderRadius, setBorderRadius] = useState< + [number, number, number, number] + >([16, 16, 16, 16]) + const [isLinked, setIsLinked] = useState(true) + + const handleChange = (index: number) => (newValue: number) => { + if (isNaN(newValue)) return + + if (isLinked) { + setBorderRadius([newValue, newValue, newValue, newValue]) + } else { + const newValues: [number, number, number, number] = [...borderRadius] + newValues[index] = newValue + setBorderRadius(newValues) + } + } + + return ( +
+
+
+
+ } + className="w-full" + /> + } + className="w-full" + /> + } + className="w-full" + /> + } + className="w-full" + /> +
+ + +
+
+ +
+
+ ) + }, +} + +/** + * ## Width & Height + * Components for controlling element dimensions. + */ +export const WidthHeight: Story = { + args: { + value: 200, + }, + render: () => { + const [width, setWidth] = useState(200) + const [height, setHeight] = useState(150) + return ( +
+
+
+ } + /> + + Width + +
+
+ } + /> + + Height + +
+
+
+ {width}×{height} +
+
+ ) + }, +} + +/** + * ## Font Size + * Components for controlling font size in different units (px, em). + */ +export const FontSize: Story = { + args: { + value: 16, + }, + render: () => { + const [fontSizePx, setFontSizePx] = useState(16) + const [fontSizeEm, setFontSizeEm] = useState(1.5) + return ( +
+
+
+ } + /> +
+

+ Text size {fontSizePx}px +

+
+
+
+ } + /> +
+

+ Text size {fontSizeEm}em +

+
+
+ ) + }, +} + +/** + * ## Line Height + * Component for controlling line height (unitless value). + */ +export const LineHeight: Story = { + args: { + value: 1.5, + }, + render: () => { + const [lineHeight, setLineHeight] = useState(1.5) + return ( +
+
+ } + /> +
+
+

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do + eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim + ad minim veniam, quis nostrud exercitation ullamco. +

+
+
+ ) + }, +} + +/** + * ## Spacing + * Components for controlling spacing in different units (px, rem). + */ +export const Spacing: Story = { + args: { + value: 16, + }, + render: () => { + const [spacingPx, setSpacingPx] = useState(16) + const [spacingRem, setSpacingRem] = useState(1) + return ( +
+
+
+ } + /> +
+
+
+
+ Padding: {spacingPx}px +
+
+
+
+
+
+ } + /> +
+
+
+
+ Padding: {spacingRem}rem +
+
+
+
+
+ ) + }, +} + +/** + * ## Blur + * Component for controlling blur effect. + */ +export const Blur: Story = { + args: { + value: 5, + }, + render: () => { + const [blur, setBlur] = useState(5) + return ( +
+
+ } + /> +
+
+
+
+
+ ) + }, +} + +/** + * ## Z-Index + * Component for controlling element z-index. + */ +export const ZIndex: Story = { + args: { + value: 1, + }, + render: () => { + const [zIndex1, setZIndex1] = useState(1) + const [zIndex2, setZIndex2] = useState(2) + return ( +
+
+
+ } + /> + + Red + +
+
+ } + /> + + Blue + +
+
+
+
+ zIndex: {zIndex1} +
+
+ zIndex: {zIndex2} +
+
+
+ ) + }, +} + +/** + * ## Scale + * Component for controlling element scale. + */ +export const Scale: Story = { + args: { + value: 1, + }, + render: () => { + const [scale, setScale] = useState(1) + return ( +
+
+ } + /> +
+
+
+ Scale: x{scale} +
+
+
+ ) + }, +} + +/** + * ## Vertical Orientation + * Component with vertical orientation for controlling Y-axis values. + */ +export const VerticalOrientation: Story = { + args: { + value: 50, + }, + render: () => { + const [value, setValue] = useState(50) + return ( +
+ +
+

+ Value: {value}% +

+

+ Drag vertically +

+
+
+ ) + }, +} + +/** + * ## Progression Types + * Demonstration of different progression types when dragging. + */ +export const ProgressionTypes: Story = { + args: { + value: 50, + }, + render: () => { + const [linear, setLinear] = useState(50) + const [arithmetic, setArithmetic] = useState(50) + const [geometric, setGeometric] = useState(50) + const [paraboloid, setParaboloid] = useState(50) + const [exponential, setExponential] = useState(50) + + return ( +
+
+
+

+ Linear +

+ +
+
+

+ Arithmetic +

+ +
+
+

+ Geometric +

+ +
+
+

+ Paraboloid +

+ +
+
+

+ Exponential +

+ +
+
+

+ Drag the wheel to feel the difference in progression +

+
+ ) + }, +} + +/** + * ## Disabled State + * Component in disabled state. + */ +export const DisabledState: Story = { + args: { + value: 50, + min: 0, + max: 100, + step: 1, + precision: 0, + disabled: true, + }, +} + +/** + * ## Mouse Event Control + * Demonstration of `isDisabledMouseEvent` property. + * When enabled, wheel dragging is disabled, only keyboard input remains. + */ +export const MouseEventControl: Story = { + args: { + value: 50, + }, + render: () => { + const [valueEnabled, setValueEnabled] = useState(50) + const [valueDisabled, setValueDisabled] = useState(50) + + return ( +
+
+

+ With dragging (default) +

+ +

+ You can drag the wheel and enter value +

+
+ +
+

+ Without dragging +

+ +

+ Keyboard input only, dragging disabled +

+
+
+ ) + }, +} diff --git a/src/stories/Inputs.stories.tsx b/src/stories/Inputs.stories.tsx deleted file mode 100644 index a0ee09a..0000000 --- a/src/stories/Inputs.stories.tsx +++ /dev/null @@ -1,695 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react' -import { CircleAlertIcon } from 'lucide-react' -import React, { useState } from 'react' -import { InputNumberSelect } from '../input-number-select' -import { - AngleInput, - BlurInput, - BorderRadiusInput, - BorderRadiusMultiInput, - FontSizeInput, - HeightInput, - LetterSpacingInput, - LineHeightInput, - OpacityInput, - ScaleInput, - SpacingInput, - WidthInput, - ZIndexInput, -} from '../inputs' - -const meta = { - title: 'Components/Inputs', - parameters: { - layout: 'centered', - }, - tags: ['autodocs'], -} satisfies Meta - -export default meta - -// OpacityInput Stories -export const Opacity: StoryObj = { - render: () => { - const [inputLightValue, setInputLightValue] = useState(50) - const [inputDarkValue, setInputDarkValue] = useState(50) - return ( -
-

Opacity Input

-
-
-
- -
-

- Прозрачность: {inputLightValue}% -

-
-
-
- -
-

- Прозрачность: {inputDarkValue}% -

-
-
-
- ) - }, -} - -export const OpacityVertical: StoryObj = { - render: () => { - const [inputLightValue, setInputLightValue] = useState(75) - const [inputDarkValue, setInputDarkValue] = useState(75) - return ( -
-

Вертикальный Opacity

-
-
-
- setInputLightValue(val)} - orientation="vertical" - theme="light" - /> -
-

- Значение: {inputLightValue}% -

-
-
-
- setInputDarkValue(val)} - orientation="vertical" - theme="dark" - /> -
-

- Значение: {inputDarkValue}% -

-
-
-
- ) - }, -} - -// AngleInput Stories -export const Angle: StoryObj = { - render: () => { - const [inputLightValue, setInputLightValue] = useState(45) - const [inputDarkValue, setInputDarkValue] = useState(45) - return ( -
-

Angle Input

-
-
-
- -
-

- Угол: {inputLightValue}° -

-
-
-
- -
-

- Угол: {inputDarkValue}° -

-
-
-
- ) - }, -} - -// BorderRadiusInput Stories -export const BorderRadius: StoryObj = { - render: () => { - const [inputLightValue, setInputLightValue] = useState(8) - const [inputDarkValue, setInputDarkValue] = useState(8) - return ( -
-

Border Radius Input

-
-
-
- -
-

- Радиус: {inputLightValue}px -

-
-
-
- -
-

- Радиус: {inputDarkValue}rem -

-
-
-
- ) - }, -} - -// BorderRadiusMultiInput Stories -export const BorderRadiusMulti: StoryObj = { - render: () => { - const [inputLightValue, setInputLightValue] = useState< - [number, number, number, number] - >([8, 16, 8, 16]) - const [inputDarkValue, setInputDarkValue] = useState< - [number, number, number, number] - >([8, 16, 8, 16]) - return ( -
-

- Border Radius Multi Input -

-
-
-
- -
-

- Углы: {inputLightValue.join(', ')}px -

-
-
-
- -
-

- Углы: {inputDarkValue.join(', ')}px -

-
-
-
- ) - }, -} - -// WidthInput Stories -export const Width: StoryObj = { - render: () => { - const [inputLightValue, setInputLightValue] = useState(200) - const [inputDarkValue, setInputDarkValue] = useState(200) - return ( -
-

Width Input

-
-
-
- -
-

- Ширина: {inputLightValue}px -

-
-
-
- -
-

- Ширина: {inputDarkValue}% -

-
-
-
- ) - }, -} - -// HeightInput Stories -export const Height: StoryObj = { - render: () => { - const [inputLightValue, setInputLightValue] = useState(150) - const [inputDarkValue, setInputDarkValue] = useState(150) - return ( -
-

Height Input

-
-
-
- -
-

- Высота: {inputLightValue}px -

-
-
-
- -
-

- Высота: {inputDarkValue}px -

-
-
-
- ) - }, -} - -// SpacingInput Stories -export const Spacing: StoryObj = { - render: () => { - const [inputLightValue, setInputLightValue] = useState(16) - const [inputDarkValue, setInputDarkValue] = useState(16) - return ( -
-

Spacing Input

-
-
-
- -
-

- Отступ: {inputLightValue}px -

-
-
-
- -
-

- Отступ: {inputDarkValue}rem -

-
-
-
- ) - }, -} - -// FontSizeInput Stories -export const FontSize: StoryObj = { - render: () => { - const [inputLightValue, setInputLightValue] = useState(16) - const [inputDarkValue, setInputDarkValue] = useState(16) - return ( -
-

Font Size Input

-
-
-
- -
-

- Размер шрифта: {inputLightValue}px -

-
-
-
- -
-

- Размер шрифта: {inputDarkValue}rem -

-
-
-
- ) - }, -} - -// LineHeightInput Stories -export const LineHeight: StoryObj = { - render: () => { - const [inputLightValue, setInputLightValue] = useState(1.5) - const [inputDarkValue, setInputDarkValue] = useState(1.5) - return ( -
-

Line Height Input

-
-
-
- -
-

- Межстрочный интервал: {inputLightValue} -

-
-
-
- -
-

- Межстрочный интервал: {inputDarkValue} -

-
-
-
- ) - }, -} - -// LetterSpacingInput Stories -export const LetterSpacing: StoryObj = { - render: () => { - const [inputLightValue, setInputLightValue] = useState(0) - const [inputDarkValue, setInputDarkValue] = useState(0) - return ( -
-

Letter Spacing Input

-
-
-
- -
-

- Межбуквенный интервал: {inputLightValue}px -

-
-
-
- -
-

- Межбуквенный интервал: {inputDarkValue}em -

-
-
-
- ) - }, -} - -// ZIndexInput Stories -export const ZIndex: StoryObj = { - render: () => { - const [inputLightValue, setInputLightValue] = useState(10) - const [inputDarkValue, setInputDarkValue] = useState(10) - return ( -
-

Z-Index Input

-
-
-
- -
-

- Z-Index: {inputLightValue} -

-
-
-
- -
-

- Z-Index: {inputDarkValue} -

-
-
-
- ) - }, -} - -// ScaleInput Stories -export const Scale: StoryObj = { - render: () => { - const [inputLightValue, setInputLightValue] = useState(1) - const [inputDarkValue, setInputDarkValue] = useState(1) - return ( -
-

Scale Input

-
-
-
- -
-

- Масштаб: {inputLightValue} -

-
-
-
- -
-

- Масштаб: {inputDarkValue} -

-
-
-
- ) - }, -} - -// BlurInput Stories -export const Blur: StoryObj = { - render: () => { - const [inputLightValue, setInputLightValue] = useState(5) - const [inputDarkValue, setInputDarkValue] = useState(5) - return ( -
-

Blur Input

-
-
-
- -
-

- Размытие: {inputLightValue}px -

-
-
-
- -
-

- Размытие: {inputDarkValue}px -

-
-
-
- ) - }, -} - -// InputNumberSelect Stories -export const InputNumberSelectBasic: StoryObj = { - render: () => { - const [value, setValue] = useState(50) - return ( -
-

- Input Number Select - Basic -

- setValue(typeof v === 'string' ? 0 : v)} - step={1} - precision={0} - orientation="horizontal" - progression="linear" - /> -
-

Текущее значение: {value}

-
-
- ) - }, -} - -export const InputNumberSelectWithIcon: StoryObj = { - render: () => { - const [value, setValue] = useState(75) - return ( -
-

- Input Number Select - С иконкой -

- setValue(typeof v === 'string' ? 0 : v)} - step={5} - precision={0} - orientation="horizontal" - progression="linear" - icon={} - /> -
-

Текущее значение: {value}

-
-
- ) - }, -} - -export const InputNumberSelectProgression: StoryObj = { - render: () => { - const [linearValue, setLinearValue] = useState(50) - const [exponentialValue, setExponentialValue] = useState(1) - const [logarithmicValue, setLogarithmicValue] = useState(10) - - return ( -
-

- Input Number Select - Типы прогрессии -

- -
-
-

Linear

- setLinearValue(typeof v === 'string' ? 0 : v)} - step={1} - precision={0} - orientation="horizontal" - progression="linear" - /> -

Значение: {linearValue}

-
- -
-

Exponential

- setExponentialValue(typeof v === 'string' ? 0 : v)} - step={0.1} - precision={2} - orientation="horizontal" - progression="exponential" - /> -

Значение: {exponentialValue}

-
- -
-

Logarithmic

- setLogarithmicValue(typeof v === 'string' ? 0 : v)} - step={1} - precision={1} - orientation="horizontal" - progression="logarithmic" - /> -

Значение: {logarithmicValue}

-
-
-
- ) - }, -} diff --git a/src/styles/tailwind.css b/src/styles/tailwind.css index b2ec8d4..acb09ec 100644 --- a/src/styles/tailwind.css +++ b/src/styles/tailwind.css @@ -1,4 +1,5 @@ /* Tailwind CSS v4 entry for Storybook */ @import 'tailwindcss'; -/* You can add design tokens or custom CSS below if needed */ +/* Configure dark mode to use class strategy instead of media query */ +@variant dark (.dark &); diff --git a/tsup.config.ts b/tsup.config.ts index 102043c..44e517c 100644 --- a/tsup.config.ts +++ b/tsup.config.ts @@ -11,24 +11,6 @@ export default defineConfig({ 'input-color-picker/index': 'src/input-color-picker/index.tsx', 'input/index': 'src/input/index.tsx', 'shared/index': 'src/shared/index.ts', - - // Inputs модульные экспорты - 'inputs/index': 'src/inputs/index.ts', - 'inputs/opacity-input/index': 'src/inputs/opacity-input/index.tsx', - 'inputs/angle-input/index': 'src/inputs/angle-input/index.tsx', - 'inputs/border-radius-input/index': 'src/inputs/border-radius-input/index.tsx', - 'inputs/border-radius-multi-input/index': - 'src/inputs/border-radius-multi-input/index.tsx', - 'inputs/width-input/index': 'src/inputs/width-input/index.tsx', - 'inputs/height-input/index': 'src/inputs/height-input/index.tsx', - 'inputs/spacing-input/index': 'src/inputs/spacing-input/index.tsx', - 'inputs/font-size-input/index': 'src/inputs/font-size-input/index.tsx', - 'inputs/line-height-input/index': 'src/inputs/line-height-input/index.tsx', - 'inputs/letter-spacing-input/index': - 'src/inputs/letter-spacing-input/index.tsx', - 'inputs/z-index-input/index': 'src/inputs/z-index-input/index.tsx', - 'inputs/scale-input/index': 'src/inputs/scale-input/index.tsx', - 'inputs/blur-input/index': 'src/inputs/blur-input/index.tsx', }, format: ['esm', 'cjs'], dts: true,