Skip to content

Commit

Permalink
feat(radio): radio with tailwind forms (#46)
Browse files Browse the repository at this point in the history
* chore(radio): radio with tailwind forms

* chore(radio): added disabled state styles

* chore: purge fix

* chore: added controllable state

* chore: addComponent for radio styles

* chore(radio): added RadioLabel

* chore(radio): radio label improvements

* chore: remove css file

* chore: changed spinner types to getThemeValue

* chore: added Box in radio label

* feat(radio): added size prop

* fix(radio): fix radio state context

* chore: radio types cleanup

* feat(radio): added radio v2 (#49)

* feat(radio): migrate radio to use conditional rendering

* refactor(radio): added size support and refactored all types

* chore: radio controlled state

* chore: review updates

* fix(radio): fixed radio controlled state

* fix(radio): fixed radio controlled state

* refactor: rearrange imports

* refactor(radio): added radio icons with css

* Revert "refactor(radio): added radio icons with css"

This reverts commit 5572582.

* refactor(radio): minor updates

* feat(radio): added icon props in radio

* chore: refactor & remove old code
  • Loading branch information
anuraghazra authored Feb 1, 2021
1 parent 54d2d80 commit e95ed2c
Show file tree
Hide file tree
Showing 15 changed files with 858 additions and 501 deletions.
5 changes: 4 additions & 1 deletion .storybook/storybookUtils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ export const storyTemplate = <ComponentProps,>(

export const createUnionControl = (keys: any) => {
return {
control: { type: "inline-radio", options: Object.keys(keys) },
control: {
type: "inline-radio",
options: Array.isArray(keys) ? keys : Object.keys(keys),
},
};
};

Expand Down
41 changes: 41 additions & 0 deletions src/icons/RadioIcons.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import * as React from "react";
import { createIcon } from "../icon";

export const RadioCheckedIcon = createIcon({
displayName: "RadioChecked",
viewBox: "0 0 16 16",
path: (
<>
<circle cx="8" cy="8" r="8" fill="currentColor" />
<circle cx="8" cy="8" r="3" fill="white" />
</>
),
});

export const RadioUncheckedIcon = createIcon({
displayName: "RadioUnchecked",
viewBox: "0 0 16 16",
path: (
<>
<circle
cx="8"
cy="8"
r="7"
fill="white"
stroke="currentColor"
strokeWidth="1.5"
/>
</>
),
});

export const RadioDisabledIcon = createIcon({
displayName: "RadioDisabled",
viewBox: "0 0 16 16",
path: (
<>
<circle cx="8" cy="8" r="8" fill="#E4E4E7" />
<circle cx="8" cy="8" r="3" fill="currentColor" />
</>
),
});
81 changes: 43 additions & 38 deletions src/radio/Radio.tsx
Original file line number Diff line number Diff line change
@@ -1,50 +1,55 @@
import { cx } from "@renderlesskit/react";
import React from "react";
import {
RadioHTMLProps,
Radio as ReakitRadio,
RadioGroup as ReakitRadioGroup,
RadioGroupProps,
RadioInitialState,
RadioProps,
useRadioState,
RadioProps as ReakitRadioProps,
} from "reakit";
import { createContext } from "../utils";
import { cx } from "@renderlesskit/react";

import { useTheme } from "../theme";
import { RadioIcon } from "./RadioIcon";
import { useRadioContext } from "./RadioGroup";
import { forwardRefWithAs } from "../utils/types";

const [RadioProvider, useRadioContext] = createContext({
name: "RadioContext",
strict: true,
errorMessage: "Radio must be used within RadioContextProvider",
});
export type RadioCommonProps = Partial<
Pick<ReakitRadioProps, "value" | "disabled">
> & {
size?: keyof Renderlesskit.GetThemeValue<"radio", "icon">["size"];
};

export const RadioInput: React.FC<RadioHTMLProps & RadioCommonProps> = ({
className,
...rest
}) => {
const theme = useTheme();
const { radioState } = useRadioContext();

const radioStyles = cx(theme.radio.input, className);

return <ReakitRadio className={radioStyles} {...radioState} {...rest} />;
};

export const Radio = forwardRefWithAs<
Partial<RadioProps>,
RadioHTMLProps &
RadioCommonProps & {
checkedIcon?: React.ReactNode;
uncheckedIcon?: React.ReactNode;
disabledIcon?: React.ReactNode;
},
HTMLInputElement,
"input"
>(({ children, className, ...props }, ref) => {
const state = useRadioContext();
return (
<label className={cx("radio", className)}>
<span className="radio__input">
<ReakitRadio className="sr-only" {...state} {...props} ref={ref} />
<span className="radio__control" />
</span>
<span className="radio__label">{children}</span>
</label>
);
});

export const RadioGroup = forwardRefWithAs<
Partial<RadioGroupProps & RadioInitialState>,
HTMLDivElement,
"div"
>(({ children, ...props }, ref) => {
const radio = useRadioState(props);

>((props, ref) => {
const { checkedIcon, uncheckedIcon, disabledIcon } = props;
return (
<RadioProvider value={radio}>
<ReakitRadioGroup {...radio} ref={ref}>
{children}
</ReakitRadioGroup>
</RadioProvider>
<>
<RadioInput ref={ref} {...props} />
<RadioIcon
value={props.value}
disabled={props.disabled}
checkedIcon={checkedIcon}
uncheckedIcon={uncheckedIcon}
disabledIcon={disabledIcon}
/>
</>
);
});
40 changes: 40 additions & 0 deletions src/radio/RadioGroup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React from "react";
import { RadioInitialState, RadioStateReturn } from "reakit";

import { createContext } from "../utils";
import { RadioCommonProps } from "./Radio";
import { useRadioState } from "./useRadioState";

type RadioContextType = {
radioState?: RadioStateReturn;
radioSize?: RadioCommonProps["size"];
};

const [RadioProvider, useRadioContext] = createContext<RadioContextType>({
errorMessage: "Radio must be used within RadioProvider",
name: "RadioContext",
strict: true,
});

export { RadioProvider, useRadioContext };

export type RadioGroupProps = RadioInitialState &
Pick<RadioCommonProps, "size"> & {
onStateChange?: (state: RadioCommonProps["value"]) => void;
state?: RadioCommonProps["value"];
defaultState?: RadioCommonProps["value"];
};

export const RadioGroup: React.FC<RadioGroupProps> = ({
children,
size = "sm",
...props
}) => {
const radioState = useRadioState(props);

return (
<RadioProvider value={{ radioState: radioState, radioSize: size }}>
{children}
</RadioProvider>
);
};
70 changes: 70 additions & 0 deletions src/radio/RadioIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import React from "react";
import { cx } from "@renderlesskit/react";
import { Box, BoxProps, RadioState } from "reakit";

import {
RadioCheckedIcon,
RadioDisabledIcon,
RadioUncheckedIcon,
} from "../icons/RadioIcons";
import { useTheme } from "..";
import { RadioCommonProps } from "./Radio";
import { useRadioContext } from "./RadioGroup";
import { forwardRefWithAs } from "../utils/types";

export type RadioIconProps = BoxProps &
RadioState &
RadioCommonProps & {
checkedIcon?: React.ReactNode;
uncheckedIcon?: React.ReactNode;
disabledIcon?: React.ReactNode;
};

export const RadioIcon = forwardRefWithAs<
Partial<RadioIconProps>,
HTMLDivElement,
"div"
>((props, ref) => {
const { radioState, radioSize } = useRadioContext();
const { value, size, disabled, ...mainProps } = props;
const { className, children, ...rest } = mainProps;

const _size = size || radioSize || "sm";
const stateProp = radioState?.state === value;

const theme = useTheme();
const radioIconStyles = cx(
theme.radio.icon.base,
theme.radio.icon.size[_size],
disabled
? theme.radio.icon.disabled
: stateProp
? theme.radio.icon.checked
: theme.radio.icon.unchecked,
className,
);

const iconMap = {
checked: props.checkedIcon || <RadioCheckedIcon />,
unchecked: props.uncheckedIcon || <RadioUncheckedIcon />,
disabled: props.disabledIcon || <RadioDisabledIcon />,
};

return (
<Box
ref={ref}
role="img"
aria-hidden="true"
className={radioIconStyles}
{...rest}
>
{children
? children
: disabled
? iconMap.disabled
: stateProp
? iconMap.checked
: iconMap.unchecked}
</Box>
);
});
33 changes: 33 additions & 0 deletions src/radio/RadioLabel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React from "react";
import { BoxProps } from "reakit";
import { cx } from "@renderlesskit/react";

import { Box } from "../box";
import { useTheme } from "..";
import { RadioCommonProps } from "./Radio";
import { useRadioContext } from "./RadioGroup";
import { forwardRefWithAs } from "../utils/types";

export type RadioLabelProps = BoxProps & Omit<RadioCommonProps, "value">;

export const RadioLabel = forwardRefWithAs<
RadioLabelProps,
HTMLLabelElement,
"label"
>((props, ref) => {
const { size, disabled = false, ...mainProps } = props;
const { className, ...rest } = mainProps;
const theme = useTheme();
const { radioSize } = useRadioContext();
const _size = size || radioSize || "sm";

const radioStyles = cx(
theme.radio.base,
theme.radio.label.base,
theme.radio.label.size[_size],
disabled ? theme.radio.disabled : "",
className,
);

return <Box as="label" ref={ref} className={radioStyles} {...rest} />;
});
5 changes: 5 additions & 0 deletions src/radio/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export * from "./Radio";
export * from "./RadioIcon";
export * from "./RadioLabel";
export * from "./RadioGroup";
export * from "./useRadioState";
Loading

0 comments on commit e95ed2c

Please sign in to comment.