Skip to content

Commit cf0736c

Browse files
authored
feat(frontend): Input Radio Component (#151)
1 parent 6e6350c commit cf0736c

File tree

3 files changed

+99
-0
lines changed

3 files changed

+99
-0
lines changed

Diff for: frontend/app/components/input-radio.tsx

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import type { ReactNode } from 'react';
2+
3+
import { cn } from '~/utils/tailwind-utils';
4+
5+
const inputBaseClassName =
6+
'h-4 w-4 border-gray-500 bg-gray-50 text-blue-600 focus:outline-hidden focus:ring-2 focus:ring-blue-500';
7+
const inputDisabledClassName = 'pointer-events-none cursor-not-allowed opacity-70';
8+
const inputErrorClassName = 'border-red-500 text-red-700 focus:border-red-500 focus:ring-red-500';
9+
const inputReadOnlyClassName = 'pointer-events-none cursor-not-allowed opacity-70';
10+
11+
export interface InputRadioProps extends OmitStrict<React.ComponentProps<'input'>, 'aria-labelledby' | 'children' | 'type'> {
12+
append?: ReactNode;
13+
appendClassName?: string;
14+
children: ReactNode;
15+
hasError?: boolean;
16+
id: string;
17+
inputClassName?: string;
18+
labelClassName?: string;
19+
name: string;
20+
}
21+
22+
export function InputRadio({
23+
append,
24+
appendClassName,
25+
children,
26+
className,
27+
hasError,
28+
id,
29+
inputClassName,
30+
labelClassName,
31+
...restProps
32+
}: InputRadioProps) {
33+
const inputRadioId = `input-radio-${id}`;
34+
const inputLabelId = `${inputRadioId}-label`;
35+
return (
36+
<div className={className}>
37+
<div className="flex items-center">
38+
<input
39+
type="radio"
40+
id={inputRadioId}
41+
aria-labelledby={inputLabelId}
42+
className={cn(
43+
inputBaseClassName,
44+
restProps.readOnly && inputReadOnlyClassName,
45+
restProps.disabled && inputDisabledClassName,
46+
hasError && inputErrorClassName,
47+
inputClassName,
48+
)}
49+
data-testid="input-radio"
50+
{...restProps}
51+
/>
52+
<label
53+
id={inputLabelId}
54+
htmlFor={inputRadioId}
55+
className={cn(
56+
'block pl-3 leading-6',
57+
restProps.readOnly && inputReadOnlyClassName,
58+
restProps.disabled && inputDisabledClassName,
59+
labelClassName,
60+
)}
61+
>
62+
{children}
63+
</label>
64+
</div>
65+
{append && <div className={cn('mt-4 ml-7', appendClassName)}>{append}</div>}
66+
</div>
67+
);
68+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2+
3+
exports[`InputRadio > disables radio button when disabled prop is provided > expected html 1`] = `"<div><div class="flex items-center"><input id="input-radio-test-radio" aria-labelledby="input-radio-test-radio-label" class="h-4 w-4 border-gray-500 bg-gray-50 text-blue-600 focus:outline-hidden focus:ring-2 focus:ring-blue-500 pointer-events-none cursor-not-allowed opacity-70" data-testid="input-radio" disabled="" type="radio" name="test-radio"><label id="input-radio-test-radio-label" for="input-radio-test-radio" class="block pl-3 leading-6 pointer-events-none cursor-not-allowed opacity-70">Radio Label</label></div></div>"`;
4+
5+
exports[`InputRadio > renders radio button with label and appends content > expected html 1`] = `"<div><div class="flex items-center"><input id="input-radio-test-radio" aria-labelledby="input-radio-test-radio-label" class="h-4 w-4 border-gray-500 bg-gray-50 text-blue-600 focus:outline-hidden focus:ring-2 focus:ring-blue-500" data-testid="input-radio" type="radio" name="test-radio"><label id="input-radio-test-radio-label" for="input-radio-test-radio" class="block pl-3 leading-6">Radio Label</label></div><div class="mt-4 ml-7"><div data-testid="append-content">Appended Content</div></div></div>"`;

Diff for: frontend/tests/components/input-radio.test.tsx

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { render } from '@testing-library/react';
2+
import { describe, expect, it } from 'vitest';
3+
4+
import { InputRadio } from '~/components/input-radio';
5+
6+
describe('InputRadio', () => {
7+
it('renders radio button with label and appends content', () => {
8+
const labelText = 'Radio Label';
9+
const appendContent = <div data-testid="append-content">Appended Content</div>;
10+
const { container } = render(
11+
<InputRadio id="test-radio" name="test-radio" append={appendContent}>
12+
{labelText}
13+
</InputRadio>,
14+
);
15+
expect(container.innerHTML).toMatchSnapshot('expected html');
16+
});
17+
18+
it('disables radio button when disabled prop is provided', () => {
19+
const { container } = render(
20+
<InputRadio id="test-radio" name="test-radio" disabled>
21+
Radio Label
22+
</InputRadio>,
23+
);
24+
expect(container.innerHTML).toMatchSnapshot('expected html');
25+
});
26+
});

0 commit comments

Comments
 (0)