Skip to content

Commit

Permalink
checkbox and radio
Browse files Browse the repository at this point in the history
  • Loading branch information
lerte committed Nov 18, 2024
1 parent d3e3b7a commit bf29ce7
Show file tree
Hide file tree
Showing 7 changed files with 75 additions and 54 deletions.
54 changes: 26 additions & 28 deletions packages/actify/src/components/Checkbox/Checkbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,32 +9,32 @@ import {
} from 'react-aria'
import { CheckboxGroupState, useToggleState } from 'react-stately'
import React, { useId } from 'react'
import { useCheckboxGroup, useCheckboxGroupItem } from 'react-aria'

import { CheckboxGroupContext } from './CheckboxGroup'
import { FocusRing } from './../FocusRing'
import { Label } from './../Label'
import { Ripple } from './../Ripple'
import { StyleProps } from '../../utils'
import clsx from 'clsx'
import styles from './checkbox.module.css'
import { useCheckboxGroupItem } from 'react-aria'

interface CheckboxProps extends AriaCheckboxProps {
interface CheckboxProps extends AriaCheckboxProps, StyleProps {
ref?: React.RefObject<HTMLInputElement>
color?: 'primary' | 'secondary' | 'tertiary' | 'error'
}

const Checkbox = (props: CheckboxProps) => {
const _id = `actify-checkbox${useId()}`
const _inputRef = React.useRef(null)

const { id = _id, ref: inputRef = _inputRef } = props
const { ref: inputRef = _inputRef } = props

const groupState = React.useContext(CheckboxGroupContext)
const toggleState = useToggleState(props)

const state = groupState ?? toggleState

const { inputProps } = groupState
const { inputProps, labelProps } = groupState
? useCheckboxGroupItem(
props as AriaCheckboxGroupItemProps,
state as CheckboxGroupState,
Expand All @@ -52,18 +52,14 @@ const Checkbox = (props: CheckboxProps) => {
const { isFocusVisible, focusProps } = useFocusRing()

return (
<Label
style={{
display: 'flex',
alignItems: 'center'
}}
>
<div role="presentation" className={styles['checkbox']}>
<div className={styles['checkbox-wrapper']}>
<Label
style={props.style}
className={clsx(styles['checkbox'], props.className)}
>
<div className={styles['container']}>
<input
id={id}
ref={inputRef}
role="checkbox"
className={styles['input']}
{...mergeProps(inputProps, focusProps)}
aria-checked={props.isIndeterminate ? 'mixed' : undefined}
Expand All @@ -83,25 +79,27 @@ const Checkbox = (props: CheckboxProps) => {
: styles['unselected']
])}
/>

<Ripple
id={inputProps.id}
disabled={isDisabled}
style={{
inset: 'unset',
borderRadius: '50%',
width: 'var(--md-checkbox-state-layer-size, 40px)',
height: 'var(--md-checkbox-state-layer-size, 40px)'
}}
/>
{isFocusVisible && (
<FocusRing
style={{
width: '44px',
height: '44px',
inset: 'unset'
inset: 'unset',
borderRadius: '50%'
}}
/>
)}
<Ripple
id={id}
disabled={isDisabled}
style={{
width: '40px',
height: '40px',
inset: 'unset',
borderRadius: '50%'
}}
/>
<svg
aria-hidden="true"
viewBox="0 0 18 18"
Expand Down Expand Up @@ -130,9 +128,9 @@ const Checkbox = (props: CheckboxProps) => {
/>
</svg>
</div>
</div>
{props.children}
</Label>
</Label>
<Label {...labelProps}>{props.children}</Label>
</div>
)
}

Expand Down
26 changes: 11 additions & 15 deletions packages/actify/src/components/Checkbox/CheckboxGroup.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
import {
CheckboxGroupProps,
CheckboxGroupState,
useCheckboxGroupState
} from 'react-stately'
import { AriaCheckboxGroupProps, useCheckboxGroup } from 'react-aria'
import { CheckboxGroupState, useCheckboxGroupState } from 'react-stately'

import { Label } from '../Label'
import React from 'react'
import { useCheckboxGroup } from 'react-aria'
import { StyleProps } from '../../utils'
import styles from './checkbox-group.module.css'

export const CheckboxGroupContext =
React.createContext<CheckboxGroupState | null>(null)

interface Props extends CheckboxGroupProps {
interface Props extends AriaCheckboxGroupProps, StyleProps {
children?: React.ReactNode
}
const CheckboxGroup = (props: Props) => {
const { children, label, description } = props
const { children, label, description, style, className } = props
const state = useCheckboxGroupState(props)
const {
groupProps,
Expand All @@ -27,18 +25,16 @@ const CheckboxGroup = (props: Props) => {
} = useCheckboxGroup(props, state)

return (
<div {...groupProps}>
<Label {...labelProps}>{label}</Label>
<CheckboxGroupContext.Provider value={state}>
{children}
</CheckboxGroupContext.Provider>
<div {...groupProps} style={style} className={className}>
{label && <Label {...labelProps}>{label}</Label>}
<CheckboxGroupContext value={state}>{children}</CheckboxGroupContext>
{description && (
<div {...descriptionProps} style={{ fontSize: 12 }}>
<div {...descriptionProps} className={styles['description']}>
{description}
</div>
)}
{isInvalid && (
<div {...errorMessageProps} style={{ color: 'red', fontSize: 12 }}>
<div {...errorMessageProps} className={styles['error-message']}>
{validationErrors.join(' ')}
</div>
)}
Expand Down
14 changes: 14 additions & 0 deletions packages/actify/src/components/Checkbox/checkbox-group.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
.checkbox-group[aria-orientation='horizontal'] {
gap: 14px;
align-items: center;
}
.checkbox-group[aria-orientation='vertical'] {
flex-direction: column;
}
.description {
font-size: 12px;
}
.error-message {
font-size: 12px;
color: rgb(var(--md-sys-color-error));
}
5 changes: 5 additions & 0 deletions packages/actify/src/components/Checkbox/checkbox.module.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
.checkbox-wrapper {
gap: 8px;
display: flex;
align-items: center;
}
.checkbox {
margin: max(0px, (48px - var(--md-checkbox-container-size, 18px)) / 2);
border-start-start-radius: var(
Expand Down
8 changes: 7 additions & 1 deletion packages/actify/src/components/Menus/Menu.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { AriaButtonProps, Placement, useMenu, useMenuTrigger } from 'react-aria'
import {
AriaButtonProps,
Placement,
useMenu,
useMenuTrigger,
useSubmenuTrigger
} from 'react-aria'
import type { AriaMenuProps, MenuTriggerProps } from '@react-types/menu'
import { useMenuTriggerState, useTreeState } from 'react-stately'

Expand Down
18 changes: 10 additions & 8 deletions packages/actify/src/components/Radio/Radio.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,24 @@ import { AriaRadioProps, mergeProps, useFocusRing, useRadio } from 'react-aria'

import { FocusRing } from '../FocusRing/FocusRing'
import { Label } from '../Label'
import { RadioContext } from './RadioGroup'
import { RadioGroupContext } from './RadioGroup'
import { RadioGroupState } from 'react-stately'
import React from 'react'
import { Ripple } from '../Ripple/Ripple'
import { StyleProps } from '../../utils'
import clsx from 'clsx'
import styles from './radio.module.css'

interface RadioProps extends AriaRadioProps {
style?: React.CSSProperties
className?: string
interface RadioProps extends AriaRadioProps, StyleProps {
ref?: React.RefObject<HTMLInputElement>
color?: 'primary' | 'secondary' | 'tertiary' | 'error'
}

const Radio = (props: RadioProps) => {
const state = React.useContext(RadioContext) as RadioGroupState
const inputRef = React.useRef(null)
const _inputRef = React.useRef(null)
const { ref: inputRef = _inputRef } = props

const state = React.useContext(RadioGroupContext) as RadioGroupState
const { inputProps, labelProps, isSelected } = useRadio(
props,
state,
Expand All @@ -33,7 +36,6 @@ const Radio = (props: RadioProps) => {
className={clsx(styles['radio'], props.className)}
>
<div
role="presentation"
className={clsx(styles['container'], isSelected && styles['checked'])}
>
<input
Expand All @@ -54,9 +56,9 @@ const Radio = (props: RadioProps) => {
{isFocusVisible && (
<FocusRing
style={{
width: '44px',
height: '44px',
inset: 'unset',
width: '44px',
borderRadius: '50%'
}}
/>
Expand Down
4 changes: 2 additions & 2 deletions packages/actify/src/components/Radio/RadioGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { StyleProps } from '../../utils'
import clsx from 'clsx'
import styles from './radio-group.module.css'

export const RadioContext = React.createContext<RadioGroupState | {}>({})
export const RadioGroupContext = React.createContext<RadioGroupState | {}>({})

interface RadioGroupProps extends AriaRadioGroupProps, StyleProps {
children?: React.ReactNode
Expand All @@ -32,7 +32,7 @@ const RadioGroup = (props: RadioGroupProps) => {
className={clsx(styles['radio-group'], className)}
>
{props.label && <Label {...labelProps}>{props.label}</Label>}
<RadioContext value={state}>{props.children}</RadioContext>
<RadioGroupContext value={state}>{props.children}</RadioGroupContext>
{description && (
<div {...descriptionProps} className={styles['description']}>
{description}
Expand Down

0 comments on commit bf29ce7

Please sign in to comment.