From c532298eba4cc0d2f5f02485ff142ee29444363c Mon Sep 17 00:00:00 2001 From: Sychev Andrey <44414066+SychevAndrey@users.noreply.github.com> Date: Wed, 5 Mar 2025 11:25:55 +0400 Subject: [PATCH] 87 create checkbox component (#3294) * refactor: remove --dbv-kit-color-text variable in favor of --dbv-kit-color-foreground, add --dbv-kit-control-disabled-opacity to use for all controls, remove --dbv-kit-btn-disabled-opacity, update docs * refactor: add ControlSize type * feat: create checkbox component * docs: add checkbox stories, update button docs * refactor: polish checkbox component, added some vars, add docs, change some defaults * feat: add indeterminate state * fix: prioritize check prop over inner state * fix: button styles layer * fix: comments * fix: change checkbox checked style * Revert "fix: change checkbox checked style" This reverts commit eba1e21eb904f70f7ceaf51c024ea163fc47d9e4. --------- Co-authored-by: Wroud Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com> --- .../@dbeaver/ui-kit/.ladle/global.css | 17 +- .../@dbeaver/ui-kit/src/Button/Button.css | 8 +- .../@dbeaver/ui-kit/src/Button/Button.tsx | 3 +- .../@dbeaver/ui-kit/src/Button/_base.css | 7 +- .../@dbeaver/ui-kit/src/Button/_variants.css | 1 + .../@dbeaver/ui-kit/src/Checkbox/Checkbox.css | 98 +++++++ .../@dbeaver/ui-kit/src/Checkbox/Checkbox.tsx | 63 ++++- .../@dbeaver/ui-kit/src/Checkbox/_base.css | 28 ++ .../@dbeaver/ui-kit/src/Checkbox/_sizes.css | 23 ++ .../ui-kit/src/Checkbox/checkbox.stories.tsx | 11 - .../@dbeaver/ui-kit/src/Input/Input.css | 6 + .../@dbeaver/ui-kit/src/Input/_base.css | 4 +- common-react/@dbeaver/ui-kit/src/index.css | 10 +- .../components/Button/button-tokens.tsx | 121 ++++----- .../components/Button/button.stories.tsx | 24 +- .../components/Checkbox/checkbox.stories.tsx | 257 ++++++++++++++++++ .../stories/components/Checkbox/styles.css | 6 + .../components/Input/Input.stories.tsx | 2 +- .../src/stories/customization.stories.tsx | 4 +- .../@dbeaver/ui-kit/src/stories/tokens.tsx | 115 ++++---- .../@dbeaver/ui-kit/src/types/controls.ts | 1 + 21 files changed, 631 insertions(+), 178 deletions(-) create mode 100644 common-react/@dbeaver/ui-kit/src/Checkbox/Checkbox.css create mode 100644 common-react/@dbeaver/ui-kit/src/Checkbox/_base.css create mode 100644 common-react/@dbeaver/ui-kit/src/Checkbox/_sizes.css delete mode 100644 common-react/@dbeaver/ui-kit/src/Checkbox/checkbox.stories.tsx create mode 100644 common-react/@dbeaver/ui-kit/src/stories/components/Checkbox/checkbox.stories.tsx create mode 100644 common-react/@dbeaver/ui-kit/src/stories/components/Checkbox/styles.css create mode 100644 common-react/@dbeaver/ui-kit/src/types/controls.ts diff --git a/common-react/@dbeaver/ui-kit/.ladle/global.css b/common-react/@dbeaver/ui-kit/.ladle/global.css index cd5487ec9b..1e1e79ab28 100644 --- a/common-react/@dbeaver/ui-kit/.ladle/global.css +++ b/common-react/@dbeaver/ui-kit/.ladle/global.css @@ -2,7 +2,7 @@ @layer theme { main { - color: var(--dbv-kit-color-text); + color: var(--dbv-kit-color-foreground); background-color: var(--tw-color-gray-50); } @@ -75,10 +75,17 @@ .comment { color: var(--tw-color-gray-500); } + + .color-sample { + display: inline-block; + width: 1em; + height: 1em; + border-radius: 0.25em; + } } :root[data-theme='dark'] { - --dbv-kit-color-text: var(--tw-color-gray-200); + --dbv-kit-color-foreground: var(--tw-color-gray-200); --dbv-kit-btn-loader-base-color: var(--tw-color-gray-900); /* Primary Button */ @@ -100,6 +107,12 @@ --dbv-kit-btn-danger-background-hover: var(--tw-color-red-600); --dbv-kit-btn-danger-background-active: var(--tw-color-red-500); + /* Input */ + --dbv-kit-input-background: var(--tw-color-gray-800); + --dbv-kit-input-border-color: var(--tw-color-gray-700); + --dbv-kit-input-color: var(--tw-color-gray-100); + --dbv-kit-input-placeholder-foreground: var(--tw-color-gray-400); + main { background-color: var(--tw-color-gray-900); } diff --git a/common-react/@dbeaver/ui-kit/src/Button/Button.css b/common-react/@dbeaver/ui-kit/src/Button/Button.css index 2146287b0f..de18dd365c 100644 --- a/common-react/@dbeaver/ui-kit/src/Button/Button.css +++ b/common-react/@dbeaver/ui-kit/src/Button/Button.css @@ -2,7 +2,7 @@ @import './_sizes.css'; @import './_variants.css'; -@layer components { +@layer base { .dbv-kit-button { display: inline-flex; text-align: center; @@ -31,7 +31,8 @@ &:focus-visible, &[data-focus-visible] { - outline: var(--dbv-kit-control-outline-width) solid var(--dbv-kit-color-outline); + outline: var(--dbv-kit-control-outline-width) solid var(--dbv-kit-btn-outline-color); + outline-offset: var(--dbv-kit-btn-outline-offset); } &:hover { @@ -46,7 +47,7 @@ } &:disabled { - opacity: var(--dbv-kit-btn-disabled-opacity); + opacity: var(--dbv-kit-control-disabled-opacity); } } @@ -121,6 +122,7 @@ --dbv-kit-btn-loader-color: var(--dbv-kit-btn-danger-background); --dbv-kit-btn-loader-background: var(--dbv-kit-btn-loader-base-color); + --dbv-kit-btn-outline-color: var(--dbv-kit-btn-danger-color-outline); } /* ---------------------------- */ diff --git a/common-react/@dbeaver/ui-kit/src/Button/Button.tsx b/common-react/@dbeaver/ui-kit/src/Button/Button.tsx index 2f0785f98c..927bc803f4 100644 --- a/common-react/@dbeaver/ui-kit/src/Button/Button.tsx +++ b/common-react/@dbeaver/ui-kit/src/Button/Button.tsx @@ -7,10 +7,11 @@ */ import { Button as AriaButton, type ButtonProps as AriaKitButtonProps } from '@ariakit/react'; import './Button.css'; +import type { ControlSize } from '../types/controls.js'; export interface ButtonProps extends Omit { variant?: 'primary' | 'secondary' | 'danger'; - size?: 'small' | 'medium' | 'large' | 'xlarge'; + size?: ControlSize; loading?: boolean; loader?: React.ReactNode; } diff --git a/common-react/@dbeaver/ui-kit/src/Button/_base.css b/common-react/@dbeaver/ui-kit/src/Button/_base.css index 377b3ea2ef..a639ef510a 100644 --- a/common-react/@dbeaver/ui-kit/src/Button/_base.css +++ b/common-react/@dbeaver/ui-kit/src/Button/_base.css @@ -3,17 +3,22 @@ --dbv-kit-btn-height: var(--dbv-kit-control-height-medium); --dbv-kit-btn-padding-inline: calc(var(--tw-spacing) * 2.5); --dbv-kit-btn-gap: calc(var(--dbv-kit-btn-padding-inline) / 4); + --dbv-kit-btn-outline-offset: var(--dbv-kit-outline-offset); + --dbv-kit-btn-foreground: var(--tw-color-white); --dbv-kit-btn-background: var(--dbv-kit-color-primary-600); --dbv-kit-btn-background-hover: var(--dbv-kit-color-primary-700); --dbv-kit-btn-background-active: var(--dbv-kit-color-primary-800); + --dbv-kit-btn-outline-color: var(--dbv-kit-color-outline); + --dbv-kit-btn-border-width: var(--dbv-kit-control-border-width); --dbv-kit-btn-border-color: transparent; --dbv-kit-btn-border-style: solid; --dbv-kit-btn-border-radius: var(--dbv-kit-control-border-radius); + --dbv-kit-btn-font-weight: var(--tw-font-weight-normal); --dbv-kit-btn-font-size: calc(var(--dbv-kit-font-size-base) * 0.875); - --dbv-kit-btn-disabled-opacity: 0.5; + --dbv-kit-btn-loader-base-color: var(--tw-color-white); --dbv-kit-btn-loader-animation: var(--tw-animate-spin); } diff --git a/common-react/@dbeaver/ui-kit/src/Button/_variants.css b/common-react/@dbeaver/ui-kit/src/Button/_variants.css index 8ca649b874..9e29c76fca 100644 --- a/common-react/@dbeaver/ui-kit/src/Button/_variants.css +++ b/common-react/@dbeaver/ui-kit/src/Button/_variants.css @@ -18,5 +18,6 @@ --dbv-kit-btn-danger-background: var(--tw-color-red-600); --dbv-kit-btn-danger-background-hover: var(--tw-color-red-700); --dbv-kit-btn-danger-background-active: var(--tw-color-red-800); + --dbv-kit-btn-danger-color-outline: var(--tw-color-red-300); } } diff --git a/common-react/@dbeaver/ui-kit/src/Checkbox/Checkbox.css b/common-react/@dbeaver/ui-kit/src/Checkbox/Checkbox.css new file mode 100644 index 0000000000..b5c726b44e --- /dev/null +++ b/common-react/@dbeaver/ui-kit/src/Checkbox/Checkbox.css @@ -0,0 +1,98 @@ +@import './_base.css'; +@import './_sizes.css'; + +@layer base { + .dbv-kit-checkbox { + display: inline-flex; + user-select: none; + align-items: start; + min-height: var(--dbv-kit-checkbox-height); + gap: var(--dbv-kit-checkbox-gap); + line-height: var(--dbv-kit-checkbox-height); + font-size: var(--dbv-kit-checkbox-font-size); + color: var(--dbv-kit-checkbox-foreground); + outline: var(--dbv-kit-color-outline); + + &:hover { + cursor: pointer; + + .dbv-kit-checkbox__check { + border-color: var(--dbv-kit-checkbox-border-color-checked); + box-shadow: 0 0 0 2px var(--dbv-kit-checkbox-hover-shadow-color); + } + } + + &[data-checked='true'] { + border-color: var(--dbv-kit-checkbox-border-color-checked); + } + + &[data-disabled='true'] { + pointer-events: none; + opacity: var(--dbv-kit-control-disabled-opacity); + } + } + + .dbv-kit-checkbox__check { + margin-top: calc(var(--dbv-kit-checkbox-height) * (calc(1 - var(--dbv-kit-checkbox-check-size)) / 2)); + height: calc(var(--dbv-kit-checkbox-height) * var(--dbv-kit-checkbox-check-size)); + line-height: calc(var(--dbv-kit-checkbox-height) * var(--dbv-kit-checkbox-check-size)); + width: calc(var(--dbv-kit-checkbox-height) * var(--dbv-kit-checkbox-check-size)); + min-width: calc(var(--dbv-kit-checkbox-height) * var(--dbv-kit-checkbox-check-size)); + border-radius: var(--dbv-kit-checkbox-border-radius); + background-color: var(--dbv-kit-checkbox-background); + border-color: var(--dbv-kit-checkbox-border-color); + border-width: var(--dbv-kit-checkbox-border-width); + border-style: var(--dbv-kit-checkbox-border-style); + color: var(--dbv-kit-checkbox-icon-color); + outline-offset: var(--dbv-kit-outline-offset); + outline-width: var(--dbv-kit-control-outline-width); + outline-color: var(--dbv-kit-color-outline); + transition-property: stroke-dashoffset; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; + stroke-dasharray: 20; + stroke-dashoffset: 20; + + &[data-focus-visible] { + outline-style: solid; + } + + &:hover { + border-color: var(--dbv-kit-checkbox-border-color-checked); + } + + &[data-checked='true'], + &[data-indeterminate='true'] { + background-color: var(--dbv-kit-checkbox-background-checked); + border-color: var(--dbv-kit-checkbox-border-color-checked); + stroke-dashoffset: 0; + } + } + + /* ---------------------------- */ + /* Sizes */ + + .dbv-kit-checkbox--small { + --dbv-kit-checkbox-font-size: var(--dbv-kit-checkbox-small-font-size); + --dbv-kit-checkbox-height: var(--dbv-kit-checkbox-small-height); + --dbv-kit-checkbox-gap: var(--dbv-kit-checkbox-small-gap); + } + + .dbv-kit-checkbox--medium { + --dbv-kit-checkbox-font-size: var(--dbv-kit-checkbox-medium-font-size); + --dbv-kit-checkbox-height: var(--dbv-kit-checkbox-medium-height); + --dbv-kit-checkbox-gap: var(--dbv-kit-checkbox-medium-gap); + } + + .dbv-kit-checkbox--large { + --dbv-kit-checkbox-font-size: var(--dbv-kit-checkbox-large-font-size); + --dbv-kit-checkbox-height: var(--dbv-kit-checkbox-large-height); + --dbv-kit-checkbox-gap: var(--dbv-kit-checkbox-large-gap); + } + + .dbv-kit-checkbox--xlarge { + --dbv-kit-checkbox-font-size: var(--dbv-kit-checkbox-xlarge-font-size); + --dbv-kit-checkbox-height: var(--dbv-kit-checkbox-xlarge-height); + --dbv-kit-checkbox-gap: var(--dbv-kit-checkbox-xlarge-gap); + } +} diff --git a/common-react/@dbeaver/ui-kit/src/Checkbox/Checkbox.tsx b/common-react/@dbeaver/ui-kit/src/Checkbox/Checkbox.tsx index 0da5921c53..6a17f1629f 100644 --- a/common-react/@dbeaver/ui-kit/src/Checkbox/Checkbox.tsx +++ b/common-react/@dbeaver/ui-kit/src/Checkbox/Checkbox.tsx @@ -6,15 +6,66 @@ * you may not use this file except in compliance with the License. */ -import { Checkbox as AriaCheckbox, type CheckboxProps as AriaKitCheckboxProps } from '@ariakit/react'; +import { Checkbox as AriaCheckbox, VisuallyHidden, type CheckboxProps as AriaKitCheckboxProps } from '@ariakit/react'; +import { useState } from 'react'; +import './Checkbox.css'; +import type { ControlSize } from '../types/controls.js'; -export interface CheckboxProps extends AriaKitCheckboxProps {} +export interface CheckboxProps extends Omit { + size?: ControlSize; + icon?: React.ReactNode; + indeterminate?: boolean; + indeterminateIcon?: React.ReactNode; +} -export function Checkbox({ children, ...props }: CheckboxProps) { +export function Checkbox({ children, className, icon, indeterminate, indeterminateIcon, size = 'medium', ...props }: CheckboxProps) { + const [innerChecked, setInnerChecked] = useState(props.defaultChecked ?? false); + const checked = props.checked ?? innerChecked; + + const [focusVisible, setFocusVisible] = useState(false); return ( -