diff --git a/src/lib/box-styles.tsx b/src/lib/box-styles.tsx index e19b9260..044b6d02 100644 --- a/src/lib/box-styles.tsx +++ b/src/lib/box-styles.tsx @@ -20,7 +20,7 @@ export const Global = styled.div` /* should be rewritten to formulas */ --woly-line-height: 24px; - --woly-border-width: 1.5px; + --woly-border-width: 2px; --woly-rounding: 4px; --woly-font-size: 15px; --woly-shadow: 3px 3px 9px rgba(57, 57, 57, 0.12); @@ -47,7 +47,7 @@ export const Global = styled.div` --woly-canvas-default: transparent; --woly-canvas-disabled: var(--palette-snow-100); --woly-canvas-hover: var(--palette-snow-500); - --woly-canvas-active: var(--palette-snow-500); + --woly-canvas-active: var(--palette-lavender-500); --woly-canvas-text-default: var(--palette-snow-1000); --woly-canvas-text-disabled: var(--palette-snow-500); diff --git a/src/ui/atoms/input/index.tsx b/src/ui/atoms/input/index.tsx index f8877175..fdb5f86f 100644 --- a/src/ui/atoms/input/index.tsx +++ b/src/ui/atoms/input/index.tsx @@ -52,6 +52,11 @@ const InputBase: React.FC = ({ ); export const Input = styled(InputBase)` + --local-vertical: calc(1px * var(--woly-component-level) * var(--woly-main-level)); + --local-horizontal: calc( + var(--woly-const-m) + (1px * var(--woly-main-level)) + var(--local-vertical) + ); + box-sizing: border-box; width: 100%; diff --git a/src/ui/atoms/input/usage.mdx b/src/ui/atoms/input/usage.mdx index cb795bc3..cae97f89 100644 --- a/src/ui/atoms/input/usage.mdx +++ b/src/ui/atoms/input/usage.mdx @@ -7,7 +7,7 @@ package: 'woly' import { Input } from './index'; import { InfoIcon } from 'icons'; -import { Playground, block, State, Form } from 'box-styles' +import { Playground, block, State } from 'box-styles' import { ButtonIcon } from '../../atoms'; @@ -54,15 +54,13 @@ Specs: ### Example -
- console.info('On input change')} - placeholder="Enter your name here" - type="text" - variant="primary" - /> -
+ console.info('On input change')} + placeholder="Enter your name here" + type="text" + variant="primary" + />
### Disabled @@ -70,25 +68,23 @@ Specs: Disabled text fields are uneditable. They have less opacity so that they appear less tappable. -
- console.info('On input change')} - placeholder="Enter your name here" - type="text" - variant="primary" - /> - } - name="name" - onChange={(event) => console.info('On input change')} - placeholder="Enter your name here" - type="text" - variant="primary" - /> -
+ console.info('On input change')} + placeholder="Enter your name here" + type="text" + variant="primary" + /> + } + name="name" + onChange={(event) => console.info('On input change')} + placeholder="Enter your name here" + type="text" + variant="primary" + />
### Icons @@ -96,73 +92,69 @@ Disabled text fields are uneditable. They have less opacity so that they appear Inputs can be combined with an icon on the left side or the right. Use icons on the both sides is not recommended. -
- } - type="text" - name="name" - placeholder="Enter your name here" - variant="primary" - onChange={(event) => console.info('On input change')} - /> - console.info('On input change')} - rightIcon={} - /> - } - type="text" - name="name" - placeholder="Enter your name here" - variant="primary" - onChange={(event) => console.info('On input change')} - rightIcon={} - /> - + } + type="text" + name="name" + placeholder="Enter your name here" + variant="primary" + onChange={(event) => console.info('On input change')} + /> + console.info('On input change')} + rightIcon={} + /> + } + type="text" + name="name" + placeholder="Enter your name here" + variant="primary" + onChange={(event) => console.info('On input change')} + rightIcon={} + />
Icons can also be touch targets for nested components. -
- - } - onClick={() => console.info('ButtonIcon clicked')} - variant="primary" - /> - - } - type="text" - name="name" - placeholder="Enter your name here" - onChange={(event) => console.info('On input change')} - variant="primary" - /> - } - type="text" - name="name" - placeholder="Enter your name here" - onChange={(event) => console.info('On input change')} - variant="primary" - rightIcon={ - - } - onClick={() => console.info('ButtonIcon clicked')} - variant="primary" - /> - - } - /> - + + } + onClick={() => console.info('ButtonIcon clicked')} + variant="primary" + /> + + } + type="text" + name="name" + placeholder="Enter your name here" + onChange={(event) => console.info('On input change')} + variant="primary" + /> + } + type="text" + name="name" + placeholder="Enter your name here" + onChange={(event) => console.info('On input change')} + variant="primary" + rightIcon={ + + } + onClick={() => console.info('ButtonIcon clicked')} + variant="primary" + /> + + } + />
### Variants @@ -172,41 +164,37 @@ Primary and secondary variants are should be used to focus user attention. -
- } - name="name" - onChange={(event) => console.info('On input change')} - placeholder="Primary input" - type="text" - value="Primary" - variant="primary" - /> - } - type="text" - name="name" - value="Secondary" - placeholder="Secondary input" - variant="secondary" - onChange={(event) => console.info('On input change')} - /> -
+ } + name="name" + onChange={(event) => console.info('On input change')} + placeholder="Primary input" + type="text" + value="Primary" + variant="primary" + /> + } + type="text" + name="name" + value="Secondary" + placeholder="Secondary input" + variant="secondary" + onChange={(event) => console.info('On input change')} + />
Error variant can be used to focus user attention on error. -
- } - name="name" - onChange={(event) => console.info('On input change')} - placeholder="Error input" - type="text" - variant="danger" - /> -
+ } + name="name" + onChange={(event) => console.info('On input change')} + placeholder="Error input" + type="text" + variant="danger" + />
### Sizes @@ -214,81 +202,70 @@ Error variant can be used to focus user attention on error. Size controlled by the `component-level` block property not from the props. -
+ console.info('On input change')} placeholder="Enter your name here" type="text" variant="primary" /> + + } name="name" onChange={(event) => console.info('On input change')} placeholder="Enter your name here" type="text" variant="primary" /> - -
- -### Icons - -Inputs can be combined with an icon on the left side or the right. Use icons on the both sides is not recommended. - - -
+ + } - type="text" name="name" + onChange={(event) => console.info('On input change')} placeholder="Enter your name here" + type="text" variant="primary" - onChange={(event) => console.info('On input change')} /> + + console.info('On input change')} placeholder="Enter your name here" + type="text" variant="primary" - onChange={(event) => console.info('On input change')} - rightIcon={} /> + + } - type="text" name="name" + onChange={(event) => console.info('On input change')} placeholder="Enter your name here" + type="text" variant="primary" - onChange={(event) => console.info('On input change')} - rightIcon={} /> - +
-Icons can also be touch targets for nested components. +### Container - -
+Inputs can be placed inside the container. Input width is equal to container size. + + + - } - onClick={() => console.info('ButtonIcon clicked')} - variant="primary" - /> - - } + leftIcon={} type="text" name="name" + value="Primary" placeholder="Enter your name here" - onChange={(event) => console.info('On input change')} variant="primary" + onChange={(event) => console.info('On input change')} /> + + } type="text" @@ -306,147 +283,7 @@ Icons can also be touch targets for nested components. } /> - -
- -### Variants - -Primary and secondary variants are should be used to focus user attention. - - - - -
- } - name="name" - onChange={(event) => console.info('On input change')} - placeholder="Primary input" - type="text" - value="Primary" - variant="primary" - /> - } - type="text" - name="name" - value="Secondary" - placeholder="Secondary input" - variant="secondary" - onChange={(event) => console.info('On input change')} - /> -
-
- -Error variant can be used to focus user attention on error. - - - - -
- } - name="name" - onChange={(event) => console.info('On input change')} - placeholder="Error input" - type="text" - value="Error" - variant="error" - /> -
-
- -### Sizes - -Size controlled by the `component-level` block property not from the props. - - -
- - console.info('On input change')} - placeholder="Enter your name here" - type="text" - variant="primary" - /> - - - console.info('On input change')} - placeholder="Enter your name here" - type="text" - variant="primary" - /> - - - console.info('On input change')} - placeholder="Enter your name here" - type="text" - variant="primary" - /> - - - console.info('On input change')} - placeholder="Enter your name here" - type="text" - variant="primary" - /> - - - console.info('On input change')} - placeholder="Enter your name here" - type="text" - variant="primary" - /> - -
-
- -### Container - -Inputs can be placed inside the container. Input width is equal to container size. - - -
- - } - type="text" - name="name" - value="Primary" - placeholder="Enter your name here" - variant="primary" - onChange={(event) => console.info('On input change')} - /> - - - } - type="text" - name="name" - placeholder="Enter your name here" - onChange={(event) => console.info('On input change')} - variant="primary" - rightIcon={ - - } - onClick={() => console.info('ButtonIcon clicked')} - variant="primary" - /> - - } - /> - -
+
### Kinds @@ -457,16 +294,15 @@ Inputs can be placed inside the container. Input width is equal to container siz ### Props -| Name | Type | Default | Description | -| -------------- | ------------------------------------------- | ----------- | ----------------------------------------------------------- | -| `autocomplete` | `"on" ӏ "off"` | `"off"` | Autocomplete attribute | -| `type` | `"text" ӏ "password" ӏ "email"` | `text` | HTML type of the input | -| `name` | `string` | | Name attribute specifies a name of the input | -| `value` | `HTMLInputElement['value']` | `null` | Value of input field | -| `onChange` | `React.EventHandler;` | | On change event handler. | -| `variant` | `string` | `'default'` | Variant prop to style Input component | -| `leftIcon` | `React.ReactNode` | | Component to show on the left side of the text (ex.: Icon) | -| `disabled` | `boolean` | | HTML disabled attribute | -| `placeholder` | `string` | | CSS pseudo-element represents the placeholder text | -| `rightIcon` | `React.ReactNode` | | Component to show on the right side of the text (ex.: Icon) | -| `...` | `HTMLInputElement` | `{}` | Other props are inherited from `HTMLInputElement` | +| Name | Type | Default | Description | +| ------------- | ------------------------------------------- | ----------- | ----------------------------------------------------------- | +| `type` | `"text" ӏ "password" ӏ "email"` | `text` | HTML type of the input | +| `name` | `string` | | Name attribute specifies a name of the input | +| `value` | `HTMLInputElement['value']` | `null` | Value of input field | +| `onChange` | `React.EventHandler;` | | On change event handler. | +| `variant` | `string` | `'default'` | Variant prop to style Input component | +| `leftIcon` | `React.ReactNode` | | Component to show on the left side of the text (ex.: Icon) | +| `disabled` | `boolean` | | HTML disabled attribute | +| `placeholder` | `string` | | CSS pseudo-element represents the placeholder text | +| `rightIcon` | `React.ReactNode` | | Component to show on the right side of the text (ex.: Icon) | +| `...` | `HTMLInputElement` | `{}` | Other props are inherited from `HTMLInputElement` | diff --git a/src/ui/elements/quarks/input-container/index.tsx b/src/ui/elements/quarks/input-container/index.tsx index b82cb0f0..80dc5016 100644 --- a/src/ui/elements/quarks/input-container/index.tsx +++ b/src/ui/elements/quarks/input-container/index.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import styled, { StyledComponent } from 'styled-components'; import { Variant } from 'lib/types'; - +import { keyboardEventHandle } from 'lib'; interface InputContainerProps extends React.InputHTMLAttributes { className?: string; disabled?: boolean; @@ -12,23 +12,52 @@ interface InputContainerProps extends React.InputHTMLAttributes = ({ children, className, - disabled = 'false', + disabled = false, leftIcon, + onChange, rightIcon, variant = 'default', -}) => ( -
- {leftIcon && {leftIcon}} -
{children}
- {rightIcon && {rightIcon}} -
-); +}) => { + const tabIndex = disabled ? -1 : 0; + + const onKeyDown = React.useCallback( + (event: React.KeyboardEvent) => { + if (event.key === 'Enter') { + event.preventDefault(); + } + const keyHandler = { + enter: (event: React.SyntheticEvent) => { + onChange(event); + }, + }; + + keyboardEventHandle({ + event, + keyHandler, + }); + }, + [onChange], + ); + + return ( +
+ {leftIcon && {leftIcon}} +
{children}
+ {rightIcon && {rightIcon}} +
+ ); +}; export const InputContainer = styled(InputContainerBase)` --local-vertical: calc(1px * var(--woly-component-level) * var(--woly-main-level)); --local-horizontal: calc( - var(--woly-const-m) + (1px * var(--woly-main-level)) + var(--local-vertical) - - var(--woly-border-width) + var(--woly-const-m) + (1px * var(--woly-main-level)) + var(--local-vertical) ); --local-gap: var(--local-vertical); @@ -70,10 +99,15 @@ export const InputContainer = styled(InputContainerBase)` } [data-icon] { + --local-icon-size: var(--woly-line-height); + display: flex; align-items: center; justify-content: center; + width: var(--local-icon-size); + height: var(--local-icon-size); + svg > path { fill: var(--local-icon-fill); } @@ -94,6 +128,7 @@ export const InputContainer = styled(InputContainerBase)` &:focus-within { box-shadow: 0 0 0 var(--woly-border-width) var(--woly-focus); + outline: none; [data-icon] { --local-icon-fill: var(--woly-canvas-text-default); diff --git a/src/ui/molecules/index.ts b/src/ui/molecules/index.ts index cd84c3a7..1a336ebf 100644 --- a/src/ui/molecules/index.ts +++ b/src/ui/molecules/index.ts @@ -2,6 +2,7 @@ export { Checkbox } from './checkbox'; export { Field } from './field'; export { InputPassword } from './input-password'; export { Modal } from './modal'; +export { RadioButton } from './radio-button'; export { Popover } from './popover'; export { Select } from './select'; export { Switch } from './switch'; diff --git a/src/ui/molecules/input-password/usage.mdx b/src/ui/molecules/input-password/usage.mdx index 73984694..d1c6fe49 100644 --- a/src/ui/molecules/input-password/usage.mdx +++ b/src/ui/molecules/input-password/usage.mdx @@ -46,7 +46,8 @@ Disabled text fields are uneditable. They have less opacity so that they appear ### Variants -Primary and danger variants are should be used to focus user attention. +Primary and secondary variants are should be used to focus user attention. + !i}> @@ -67,13 +68,13 @@ Primary and danger variants are should be used to focus user attention. name="name" placeholder="Enter password" onChange={(event) => console.info('On input change')} - variant="danger" + variant="secondary" /> )} -Secondary variant should be used as a default variant. +Error variant can be used to focus user attention on error. !i}> @@ -83,7 +84,7 @@ Secondary variant should be used as a default variant. name="name" placeholder="Enter password" onChange={(event) => console.info('On input change')} - variant="secondary" + variant="danger" /> )} @@ -195,13 +196,13 @@ Inputs can be placed inside the container. Input width is equal to container siz ### Props -| Name | Type | Default | Description | -| -------------- | -------------------------------------------- | ------------- | ----------------------------------------------------------- | -| `disabled` | `boolean` | `null` | HTML disabled attribute | -| `name` | `string` | | HTML name attribute | -| `onChange` | `React.ChangeEventHandler` | | Callback when input is changed | -| `value` | `HTMLInputElement['value']` | `null` | HTML value attribute | -| `variant` | `string` | `'secondary'` | Variant prop to style InputPassword component | -| `placeholder` | `string` | | CSS pseudo-element represents the placeholder text | -| `rightIcon` | `React.ReactNode` | | Component to show on the right side of the text (ex.: Icon) | -| `...` | `HTMLInputElement` | `{}` | Other props are inherited from `HTMLInputElement` | +| Name | Type | Default | Description | +| ------------- | -------------------------------------------- | ----------- | ----------------------------------------------------------- | +| `disabled` | `boolean` | `null` | HTML disabled attribute | +| `name` | `string` | | HTML name attribute | +| `onChange` | `React.ChangeEventHandler` | | Callback when input is changed | +| `value` | `HTMLInputElement['value']` | `null` | HTML value attribute | +| `variant` | `string` | `'default'` | Variant prop to style InputPassword component | +| `placeholder` | `string` | | CSS pseudo-element represents the placeholder text | +| `rightIcon` | `React.ReactNode` | | Component to show on the right side of the text (ex.: Icon) | +| `...` | `HTMLInputElement` | `{}` | Other props are inherited from `HTMLInputElement` | diff --git a/src/ui/molecules/radio-button/index.tsx b/src/ui/molecules/radio-button/index.tsx new file mode 100644 index 00000000..a8c0863c --- /dev/null +++ b/src/ui/molecules/radio-button/index.tsx @@ -0,0 +1,175 @@ +import * as React from 'react'; +import styled, { StyledComponent } from 'styled-components'; +import { Variant } from 'lib/types'; +import { keyboardEventHandle } from 'lib'; + +interface RadioButtonProps { + className?: string; + disabled?: boolean; + id: string; + checked: boolean; + onChange: React.EventHandler; + text?: string; + name: string; +} + +const RadioButtonBase: React.FC = ({ + className, + disabled = false, + id, + checked, + onChange, + text, + name, + variant = 'primary', + ...p +}) => { + const tabIndex = disabled ? -1 : 0; + + const onKeyDown = React.useCallback( + (event: React.KeyboardEvent) => { + if (event.key === 'Enter') { + event.preventDefault(); + } + const keyHandler = { + enter: (event: React.SyntheticEvent) => { + onChange(event); + }, + }; + + keyboardEventHandle({ + event, + keyHandler, + }); + }, + [onChange], + ); + + return ( +
+ +
+ ); +} + + +export const RadioButton = styled(RadioButtonBase)` + --local-vertical: calc(1px * var(--woly-component-level) * var(--woly-main-level)); + --local-horizontal: calc( + var(--woly-const-m) + (1px * var(--woly-main-level)) + var(--local-vertical) + ); + --local-gap: var(--woly-const-m); + + --local-radio-size: 18px; + --local-ellipse-size: 10px; + + --local-color-text: var(--woly-canvas-text-default); + --local-background: var(--woly-shape-text-default); + + --local-icon-fill: var(--woly-shape-default); + + --local-border-color: var(--woly-canvas-hover); + --local-border-rounding: 50%; + + display: flex; + align-items: center; + outline: none; + border-radius: var(--local-border-rounding); + + margin-right: var(--local-vertical); + + label { + display: flex; + align-items: center; + + cursor: pointer; + + input { + display: none; + outline: none; + } + } + + [data-checkbox] { + display: flex; + align-items: center; + justify-content: center; + + width: var(--local-radio-size); + height: var(--local-radio-size); + + background: var(--local-background); + border-radius: var(--local-border-rounding); + border: var(--woly-border-width) solid var(--local-border-color); + + margin-right: var(--local-gap); + } + + input:checked + [data-checkbox] { + --local-border-color: var(--woly-shape-default); + &:before { + content: ''; + + width: var(--local-ellipse-size); + height: var(--local-ellipse-size); + + background-color: var(--local-icon-fill); + border-radius: var(--local-border-rounding); + } + + &:hover { + --local-border-color: var(--woly-shape-hover); + --local-icon-fill: var(--woly-shape-hover); + } + + &:active { + --local-border-color: var(--woly-shape-active); + --local-icon-fill: var(--woly-shape-active); + } + } + + &:hover { + --local-border-color: var(--woly-shape-hover); + } + + &:active > label > [data-checkbox]{ + --local-border-color: var(--woly-shape-active); + } + + &:focus > label > [data-checkbox]{ + box-shadow: 0 0 0 var(--woly-border-width) var(--woly-focus); + } + + &[data-disabled='true'] { + pointer-events: none; + + [data-checkbox] { + --local-border-color: var(--woly-shape-disabled); + } + + input:checked + [data-checkbox] { + --local-border-color: var(--woly-shape-disabled); + --local-icon-fill: var(--woly-shape-disabled); + } + + [data-text] { + --local-color-text: var(--woly-canvas-text-disabled); + } + } + + [data-text] { + font-size: var(--woly-font-size); + line-height: var(--woly-line-height); + color: var(--local-color-text); + } +` as StyledComponent<'div', Record, RadioButtonProps & Variant>; diff --git a/src/ui/molecules/radio-button/usage.mdx b/src/ui/molecules/radio-button/usage.mdx new file mode 100644 index 00000000..f8155885 --- /dev/null +++ b/src/ui/molecules/radio-button/usage.mdx @@ -0,0 +1,153 @@ +--- +name: radio-button +category: molecules +package: 'woly' +--- + +import { RadioButton } from './index'; +import { Playground, State } from 'box-styles'; + +The `RadioButton` component lets you add a radio button and assign it to a radio group. +Users can select only one radio button at a time within a radio group. + +### Radio button group. + +Radio buttons are used for mutually exclusive choices, not for multiple choices. +Only one radio button can be selected at a time. +When a user chooses a new item, the previous choice is automatically deselected. + +### Example + + + !i}> + {(value, change) => ( + <> + + + + )} + + + +### Disabled + +Disabled radio button are uneditable. They have less opacity so that they appear less tappable. + + + !i}> + {(value, change) => ( + + )} + + + +### Variants + +Primary and danger variants are should be used to focus user attention. + + + !i}> + {(value, change) => ( + <> + + + + )} + + + + + !i}> + {(value, change) => ( + <> + + + + )} + + + +Secondary variant should be used as default variant + + + !i}> + {(value, change) => ( + <> + + + + )} + + +### Kinds + +| Name | Description | +| ------------- | ------------------------------------------------------------------ | +| `RadioButton` | Base radio-button. Useful for creating a new kind of radio-buttons | + +### Props + +| Name | Type | Default | Description | +| ---------- | -------------------------------------------- | ----------- | ------------------------------------------------- | +| `checked` | `boolean` | | Whether radio-button is checked or not | +| `disabled` | `boolean` | | Disable radio-button | +| `id` | `string` | | HTML id attribute | +| `onChange` | `React.ChangeEventHandler` | | Callback when radio-button is clicked | +| `variant` | `string` | `'default'` | Variant prop to style RadioButton component | +| `...` | `HTMLInputElement` | `{}` | Other props are inherited from `HTMLInputElement` |