From e9f4dcb1f7e52c93666f3cf66d35fb2e34a1bbf9 Mon Sep 17 00:00:00 2001 From: takanorip Date: Wed, 27 Sep 2023 00:06:55 +0900 Subject: [PATCH 1/6] add form elements --- .../RadioButton/RadioButton.module.css | 43 ++++++++++++ src/components/RadioButton/RadioButton.tsx | 28 ++++++++ src/components/Select/Select.module.css | 65 +++++++++++++++++++ src/components/Select/Select.tsx | 27 ++++++++ src/components/TextArea/TextArea.module.css | 29 +++++++++ src/components/TextArea/TextArea.tsx | 13 ++++ 6 files changed, 205 insertions(+) create mode 100644 src/components/RadioButton/RadioButton.module.css create mode 100644 src/components/RadioButton/RadioButton.tsx create mode 100644 src/components/Select/Select.module.css create mode 100644 src/components/Select/Select.tsx create mode 100644 src/components/TextArea/TextArea.module.css create mode 100644 src/components/TextArea/TextArea.tsx diff --git a/src/components/RadioButton/RadioButton.module.css b/src/components/RadioButton/RadioButton.module.css new file mode 100644 index 00000000..a2edf0e0 --- /dev/null +++ b/src/components/RadioButton/RadioButton.module.css @@ -0,0 +1,43 @@ +.icon { + position: relative; + width: 24px; + height: 24px; + background-color: var(--color-background-white); + border: 2px solid var(--color-border); + border-radius: 24px; +} + +.icon::before { + position: absolute; + top: 50%; + left: 50%; + width: 16px; + height: 16px; + content: ''; + border-radius: 16px; + transform: translate(-50%, -50%); +} + +.radio { + position: absolute; + opacity: 0; +} + +.radio:checked + label .icon { + border-color: var(--color-primary); +} + +.radio:checked + label .icon::before { + background: var(--color-primary); +} + +/* 詳細度を上げるために:focus-visibleを複数指定 */ +.radio:focus-visible:focus-visible + label .icon { + border-color: var(--color-accent); +} + +.label { + display: flex; + gap: var(--size-spacing-xs); + align-items: center; +} diff --git a/src/components/RadioButton/RadioButton.tsx b/src/components/RadioButton/RadioButton.tsx new file mode 100644 index 00000000..4f5886e7 --- /dev/null +++ b/src/components/RadioButton/RadioButton.tsx @@ -0,0 +1,28 @@ +import { FC } from 'react'; +import styles from './RadioButton.module.scss'; + +type Props = Required< + Pick, 'id' | 'name' | 'onChange' | 'value' | 'checked' | 'children'> +> & + React.InputHTMLAttributes; + +export const RadioButton: FC = ({ checked, onChange, value, id, name, children, ...otherProps }) => { + return ( +
+ + +
+ ); +}; diff --git a/src/components/Select/Select.module.css b/src/components/Select/Select.module.css new file mode 100644 index 00000000..111a0dff --- /dev/null +++ b/src/components/Select/Select.module.css @@ -0,0 +1,65 @@ +.wrapper { + position: relative; +} + +.select { + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + min-height: 54px; + padding: 0 64px 0 16px; + font-size: 16px; + font-weight: bold; + color: var(--color-text-main); + border: none; + border: 2px solid var(--color-border); + border-radius: 8px; + transition: border-color 0.3s var(--ease-out-quint); + appearance: none; + caret-color: var(--color-primary); +} + +.select::placeholder { + color: var(--color-text-placeholder); +} + +.select:focus { + border-color: var(--color-primary); +} + +@media (pointer: fine) { + .select:hover { + border: 2px solid var(--color-primary); + } +} + +.icon { + position: absolute; + top: 50%; + right: 16px; + width: 24px; + height: 24px; + color: var(--color-primary); + pointer-events: none; + transform: translateY(-50%); +} + +.disabled .select { + color: var(--color-text-sub); + background-color: var(--color-background-gray); + border-color: var(--color-border); +} + +.disabled .icon { + color: var(--color-text-sub); +} + +.isInvalid .select { + background: var(--color-background-accent-darken); + border-color: var(--color-alert-darken); +} + +.isInvalid .icon { + color: var(--color-alert-darken); +} diff --git a/src/components/Select/Select.tsx b/src/components/Select/Select.tsx new file mode 100644 index 00000000..bfbed5eb --- /dev/null +++ b/src/components/Select/Select.tsx @@ -0,0 +1,27 @@ +import { UnfoldMoreIcon } from '@ubie/ubie-icons'; +import clsx from 'clsx'; +import { FC, forwardRef, InputHTMLAttributes } from 'react'; +import styles from './Select.module.scss'; + +type Props = { + isInvalid?: boolean; + disabled?: boolean; + className?: string; +} & InputHTMLAttributes; + +const Select: FC = forwardRef( + ({ isInvalid = false, disabled = false, children, className, ...props }, ref) => { + return ( +
+ + +
+ ); + }, +); + +Select.displayName = 'Select'; + +export { Select }; diff --git a/src/components/TextArea/TextArea.module.css b/src/components/TextArea/TextArea.module.css new file mode 100644 index 00000000..747ad7b8 --- /dev/null +++ b/src/components/TextArea/TextArea.module.css @@ -0,0 +1,29 @@ +.textArea { + width: 100%; + padding: var(--size-spacing-md) var(--size-spacing-sm) 0.875rem; + font-size: var(--text-body-md-narrow-size); + font-weight: bold; + line-height: var(--text-body-md-narrow-line); + color: var(--color-text-main); + text-align: inherit; + background-color: var(--color-background-gray); + border: none; + border-bottom: 2px solid var(--color-border); + border-radius: 8px 8px 0 0; + appearance: none; + caret-color: var(--color-primary); +} + +.textArea::placeholder { + color: var(--color-text-placeholder); +} + +.textArea:focus { + border-bottom: 2px solid var(--color-primary); + outline: none; +} + +.textArea.isInvalid { + background: var(--color-background-accent-darken); + border-bottom: 2px solid var(--color-alert-darken); +} diff --git a/src/components/TextArea/TextArea.tsx b/src/components/TextArea/TextArea.tsx new file mode 100644 index 00000000..a4965efa --- /dev/null +++ b/src/components/TextArea/TextArea.tsx @@ -0,0 +1,13 @@ +import clsx from 'clsx'; +import { FC, TextareaHTMLAttributes } from 'react'; +import styles from './TextArea.module.scss'; + +type Props = { + isInvalid?: boolean; +} & TextareaHTMLAttributes; + +export const TextArea: FC = ({ isInvalid = false, className, ...props }) => { + const _className = clsx({ [styles.isInvalid]: isInvalid }, styles.textArea, className); + + return