-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Dika Mahendra
committed
Mar 21, 2024
1 parent
36a4508
commit 977e46f
Showing
15 changed files
with
1,549 additions
and
120 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
# @jala-banyu/radio | ||
|
||
Radios allow users to select a single option from a list of mutually exclusive options. | ||
|
||
> This is an internal utility, not intended for public usage. | ||
## Installation | ||
|
||
```sh | ||
yarn add @jala-banyu/radio | ||
# or | ||
npm i @jala-banyu/radio | ||
``` | ||
|
||
## Contribution | ||
|
||
Yes please! See the | ||
[contributing guidelines](https://github.com/Atnic/banyu/blob/master/CONTRIBUTING.md) | ||
for details. | ||
|
||
## Licence | ||
|
||
This project is licensed under the terms of the | ||
[MIT license](https://github.com/Atnic/banyu/blob/master/LICENSE). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,198 @@ | ||
import * as React from "react"; | ||
import {act, render} from "@testing-library/react"; | ||
|
||
import {RadioGroup, Radio, RadioGroupProps} from "../src"; | ||
|
||
describe("Radio", () => { | ||
it("should render correctly", () => { | ||
const wrapper = render( | ||
<RadioGroup label="Options"> | ||
<Radio value="1">Option 1</Radio> | ||
</RadioGroup>, | ||
); | ||
|
||
expect(() => wrapper.unmount()).not.toThrow(); | ||
}); | ||
|
||
it("ref should be forwarded - group", () => { | ||
const ref = React.createRef<HTMLDivElement>(); | ||
|
||
render( | ||
<RadioGroup ref={ref} label="Options"> | ||
<Radio value="1">Option 1</Radio> | ||
</RadioGroup>, | ||
); | ||
expect(ref.current).not.toBeNull(); | ||
}); | ||
|
||
it("ref should be forwarded - option", () => { | ||
const ref = React.createRef<HTMLLabelElement>(); | ||
|
||
render( | ||
<RadioGroup label="Options"> | ||
<Radio ref={ref} value="1"> | ||
Option 1 | ||
</Radio> | ||
</RadioGroup>, | ||
); | ||
expect(ref.current).not.toBeNull(); | ||
}); | ||
|
||
it("should work correctly with initial value", () => { | ||
let {container} = render( | ||
<RadioGroup label="Options" value="1"> | ||
<Radio data-testid="radio-test-1" value="1"> | ||
Option 1 | ||
</Radio> | ||
</RadioGroup>, | ||
); | ||
|
||
expect(container.querySelector("[data-testid=radio-test-1] input")).toBeChecked(); | ||
|
||
let wrapper = render( | ||
<RadioGroup defaultValue="2" label="Options"> | ||
<Radio value="1">Option 1</Radio> | ||
<Radio data-testid="radio-test-2" value="2"> | ||
Option 1 | ||
</Radio> | ||
</RadioGroup>, | ||
); | ||
|
||
expect(wrapper.container.querySelector("[data-testid=radio-test-2] input")).toBeChecked(); | ||
}); | ||
|
||
it("should change value after click", () => { | ||
const {container} = render( | ||
<RadioGroup defaultValue="1" label="Options"> | ||
<Radio value="1">Option 1</Radio> | ||
<Radio className="radio-test-2" value="2"> | ||
Option 1 | ||
</Radio> | ||
</RadioGroup>, | ||
); | ||
|
||
let radio2 = container.querySelector(".radio-test-2 input") as HTMLInputElement; | ||
|
||
act(() => { | ||
radio2.click(); | ||
}); | ||
|
||
expect(radio2).toBeChecked(); | ||
}); | ||
|
||
it("should ignore events when disabled", () => { | ||
const {container} = render( | ||
<RadioGroup label="Options"> | ||
<Radio isDisabled className="radio-test-1" value="1"> | ||
Option 1 | ||
</Radio> | ||
<Radio value="2">Option 2</Radio> | ||
</RadioGroup>, | ||
); | ||
|
||
let radio1 = container.querySelector(".radio-test-1 input") as HTMLInputElement; | ||
|
||
act(() => { | ||
radio1.click(); | ||
}); | ||
|
||
expect(radio1).not.toBeChecked(); | ||
}); | ||
|
||
it('should work correctly with "onValueChange" prop', () => { | ||
const onValueChange = jest.fn(); | ||
|
||
const {container} = render( | ||
<RadioGroup defaultValue="1" label="Options" onValueChange={onValueChange}> | ||
<Radio value="1">Option 1</Radio> | ||
<Radio className="radio-test-2" value="2"> | ||
Option 2 | ||
</Radio> | ||
</RadioGroup>, | ||
); | ||
|
||
let radio2 = container.querySelector(".radio-test-2 input") as HTMLInputElement; | ||
|
||
act(() => { | ||
radio2.click(); | ||
}); | ||
|
||
expect(onValueChange).toBeCalledWith("2"); | ||
|
||
expect(radio2).toBeChecked(); | ||
}); | ||
|
||
it('should work correctly with "onFocus" prop', () => { | ||
const onFocus = jest.fn(); | ||
|
||
const {container} = render( | ||
<RadioGroup defaultValue="1" label="Options" onFocus={onFocus}> | ||
<Radio value="1">Option 1</Radio> | ||
<Radio className="radio-test-2" value="2"> | ||
Option 2 | ||
</Radio> | ||
</RadioGroup>, | ||
); | ||
|
||
let radio2 = container.querySelector(".radio-test-2 input") as HTMLInputElement; | ||
|
||
act(() => { | ||
radio2.focus(); | ||
}); | ||
|
||
expect(onFocus).toBeCalled(); | ||
}); | ||
|
||
it('should work correctly with "isRequired" prop', () => { | ||
const {container} = render( | ||
<RadioGroup isRequired label="Options"> | ||
<Radio value="1">Option 1</Radio> | ||
<Radio className="radio-test-2" value="2"> | ||
Option 2 | ||
</Radio> | ||
</RadioGroup>, | ||
); | ||
|
||
let radio2 = container | ||
.querySelector(".radio-test-2") | ||
?.querySelector("input") as HTMLInputElement; | ||
|
||
expect(radio2?.required).toBe(true); | ||
}); | ||
|
||
it("should work correctly with controlled value", () => { | ||
const onValueChange = jest.fn(); | ||
|
||
const Component = ({onValueChange}: Omit<RadioGroupProps, "value">) => { | ||
const [value, setValue] = React.useState("1"); | ||
|
||
return ( | ||
<RadioGroup | ||
label="Options" | ||
value={value} | ||
onValueChange={(next) => { | ||
setValue(next); | ||
onValueChange?.(next as any); | ||
}} | ||
> | ||
<Radio value="1">Option 1</Radio> | ||
<Radio className="radio-test-2" value="2"> | ||
Option 2 | ||
</Radio> | ||
</RadioGroup> | ||
); | ||
}; | ||
|
||
const {container} = render(<Component onValueChange={onValueChange} />); | ||
|
||
let radio2 = container.querySelector(".radio-test-2 input") as HTMLInputElement; | ||
|
||
act(() => { | ||
radio2.click(); | ||
}); | ||
|
||
expect(onValueChange).toBeCalled(); | ||
|
||
expect(radio2).toBeChecked(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
{ | ||
"name": "@jala-banyu/radio", | ||
"version": "0.0.0", | ||
"description": "Radios allow users to select a single option from a list of mutually exclusive options.", | ||
"keywords": [ | ||
"radio" | ||
], | ||
"author": "Dika Mahendra <[email protected]>", | ||
"homepage": "#", | ||
"license": "MIT", | ||
"main": "src/index.ts", | ||
"sideEffects": false, | ||
"files": [ | ||
"dist" | ||
], | ||
"publishConfig": { | ||
"access": "public" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "git+https://github.com/Atnic/banyu.git", | ||
"directory": "packages/components/radio" | ||
}, | ||
"bugs": { | ||
"url": "https://github.com/Atnic/banyu/issues" | ||
}, | ||
"scripts": { | ||
"build": "tsup src --dts", | ||
"build:fast": "tsup src", | ||
"dev": "yarn build:fast -- --watch", | ||
"clean": "rimraf dist .turbo", | ||
"typecheck": "tsc --noEmit", | ||
"prepack": "clean-package", | ||
"postpack": "clean-package restore" | ||
}, | ||
"peerDependencies": { | ||
"react": ">=18", | ||
"react-dom": ">=18", | ||
"@jala-banyu/theme": ">=1.3.0", | ||
"@jala-banyu/system": ">=1.0.0" | ||
}, | ||
"dependencies": { | ||
"@jala-banyu/shared-utils": "workspace:*", | ||
"@jala-banyu/react-utils": "workspace:*", | ||
"@jala-banyu/use-aria-press": "workspace:*", | ||
"@react-aria/focus": "^3.14.3", | ||
"@react-aria/interactions": "^3.19.1", | ||
"@react-aria/radio": "^3.8.2", | ||
"@react-aria/utils": "^3.21.1", | ||
"@react-aria/visually-hidden": "^3.8.6", | ||
"@react-stately/radio": "^3.9.1", | ||
"@react-types/radio": "^3.5.2", | ||
"@react-types/shared": "^3.21.0" | ||
}, | ||
"devDependencies": { | ||
"@jala-banyu/theme": "workspace:*", | ||
"@jala-banyu/system": "workspace:*", | ||
"@jala-banyu/button": "workspace:*", | ||
"clean-package": "2.2.0", | ||
"react": "^18.0.0", | ||
"react-dom": "^18.0.0" | ||
}, | ||
"clean-package": "../../../clean-package.config.json" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import Radio from "./radio"; | ||
import RadioGroup from "./radio-group"; | ||
|
||
// export types | ||
export type {RadioProps} from "./radio"; | ||
export type {RadioGroupProps} from "./radio-group"; | ||
|
||
// export hooks | ||
export {useRadio} from "./use-radio"; | ||
export {useRadioGroup} from "./use-radio-group"; | ||
|
||
// export context | ||
export {RadioGroupProvider, useRadioGroupContext} from "./radio-group-context"; | ||
|
||
// export component | ||
export {Radio, RadioGroup}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import type {ContextType} from "./use-radio-group"; | ||
|
||
import {createContext} from "@jala-banyu/react-utils"; | ||
|
||
export const [RadioGroupProvider, useRadioGroupContext] = createContext<ContextType>({ | ||
name: "RadioGroupContext", | ||
strict: false, | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import {forwardRef} from "@jala-banyu/system"; | ||
|
||
import {RadioGroupProvider} from "./radio-group-context"; | ||
import {UseRadioGroupProps, useRadioGroup} from "./use-radio-group"; | ||
|
||
export interface RadioGroupProps extends Omit<UseRadioGroupProps, "defaultChecked"> {} | ||
|
||
const RadioGroup = forwardRef<"div", RadioGroupProps>((props, ref) => { | ||
const { | ||
Component, | ||
children, | ||
label, | ||
context, | ||
description, | ||
errorMessage, | ||
getGroupProps, | ||
getLabelProps, | ||
getWrapperProps, | ||
getDescriptionProps, | ||
getErrorMessageProps, | ||
} = useRadioGroup({...props, ref}); | ||
|
||
return ( | ||
<Component {...getGroupProps()}> | ||
{label && <span {...getLabelProps()}>{label}</span>} | ||
<div {...getWrapperProps()}> | ||
<RadioGroupProvider value={context}>{children}</RadioGroupProvider> | ||
</div> | ||
{errorMessage ? ( | ||
<div {...getErrorMessageProps()}>{errorMessage as string}</div> | ||
) : description ? ( | ||
<div {...getDescriptionProps()}>{description}</div> | ||
) : null} | ||
</Component> | ||
); | ||
}); | ||
|
||
RadioGroup.displayName = "Banyu.RadioGroup"; | ||
|
||
export default RadioGroup; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import {forwardRef} from "@jala-banyu/system"; | ||
import {VisuallyHidden} from "@react-aria/visually-hidden"; | ||
|
||
import {UseRadioProps, useRadio} from "./use-radio"; | ||
|
||
export interface RadioProps extends UseRadioProps {} | ||
|
||
const Radio = forwardRef<"input", RadioProps>((props, ref) => { | ||
const { | ||
Component, | ||
children, | ||
slots, | ||
classNames, | ||
description, | ||
getBaseProps, | ||
getWrapperProps, | ||
getInputProps, | ||
getLabelProps, | ||
getLabelWrapperProps, | ||
getControlProps, | ||
} = useRadio({...props, ref}); | ||
|
||
return ( | ||
<Component {...getBaseProps()}> | ||
<VisuallyHidden> | ||
<input {...getInputProps()} /> | ||
</VisuallyHidden> | ||
<span {...getWrapperProps()}> | ||
<span {...getControlProps()} /> | ||
</span> | ||
<div {...getLabelWrapperProps()}> | ||
{children && <span {...getLabelProps()}>{children}</span>} | ||
{description && ( | ||
<span className={slots.description({class: classNames?.description})}>{description}</span> | ||
)} | ||
</div> | ||
</Component> | ||
); | ||
}); | ||
|
||
Radio.displayName = "Banyu.Radio"; | ||
|
||
export default Radio; |
Oops, something went wrong.