diff --git a/src/lib/box-styles.tsx b/src/lib/box-styles.tsx index 52716796..119377cf 100644 --- a/src/lib/box-styles.tsx +++ b/src/lib/box-styles.tsx @@ -16,7 +16,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; @@ -42,7 +42,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 2180ffa9..bc33bf15 100644 --- a/src/ui/atoms/input/index.tsx +++ b/src/ui/atoms/input/index.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; import styled, { StyledComponent } from 'styled-components'; import { Variant } from 'lib/types'; + import { InputContainer, InputElement } from '../../elements/quarks'; interface InputProps extends React.InputHTMLAttributes { @@ -52,12 +53,11 @@ export const Input = styled(InputBase)` --local-horizontal: calc( var(--woly-const-m) + (1px * var(--woly-main-level)) + var(--local-vertical) ); - + box-sizing: border-box; width: 100%; &[data-disabled='true'] { pointer-events: none; } - ` as StyledComponent<'div', Record, InputProps & Variant>; diff --git a/src/ui/atoms/input/usage.mdx b/src/ui/atoms/input/usage.mdx index ca4c7249..cae97f89 100644 --- a/src/ui/atoms/input/usage.mdx +++ b/src/ui/atoms/input/usage.mdx @@ -14,14 +14,14 @@ import { ButtonIcon } from '../../atoms'; `Input` is a field to accept data from the user. Input is used to create form fields that accept user input. They typically appear in forms and dialogs. -Inputs requiring certain data formats such as numbers, e-mail addresses or passwords are declared accordingly. +Inputs requiring certain data formats such as numbers, e-mail addresses or passwords are declared accordingly. This makes it easier for the user to enter information using a virtual keyboard. When a input is active or contains an error, the input’s border color and thickness vary. ## Placeholder text (Hint text) -Placeholder text rests in the input field until the user starts entering text. +Placeholder text rests in the input field until the user starts entering text. It may contain an action or an example, such as a phone number or email address. ## Helper text @@ -35,10 +35,9 @@ Specs: - Left justified - On a single line if possible, or with text wrapping (if multiple lines) - ## Error message -When input isn’t accepted, text fields can display an error message below the input line, with instructions on how to fix the error. +When input isn’t accepted, text fields can display an error message below the input line, with instructions on how to fix the error. Until the error is fixed, the error replaces the helper text. An error message should appear on a single line, if possible. @@ -52,7 +51,6 @@ Specs: - Right justified - Displayed as a ratio of characters used and the character limit (formatted as: characters used / character limit) - ### Example @@ -69,7 +67,7 @@ Specs: Disabled text fields are uneditable. They have less opacity so that they appear less tappable. - + -### Icons +### Icons Inputs can be combined with an icon on the left side or the right. Use icons on the both sides is not recommended. @@ -121,10 +119,10 @@ Inputs can be combined with an icon on the left side or the right. Use icons on /> -Icons can also be touch targets for nested components. +Icons can also be touch targets for nested components. - console.info('On input change')} variant="primary" /> - } type="text" name="name" @@ -162,9 +160,10 @@ Icons can also be touch targets for nested components. ### Variants Primary and secondary variants are should be used to focus user attention. + - + } name="name" @@ -185,7 +184,6 @@ Primary and secondary variants are should be used to focus user attention. /> - Error variant can be used to focus user attention on error. @@ -211,7 +209,7 @@ Size controlled by the `component-level` block property not from the props. placeholder="Enter your name here" type="text" variant="primary" - /> + /> + /> + /> + /> + /> @@ -265,10 +263,10 @@ Inputs can be placed inside the container. Input width is equal to container siz placeholder="Enter your name here" variant="primary" onChange={(event) => console.info('On input change')} - /> + /> - } type="text" name="name" @@ -288,7 +286,6 @@ Inputs can be placed inside the container. Input width is equal to container siz - ### Kinds | Name | Description | @@ -297,15 +294,15 @@ Inputs can be placed inside the container. Input width is equal to container siz ### Props -| 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` | +| 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 971d3570..8f5215c7 100644 --- a/src/ui/elements/quarks/input-container/index.tsx +++ b/src/ui/elements/quarks/input-container/index.tsx @@ -2,8 +2,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 { +interface InputContainerProps extends React.InputHTMLAttributes { className?: string; disabled?: boolean; leftIcon?: React.ReactNode; @@ -20,12 +19,10 @@ const InputContainerBase: React.FC = ({ rightIcon, variant = 'default', }) => { - const tabIndex = disabled ? -1 : 0; const onKeyDown = React.useCallback( (event: React.KeyboardEvent) => { - if (event.key === 'Enter') { event.preventDefault(); } @@ -56,8 +53,7 @@ const InputContainerBase: React.FC = ({ {rightIcon && {rightIcon}} ); -} - +}; export const InputContainer = styled(InputContainerBase)` --local-vertical: calc(1px * var(--woly-component-level) * var(--woly-main-level)); @@ -88,13 +84,13 @@ export const InputContainer = styled(InputContainerBase)` border: var(--woly-border-width) solid var(--local-border-color); border-radius: var(--woly-rounding); - [data-input="input"] { + [data-input='input'] { flex: 1; color: var(--local-value-color); - + padding: 0 var(--local-horizontal); - input{ + input { padding: 0; } @@ -105,7 +101,7 @@ export const InputContainer = styled(InputContainerBase)` [data-icon] { --local-icon-size: var(--woly-line-height); - + display: flex; align-items: center; justify-content: center; @@ -113,9 +109,9 @@ export const InputContainer = styled(InputContainerBase)` width: var(--local-icon-size); height: var(--local-icon-size); - svg > path { - fill: var(--local-icon-fill); - } + svg > path { + fill: var(--local-icon-fill); + } } [data-icon='left'] { @@ -126,8 +122,8 @@ export const InputContainer = styled(InputContainerBase)` padding: 0 calc(var(--local-horizontal) - var(--local-compensate)) 0 0; } - [data-icon='left'] ~ [data-input="input"], - [data-input="input"] ~ [data-icon='right'] { + [data-icon='left'] ~ [data-input='input'], + [data-input='input'] ~ [data-icon='right'] { padding-left: var(--local-gap); } @@ -146,7 +142,7 @@ export const InputContainer = styled(InputContainerBase)` &:active { --local-border-color: var(--woly-focus); - + [data-icon] { --local-icon-fill: var(--woly-canvas-text-default); } @@ -159,8 +155,4 @@ export const InputContainer = styled(InputContainerBase)` --local-border-color: var(--woly-shape-disabled); --local-value-color: var(--woly-canvas-text-disabled); } -` as StyledComponent< - 'div', - Record, - InputContainerProps & Variant ->; +` as StyledComponent<'div', Record, InputContainerProps & Variant>; diff --git a/src/ui/elements/quarks/input-element/index.tsx b/src/ui/elements/quarks/input-element/index.tsx index ae2527cb..4ea6fca0 100644 --- a/src/ui/elements/quarks/input-element/index.tsx +++ b/src/ui/elements/quarks/input-element/index.tsx @@ -1,8 +1,7 @@ import * as React from 'react'; import styled, { StyledComponent } from 'styled-components'; -interface InputElementProps - extends React.InputHTMLAttributes { +interface InputElementProps extends React.InputHTMLAttributes { className?: string; name: string; onChange: React.EventHandler; diff --git a/src/ui/molecules/index.ts b/src/ui/molecules/index.ts index 844d44cb..16ccf839 100644 --- a/src/ui/molecules/index.ts +++ b/src/ui/molecules/index.ts @@ -1,6 +1,7 @@ export { Checkbox } from './checkbox'; export { Field } from './field'; export { InputPassword } from './input-password'; +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/index.tsx b/src/ui/molecules/input-password/index.tsx index fa9f7b36..e1a940e3 100644 --- a/src/ui/molecules/input-password/index.tsx +++ b/src/ui/molecules/input-password/index.tsx @@ -1,10 +1,10 @@ import * as React from 'react'; import styled, { StyledComponent } from 'styled-components'; -import { Variant } from 'lib/types'; - import { ButtonIcon, Input } from 'ui'; +import { ClosedEyeIcon, InfoIcon, OpenedEyeIcon } from 'icons'; +import { Variant } from 'lib/types'; import { block } from 'box-styles'; -import { ClosedEyeIcon, OpenedEyeIcon, InfoIcon } from 'icons'; + interface InputPasswordProps { className?: string; disabled?: boolean; @@ -53,19 +53,17 @@ export const InputPasswordBase: React.FC = ({ /> ); -} +}; -export const InputPassword = styled(InputPasswordBase)` +export const InputPassword = (styled(InputPasswordBase)` --local-gap: calc( - (1px * var(--woly-main-level)) + - (1px * var(--woly-main-level) * var(--woly-component-level)) + (1px * var(--woly-main-level)) + (1px * var(--woly-main-level) * var(--woly-component-level)) ); - + box-sizing: border-box; width: 100%; & > *:not(:first-child) { margin-left: var(--woly-gap); } - -` as unknown as StyledComponent<'div', Record, InputPasswordProps & Variant>; +` as unknown) as StyledComponent<'div', Record, InputPasswordProps & Variant>; diff --git a/src/ui/molecules/input-password/usage.mdx b/src/ui/molecules/input-password/usage.mdx index 26d00365..d1c6fe49 100644 --- a/src/ui/molecules/input-password/usage.mdx +++ b/src/ui/molecules/input-password/usage.mdx @@ -16,7 +16,7 @@ InputPassword can be used in an authorization form or to confirm user action. ### Example - !i}> + !i}> {(value, change) => ( !i}> - {(value, change) => ( - console.info('On input change')} - variant="primary" - /> - )} - + {(value, change) => ( + console.info('On input change')} + variant="primary" + /> + )} + !i}> - {(value, change) => ( - console.info('On input change')} - variant="primary" - /> - )} - + {(value, change) => ( + console.info('On input change')} + variant="primary" + /> + )} + !i}> - {(value, change) => ( - console.info('On input change')} - variant="primary" - /> - )} - + {(value, change) => ( + console.info('On input change')} + variant="primary" + /> + )} + !i}> - {(value, change) => ( - console.info('On input change')} - variant="primary" - /> - )} - + {(value, change) => ( + console.info('On input change')} + variant="primary" + /> + )} + !i}> - {(value, change) => ( - console.info('On input change')} - variant="primary" - /> - )} - + {(value, change) => ( + console.info('On input change')} + variant="primary" + /> + )} + @@ -174,17 +174,17 @@ Inputs can be placed inside the container. Input width is equal to container siz !i}> - {(value, change) => ( - console.info('On input change')} - variant="primary" - /> - )} - + {(value, change) => ( + console.info('On input change')} + variant="primary" + /> + )} + @@ -196,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` | `'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` | +| 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` | diff --git a/src/ui/molecules/switch/index.tsx b/src/ui/molecules/switch/index.tsx index f81a7f0e..940254d2 100644 --- a/src/ui/molecules/switch/index.tsx +++ b/src/ui/molecules/switch/index.tsx @@ -24,7 +24,6 @@ const SwitchBase: React.FC = ({ const onKeyDown = React.useCallback( (event: React.KeyboardEvent) => { - if (event.key === 'Enter') { event.preventDefault(); }