Skip to content
Open
10 changes: 10 additions & 0 deletions packages/engine/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,16 @@ export default () => (
)
```

## Checkbox

`Checkbox` accepts `checked`, `indeterminate`, `disabled`, `label`, `class`,
and `style` props.

`onChange(checked)` receives the next checked value when an enabled checkbox
is toggled. `onPress(event)` and `onClick(event)` run from the native `click`
path. `onPress` is not a native `press` event binding. A Space keydown toggles
the checkbox but does not invoke either callback.

The component prop types live in `@geastack/core`. The C++ sources are
compiled into a target through `@geastack/core`'s source manifest. Vendored
third-party code is listed in the repository's `THIRD_PARTY.md`.
Expand Down
26 changes: 26 additions & 0 deletions packages/engine/components/Checkbox.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import type { ClassValue, KeyEvent, PressHandler, Style } from '@geastack/core'

export interface CheckboxProps {
/** Checked state of the checkbox */
checked?: boolean
/** Indeterminate/mixed state of the checkbox */
indeterminate?: boolean
/** Whether the checkbox is disabled */
disabled?: boolean
/** Optional text label to display beside the checkbox */
label?: string
/** Custom class names (string, array, or class map) */
class?: ClassValue
/** Custom inline styles */
style?: Style
/** Triggered when the checked state changes */
onChange?: (checked: boolean) => void
/** Triggered via press/click. Note: Runs through the native click event path */
onPress?: PressHandler
/** Triggered via click. Note: Runs through the native click event path */
onClick?: PressHandler
/** Accessibility Labels When Labels Are Not Displayed */
ariaLabel?: string
}

export function Checkbox(props: CheckboxProps): any
91 changes: 91 additions & 0 deletions packages/engine/components/Checkbox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import type { ClassValue, KeyEvent, PressHandler, Style } from '@geastack/core'

export interface CheckboxProps {
checked?: boolean
indeterminate?: boolean
disabled?: boolean
label?: string
class?: ClassValue
style?: Style
onChange?: (checked: boolean) => void
onPress?: PressHandler
onClick?: PressHandler
ariaLabel?: string
}

const resolveClass = (val?: ClassValue | null): string => {
if (!val) return ''
if (typeof val === 'string') return val
if (Array.isArray(val)) return val.map(resolveClass).filter(Boolean).join(' ')
if (typeof val === 'object') {
return Object.entries(val)
.filter(([_, enabled]) => Boolean(enabled))
.map(([key]) => key)
.join(' ')
}
return ''
}

export function Checkbox({
checked = false,
indeterminate = false,
disabled = false,
label,
class: cls,
style,
onChange,
onPress,
onClick,
ariaLabel
}: CheckboxProps) {
const toggle = () => {
if (disabled) return
onChange?.(indeterminate ? true : !checked)
}

const handlePress = (e: any) => {
if (disabled) return
toggle()
onPress?.(e)
onClick?.(e)
}

const handleKeyDown = (e: KeyEvent) => {
if (e.keyCode === 32 || e.which === 32) {
toggle()
}
}

const checkClass = [
'gea-checkbox',
disabled && 'gea-checkbox-disabled',
indeterminate && 'gea-checkbox-indeterminate',
!indeterminate && checked && 'gea-checkbox-checked',
!indeterminate && !checked && 'gea-checkbox-unchecked',
resolveClass(cls),
]
.filter(Boolean)
.join(' ')

const mark = indeterminate ? '-' : checked ? '✓' : ''

const computedAriaLabel = ariaLabel || label || 'Checkbox'

return (
<div
class={checkClass}
style={style}
onPress={handlePress}
onClick={handlePress}
onKeyDown={handleKeyDown}
role="checkbox"
aria-checked={indeterminate ? 'mixed' : checked}
aria-disabled={disabled}
aria-label={computedAriaLabel}
tabIndex={disabled ? -1 : 0}
>
<span class="gea-checkbox-mark">{mark}</span>
{label && <span class="gea-checkbox-label">{label}</span>}
</div>
)
}
2 changes: 2 additions & 0 deletions packages/engine/components/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ export { Image } from './Image'
export type { ImageProps } from '@geastack/core'
export { Button } from './Button'
export type { ButtonProps } from '@geastack/core'
export { Checkbox } from './Checkbox'
export type { CheckboxProps } from './Checkbox'
1 change: 1 addition & 0 deletions packages/engine/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export { View } from './View'
export { Text } from './Text'
export { Image } from './Image'
export { Button } from './Button'
export { Checkbox } from './Checkbox'
4 changes: 4 additions & 0 deletions packages/engine/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@
"types": "./components/Button.d.ts",
"default": "./components/Button.tsx"
},
"./components/Checkbox": {
"types": "./components/Checkbox.d.ts",
"default": "./components/Checkbox.tsx"
},
"./package.json": "./package.json",
"./ui/*": "./ui/*"
},
Expand Down
Loading