Skip to content

Commit

Permalink
Merge pull request #9 from ubie-oss/add-inputs
Browse files Browse the repository at this point in the history
add form elements
  • Loading branch information
takanorip committed Oct 2, 2023
2 parents 50f540d + 817a3aa commit 07d1aa1
Show file tree
Hide file tree
Showing 14 changed files with 452 additions and 22 deletions.
76 changes: 65 additions & 11 deletions src/components/Checkbox/Checkbox.module.css
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
.container {
display: flex;
align-items: center;
gap: var(--size-spacing-xs);
user-select: none;
font-size: 12px;
cursor: pointer;
}

.container:has(input:disabled) {
cursor: initial;
}

.checkbox {
Expand All @@ -14,29 +21,76 @@
}

.checkIconContainer {
display: inline-block;
width: 16px;
height: 16px;
margin-right: 6px;
border: 2px solid var(--color-primary);
border-radius: 4px;
font-size: 12px;
display: flex;
align-items: center;
justify-content: center;
border: 2px solid var(--color-ubie-black-400);
border-radius: var(--radius-sm);
transition: background-color 200ms;
box-sizing: border-box;
}

.container.medium {
font-size: var(--text-body-md-tight-size);
line-height: var(--text-body-md-tight-line);
}

.container.small {
font-size: var(--text-body-sm-tight-size);
line-height: var(--text-body-sm-tight-line);
}

.medium .checkIconContainer {
width: 1.5rem;
height: 1.5rem;
}

.small .checkIconContainer {
width: 1.25rem;
height: 1.25rem;
}

@media (pointer: fine) {
.container:hover .checkIconContainer {
border-color: var(--color-primary);
}

.container:hover input:disabled + .checkIconContainer {
border-color: var(--color-text-disabled);
}
}

.checkIcon {
visibility: hidden;
color: #fff;
}

.container input:checked + .checkIconContainer {
.medium .checkIcon {
font-size: 1.25rem;
}

.small .checkIcon {
font-size: 1rem;
}

input:checked + .checkIconContainer {
background-color: var(--color-primary);
border-color: var(--color-primary);
}

.container input:checked + .checkIconContainer .checkIcon {
input:checked + .checkIconContainer .checkIcon {
visibility: initial;
}

.container input:focus-visible + .checkIconContainer {
input:focus-visible + .checkIconContainer {
outline: solid var(--color-accent) 2px;
}

input:disabled + .checkIconContainer {
border-color: var(--color-text-disabled);
}

input:disabled:checked + .checkIconContainer {
border: none;
background-color: var(--color-text-disabled);
}
14 changes: 8 additions & 6 deletions src/components/Checkbox/Checkbox.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
'use client';

import { CheckAIcon } from '@ubie/ubie-icons';
import { FC, ReactNode } from 'react';
import clsx from 'clsx';
import { FC, InputHTMLAttributes } from 'react';
import styles from './Checkbox.module.css';

type Props = {
children: ReactNode;
};
size?: 'medium' | 'small';
} & Required<Pick<InputHTMLAttributes<HTMLInputElement>, 'value' | 'children'>> &
Omit<InputHTMLAttributes<HTMLInputElement>, 'size'>;

export const Checkbox: FC<Props> = ({ children }) => {
export const Checkbox: FC<Props> = ({ size = 'medium', children, ...otherProps }) => {
return (
<label className={styles.container}>
<input type="checkbox" className={styles.checkbox} />
<label className={clsx(styles.container, styles[size])}>
<input type="checkbox" className={styles.checkbox} {...otherProps} />
<span className={styles.checkIconContainer}>
<CheckAIcon className={styles.checkIcon} />
</span>
Expand Down
90 changes: 90 additions & 0 deletions src/components/RadioButton/RadioButton.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
.icon {
position: relative;
background-color: var(--color-background-white);
border: 2px solid var(--color-ubie-black-400);
border-radius: 24px;
box-sizing: border-box;
flex: none;
}

.medium .icon {
width: 1.5rem;
height: 1.5rem;
}

.small .icon {
width: 1.25rem;
height: 1.25rem;
}

@media (pointer: fine) {
.label:hover .icon {
border-color: var(--color-primary);
}
}

.icon::before {
position: absolute;
top: 50%;
left: 50%;
content: '';
border-radius: 16px;
transform: translate(-50%, -50%);
}

.medium .icon::before {
width: 1rem;
height: 1rem;
}

.small .icon::before {
width: 0.75rem;
height: 0.75rem;
}

.radio {
position: absolute;
opacity: 0;
}

.radio:checked + label .icon {
border-color: var(--color-primary);
}

.radio:checked + label .icon::before {
background: var(--color-primary);
}

.radio:disabled + label {
color: var(--color-text-disabled);
}

.radio:disabled + label > .icon {
border-color: var(--color-text-disabled);
}

.radio:checked:disabled + label > .icon::before {
background: var(--color-text-disabled);
}

/* 詳細度を上げるために: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;
cursor: pointer;
}

.medium .label {
font-size: var(--text-body-md-tight-size);
line-height: var(--text-body-md-tight-line);
}

.small .label {
font-size: var(--text-body-sm-tight-size);
line-height: var(--text-body-sm-tight-line);
}
42 changes: 42 additions & 0 deletions src/components/RadioButton/RadioButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import clsx from 'clsx';
import { FC, InputHTMLAttributes } from 'react';
import styles from './RadioButton.module.css';

type RadioProps = Required<
Pick<InputHTMLAttributes<HTMLInputElement>, 'id' | 'name' | 'onChange' | 'value' | 'checked' | 'children'>
> &
Omit<InputHTMLAttributes<HTMLInputElement>, 'size'>;

type Props = {
size?: 'medium' | 'small';
} & RadioProps;

export const RadioButton: FC<Props> = ({
size = 'medium',
checked,
onChange,
value,
id,
name,
children,
...otherProps
}) => {
return (
<div className={clsx(styles.container, styles[size])}>
<input
type="radio"
id={id}
checked={checked}
name={name}
value={value}
className={styles.radio}
onChange={onChange}
{...otherProps}
/>
<label htmlFor={id} className={styles.label}>
<span className={styles.icon} />
{children}
</label>
</div>
);
};
65 changes: 65 additions & 0 deletions src/components/Select/Select.module.css
Original file line number Diff line number Diff line change
@@ -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);
}
27 changes: 27 additions & 0 deletions src/components/Select/Select.tsx
Original file line number Diff line number Diff line change
@@ -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.css';

type Props = {
isInvalid?: boolean;
disabled?: boolean;
className?: string;
} & InputHTMLAttributes<HTMLSelectElement>;

const Select: FC<Props> = forwardRef<HTMLSelectElement, Props>(
({ isInvalid = false, disabled = false, children, className, ...props }, ref) => {
return (
<div className={clsx({ [styles.isInvalid]: isInvalid, [styles.disabled]: disabled }, styles.wrapper, className)}>
<select {...props} disabled={disabled} className={styles.select} ref={ref}>
{children}
</select>
<UnfoldMoreIcon aria-hidden className={styles.icon} />
</div>
);
},
);

Select.displayName = 'Select';

export { Select };
29 changes: 29 additions & 0 deletions src/components/TextArea/TextArea.module.css
Original file line number Diff line number Diff line change
@@ -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);
}
13 changes: 13 additions & 0 deletions src/components/TextArea/TextArea.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import clsx from 'clsx';
import { FC, TextareaHTMLAttributes } from 'react';
import styles from './TextArea.module.css';

type Props = {
isInvalid?: boolean;
} & TextareaHTMLAttributes<HTMLTextAreaElement>;

export const TextArea: FC<Props> = ({ isInvalid = false, className, ...props }) => {
const _className = clsx({ [styles.isInvalid]: isInvalid }, styles.textArea, className);

return <textarea {...props} className={_className} />;
};
Loading

0 comments on commit 07d1aa1

Please sign in to comment.