diff --git a/packages/engine/README.md b/packages/engine/README.md index 5874116..ddb5b30 100644 --- a/packages/engine/README.md +++ b/packages/engine/README.md @@ -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`. diff --git a/packages/engine/components/Checkbox.d.ts b/packages/engine/components/Checkbox.d.ts new file mode 100644 index 0000000..efb0a54 --- /dev/null +++ b/packages/engine/components/Checkbox.d.ts @@ -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 \ No newline at end of file diff --git a/packages/engine/components/Checkbox.tsx b/packages/engine/components/Checkbox.tsx new file mode 100644 index 0000000..146fe81 --- /dev/null +++ b/packages/engine/components/Checkbox.tsx @@ -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 ( + + ) +} \ No newline at end of file diff --git a/packages/engine/components/index.d.ts b/packages/engine/components/index.d.ts index 2e70e2b..94654dc 100644 --- a/packages/engine/components/index.d.ts +++ b/packages/engine/components/index.d.ts @@ -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' diff --git a/packages/engine/components/index.ts b/packages/engine/components/index.ts index 6b6c2e0..39a11c1 100644 --- a/packages/engine/components/index.ts +++ b/packages/engine/components/index.ts @@ -2,3 +2,4 @@ export { View } from './View' export { Text } from './Text' export { Image } from './Image' export { Button } from './Button' +export { Checkbox } from './Checkbox' \ No newline at end of file diff --git a/packages/engine/package.json b/packages/engine/package.json index 3882769..1fce352 100644 --- a/packages/engine/package.json +++ b/packages/engine/package.json @@ -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/*" },