Skip to content

Commit 5c095ba

Browse files
authored
Merge pull request #194 from digital-go-jp/update-v2-20250423
Update v2 20250423
2 parents c321177 + dd2d11d commit 5c095ba

25 files changed

+1135
-56
lines changed

src/components/DatePicker/DatePicker.stories.tsx

Lines changed: 406 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { type ComponentProps, type KeyboardEvent, type Ref, useRef } from 'react';
2+
3+
export type DatePickerSize = 'lg' | 'md' | 'sm';
4+
5+
export type DatePickerProps = Omit<ComponentProps<'div'>, 'children'> & {
6+
size?: DatePickerSize;
7+
isError?: boolean;
8+
isDisabled?: boolean;
9+
children: (props: {
10+
yearRef: Ref<HTMLInputElement>;
11+
monthRef: Ref<HTMLInputElement>;
12+
dateRef: Ref<HTMLInputElement>;
13+
}) => JSX.Element;
14+
};
15+
16+
export const DatePicker = (props: DatePickerProps) => {
17+
const { className, size = 'lg', isError, isDisabled, children, ...rest } = props;
18+
19+
const yearRef = useRef<HTMLInputElement>(null);
20+
const monthRef = useRef<HTMLInputElement>(null);
21+
const dateRef = useRef<HTMLInputElement>(null);
22+
23+
function handleKeyDown(event: KeyboardEvent<HTMLInputElement>) {
24+
if (event.key === 'ArrowRight') {
25+
moveRight(event);
26+
} else if (event.key === 'ArrowLeft') {
27+
moveLeft(event);
28+
} else if (event.key.match(/^[^0-9]$/)) {
29+
if (!event.ctrlKey && !event.metaKey) {
30+
event.preventDefault();
31+
}
32+
}
33+
}
34+
35+
function moveRight(event: KeyboardEvent<HTMLInputElement>) {
36+
const input = event.target as HTMLInputElement;
37+
if (input.selectionStart !== input.selectionEnd) {
38+
return;
39+
}
40+
if (input.selectionEnd === input.value.length) {
41+
event.preventDefault();
42+
if (input === yearRef.current) {
43+
monthRef.current?.focus();
44+
} else if (input === monthRef.current) {
45+
dateRef.current?.focus();
46+
}
47+
}
48+
}
49+
50+
function moveLeft(event: KeyboardEvent<HTMLInputElement>) {
51+
const input = event.target as HTMLInputElement;
52+
if (input.selectionStart !== input.selectionEnd) {
53+
return;
54+
}
55+
if (input.selectionStart === 0) {
56+
event.preventDefault();
57+
if (input === monthRef.current) {
58+
yearRef.current?.focus();
59+
} else if (input === dateRef.current) {
60+
monthRef.current?.focus();
61+
}
62+
}
63+
}
64+
65+
return (
66+
<div
67+
className={`inline-flex h-14 -space-x-1 rounded-8 border border-solid-gray-600 bg-[--bg] p-0.5 pe-0 text-solid-gray-900 [--bg:theme(colors.white)] focus-within:border-black hover:border-solid-gray-900 data-[size=md]:h-12 data-[size=sm]:h-10 data-[disabled]:border-solid-gray-300 data-[error]:border-error-1 data-[disabled]:text-solid-gray-420 data-[disabled]:[--bg:theme(colors.solid-gray.50)] data-[error]:focus-within:border-red-1000 data-[error]:hover:border-red-1000 forced-colors:data-[disabled]:border-[GrayText] forced-colors:data-[disabled]:text-[GrayText] ${className ?? ''}`}
68+
data-size={size}
69+
data-error={isError || null}
70+
data-disabled={isDisabled || null}
71+
onKeyDown={handleKeyDown}
72+
{...rest}
73+
>
74+
{children({ yearRef, monthRef, dateRef })}
75+
</div>
76+
);
77+
};

src/components/DatePicker/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export * from './DatePicker';
2+
export * from './parts/DatePickerCalendarButton';
3+
export * from './parts/DatePickerDate';
4+
export * from './parts/DatePickerMonth';
5+
export * from './parts/DatePickerYear';
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { type ComponentProps, forwardRef } from 'react';
2+
import type { DatePickerSize } from '../DatePicker';
3+
4+
export type DatePickerCalendarButtonProps = ComponentProps<'button'> & {
5+
size?: DatePickerSize;
6+
};
7+
8+
export const DatePickerCalendarButton = forwardRef<
9+
HTMLButtonElement,
10+
DatePickerCalendarButtonProps
11+
>((props, ref) => {
12+
const { className, size = 'lg', ...rest } = props;
13+
14+
return (
15+
<button
16+
className={`group flex h-14 items-center justify-center gap-x-1 rounded-6 border border-blue-900 bg-white px-3 text-blue-900 hover:border-[calc(3/16*1rem)] hover:px-2.5 focus-visible:outline focus-visible:outline-4 focus-visible:outline-offset-[calc(2/16*1rem)] focus-visible:outline-black focus-visible:ring-[calc(2/16*1rem)] focus-visible:ring-yellow-300 data-[size=md]:h-12 data-[size=sm]:h-10 ${className ?? ''}`}
17+
type='button'
18+
data-size={size}
19+
ref={ref}
20+
{...rest}
21+
>
22+
<svg width='24px' height='24px' viewBox='0 -960 960 960' role='img' aria-label='カレンダー'>
23+
<path
24+
d='M360-300q-42 0-71-29t-29-71q0-42 29-71t71-29q42 0 71 29t29 71q0 42-29 71t-71 29ZM200-80q-33 0-56.5-23.5T120-160v-560q0-33 23.5-56.5T200-800h40v-80h80v80h320v-80h80v80h40q33 0 56.5 23.5T840-720v560q0 33-23.5 56.5T760-80H200Zm0-80h560v-400H200v400Z'
25+
fill='currentcolor'
26+
/>
27+
</svg>
28+
<svg className='size-4 group-aria-expanded:rotate-180' viewBox='0 0 24 24' aria-hidden={true}>
29+
<path d='M12 17.1L3 8L4 7L12 15L20 7L21 8L12 17.1Z' fill='currentcolor' />
30+
</svg>
31+
</button>
32+
);
33+
});
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { type ComponentProps, forwardRef } from 'react';
2+
3+
export type DatePickerDateProps = ComponentProps<'input'> & {};
4+
5+
export const DatePickerDate = forwardRef<HTMLInputElement, DatePickerDateProps>((props, ref) => {
6+
const { className, 'aria-disabled': disabled, readOnly, ...rest } = props;
7+
8+
return (
9+
<label className='relative z-0 inline-flex flex-row-reverse last:pe-4 [&:has([aria-disabled="true"])]:pointer-events-none'>
10+
<span className='relative z-10 self-center bg-[--bg] p-1 text-oln-16N-100'></span>
11+
<input
12+
className={`-me-1 w-11 rounded-8 border border-transparent bg-transparent pe-3 text-right focus:border-solid-gray-600 focus:outline focus:outline-4 focus:outline-offset-[calc(2/16*1rem)] focus:outline-black focus:ring-[calc(2/16*1rem)] focus:ring-yellow-300 aria-disabled:pointer-events-none forced-colors:border-[Canvas] forced-colors:aria-disabled:focus:border-[GrayText] ${className ?? ''}`}
13+
type='text'
14+
inputMode='numeric'
15+
pattern='\d+'
16+
readOnly={disabled ? true : readOnly}
17+
aria-disabled={disabled}
18+
ref={ref}
19+
{...rest}
20+
/>
21+
</label>
22+
);
23+
});
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { type ComponentProps, forwardRef } from 'react';
2+
3+
export type DatePickerMonthProps = ComponentProps<'input'> & {};
4+
5+
export const DatePickerMonth = forwardRef<HTMLInputElement, DatePickerMonthProps>((props, ref) => {
6+
const { className, 'aria-disabled': disabled, readOnly, ...rest } = props;
7+
8+
return (
9+
<label className='relative z-0 inline-flex flex-row-reverse last:pe-4 [&:has([aria-disabled="true"])]:pointer-events-none'>
10+
<span className='relative z-10 self-center bg-[--bg] p-1 text-oln-16N-100'></span>
11+
<input
12+
className={`-me-1 w-11 rounded-8 border border-transparent bg-transparent pe-3 text-right focus:border-solid-gray-600 focus:outline focus:outline-4 focus:outline-offset-[calc(2/16*1rem)] focus:outline-black focus:ring-[calc(2/16*1rem)] focus:ring-yellow-300 aria-disabled:pointer-events-none forced-colors:border-[Canvas] forced-colors:aria-disabled:focus:border-[GrayText] ${className ?? ''}`}
13+
type='text'
14+
inputMode='numeric'
15+
pattern='\d+'
16+
readOnly={disabled ? true : readOnly}
17+
aria-disabled={disabled}
18+
ref={ref}
19+
{...rest}
20+
/>
21+
</label>
22+
);
23+
});
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { type ComponentProps, forwardRef } from 'react';
2+
3+
export type DatePickerYearProps = ComponentProps<'input'> & {};
4+
5+
export const DatePickerYear = forwardRef<HTMLInputElement, DatePickerYearProps>((props, ref) => {
6+
const { className, 'aria-disabled': disabled, readOnly, ...rest } = props;
7+
8+
return (
9+
<label className='relative z-0 inline-flex flex-row-reverse last:pe-4 [&:has([aria-disabled="true"])]:pointer-events-none'>
10+
<span className='relative z-10 self-center bg-[--bg] p-1 text-oln-16N-100'></span>
11+
<input
12+
className={`-me-1 w-16 rounded-8 border border-transparent bg-transparent pe-3 text-right focus:border-solid-gray-600 focus:outline focus:outline-4 focus:outline-offset-[calc(2/16*1rem)] focus:outline-black focus:ring-[calc(2/16*1rem)] focus:ring-yellow-300 aria-disabled:pointer-events-none forced-colors:border-[Canvas] forced-colors:aria-disabled:focus:border-[GrayText] ${className ?? ''}`}
13+
type='text'
14+
inputMode='numeric'
15+
pattern='\d+'
16+
readOnly={disabled ? true : readOnly}
17+
aria-disabled={disabled}
18+
ref={ref}
19+
{...rest}
20+
/>
21+
</label>
22+
);
23+
});

src/components/Divider/Divider.tsx

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,21 @@ import type { ComponentProps } from 'react';
22

33
export type DividerColor = 'gray-420' | 'gray-536' | 'black';
44

5-
export const DividerColorStyle: { [key in DividerColor]: string } = {
6-
'gray-420': 'border-solid-gray-420',
7-
'gray-536': 'border-solid-gray-536',
8-
black: 'border-black',
9-
};
10-
115
export type DividerProps = ComponentProps<'hr'> & {
126
color?: DividerColor;
137
};
148

159
export const Divider = (props: DividerProps) => {
1610
const { className, color = 'gray-420', ...rest } = props;
1711

18-
return <hr className={`${DividerColorStyle[color]} ${className ?? ''}`} {...rest}></hr>;
12+
return (
13+
<hr
14+
className={`
15+
data-[color=gray-420]:border-solid-gray-420 data-[color=gray-536]:border-solid-gray-536 data-[color=black]:border-black
16+
${className ?? ''}
17+
`}
18+
data-color={color}
19+
{...rest}
20+
></hr>
21+
);
1922
};

src/components/Input/Input.tsx

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,6 @@ import { type ComponentProps, forwardRef } from 'react';
22

33
export type InputBlockSize = 'lg' | 'md' | 'sm';
44

5-
export const InputBlockSizeStyle: { [key in InputBlockSize]: string } = {
6-
// NOTE:
7-
// Tailwind CSS (v3.4.4) does not have any utility classes for logical properties of sizing.
8-
// Once it is officially released, we will replace them with classes like `bs-14`.
9-
lg: 'h-14',
10-
md: 'h-12',
11-
sm: 'h-10',
12-
};
13-
145
export type InputProps = ComponentProps<'input'> & {
156
isError?: boolean;
167
blockSize?: InputBlockSize;
@@ -22,13 +13,16 @@ export const Input = forwardRef<HTMLInputElement, InputProps>((props, ref) => {
2213
return (
2314
<input
2415
className={`
25-
max-w-full rounded-8 border bg-white px-4 py-3 text-oln-16N-100 text-solid-gray-800
26-
${InputBlockSizeStyle[blockSize]}
27-
${isError ? 'border-error-1' : 'border-solid-gray-900'}
16+
max-w-full rounded-8 border bg-white px-4 py-3 border-solid-gray-600 text-oln-16N-100 text-solid-gray-800
17+
hover:border-black
18+
data-[size=sm]:h-10 data-[size=md]:h-12 data-[size=lg]:h-14
19+
aria-[invalid=true]:border-error-1 aria-[invalid=true]:hover:border-red-1000
2820
focus:outline focus:outline-4 focus:outline-black focus:outline-offset-[calc(2/16*1rem)] focus:ring-[calc(2/16*1rem)] focus:ring-yellow-300
2921
aria-disabled:border-solid-gray-300 aria-disabled:bg-solid-gray-50 aria-disabled:text-solid-gray-420 aria-disabled:pointer-events-none aria-disabled:forced-colors:text-[GrayText] aria-disabled:forced-colors:border-[GrayText]
3022
${className ?? ''}
3123
`}
24+
aria-invalid={isError || undefined}
25+
data-size={blockSize}
3226
readOnly={props['aria-disabled'] ? true : readOnly}
3327
ref={ref}
3428
{...rest}

src/components/Label/Label.tsx

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,6 @@ import type { ComponentProps } from 'react';
22

33
export type LabelSize = 'lg' | 'md' | 'sm';
44

5-
export const labelSizeStyle: { [key in LabelSize]: string } = {
6-
lg: 'text-std-18B-160',
7-
md: 'text-std-17B-170',
8-
sm: 'text-std-16B-170',
9-
};
10-
115
export type LabelProps = ComponentProps<'label'> & {
126
size?: LabelSize;
137
};
@@ -20,9 +14,10 @@ export const Label = (props: LabelProps) => {
2014
<label
2115
className={`
2216
flex w-fit items-center gap-2 text-solid-gray-800
23-
${labelSizeStyle[size]}
17+
data-[size=sm]:text-std-16B-170 data-[size=md]:text-std-17B-170 data-[size=lg]:text-std-18B-160
2418
${className ?? ''}
2519
`}
20+
data-size={size}
2621
{...rest}
2722
>
2823
{children}

0 commit comments

Comments
 (0)