diff --git a/apps/docs/src/content/docs/components/checkbox.mdx b/apps/docs/src/content/docs/components/checkbox.mdx
index bf56a182..7f4f9383 100644
--- a/apps/docs/src/content/docs/components/checkbox.mdx
+++ b/apps/docs/src/content/docs/components/checkbox.mdx
@@ -11,10 +11,8 @@ import { LinkButton } from '@/components/react/LinkButton';
import { Aside, Tabs, TabItem } from '@astrojs/starlight/components';
import importedCode from '@rnr/reusables/components/ui/checkbox?raw';
-
- Checkbox Primitive
-
-
+Checkbox Primitive
+
Demo
@@ -23,6 +21,7 @@ import importedCode from '@rnr/reusables/components/ui/checkbox?raw';
A box that is a checked (ticked) indicator when activated.
### Installation
+
```bash
@@ -40,28 +39,138 @@ A box that is a checked (ticked) indicator when activated.
+
### Usage
```tsx
+import * as React from 'react';
import { Checkbox } from '~/components/ui/checkbox';
function Example() {
- const [checked, setChecked] = React.useState(false);
- return (
-
- );
+ const [checked, setChecked] = React.useState(false);
+ return ;
}
```
+
## Props
### Checkbox
-Extends [`Pressable`](https://reactnative.dev/docs/pressable#props) props
+Extends [`Pressable`](https://reactnative.dev/docs/pressable#props) props.
+
+| Prop | Type | Default | Notes |
+| ------------------------ | ------------------------------------- | --------------------------- | ---------------------------------------------------- |
+| `checked` **\*** | `boolean` | — | Controlled state. |
+| `onCheckedChange` **\*** | `(checked: boolean) => void` | — | Change handler. |
+| `disabled` | `boolean` | `false` | Disables interactions. |
+| `className` | `string` | — | Base classes always applied (shape/size/etc.). |
+| `checkedClassName` | `string` | `bg-primary border-primary` | Added **only** when checked. |
+| `uncheckedClassName` | `string` | `border-primary` | Added **only** when not checked. |
+| `indicatorClassName` | `string` | — | Container for the icon (fills the box). |
+| `iconClassName` | `string` | — | Applied to the default icon always. |
+| `iconCheckedClassName` | `string` | `text-primary-foreground` | Extra classes for the default icon when checked. |
+| `iconUncheckedClassName` | `string` | — | Extra classes for the default icon when not checked. |
+| `renderIcon` | `({ checked: boolean }) => ReactNode` | default check icon | Render a custom icon; overrides `icon*ClassName`. |
+
+**Theme defaults** (when you don’t pass overrides):
+
+- Unchecked border: `border-primary`
+- Checked: `bg-primary border-primary`
+- Checked icon: `text-primary-foreground`
+
+---
+
+## Customizing styles (no presets)
+
+Use state-aware class props to color only when checked (and keep defaults otherwise).
+
+```tsx
+
+```
+
+**Web-only selectors:** you can also do:
+
+```tsx
+
+```
+
+> React Native doesn’t support `data-*` selectors—prefer `checkedClassName` on native.
+
+---
+
+## Custom icon with `renderIcon`
+
+When you supply `renderIcon`, you fully control the icon node.
+
+```tsx
+import { Platform, View } from 'react-native';
+import { Check } from '~/lib/icons/Check';
+
+
+ checked ? (
+
+ ) : null
+ }
+/>;
+```
+
+Show a subtle dot when unchecked:
+
+```tsx
+renderIcon={({ checked }) =>
+ checked ? (
+
+ ) : (
+
+ )
+}
+```
-| Prop | Type | Note |
-| :---------------: | :------------------------: | :----------: |
-| checked\* | boolean | |
-| onCheckedChange\* | (checked: boolean) => void | |
-| disabled | boolean | _(optional)_ |
\ No newline at end of file
+---
+
+## Examples
+
+**Square, red when checked**
+
+```tsx
+
+```
+
+**Subtle unchecked, bold checked**
+
+```tsx
+
+```
diff --git a/packages/reusables/src/components/ui/checkbox.tsx b/packages/reusables/src/components/ui/checkbox.tsx
index 576438ef..ceb491c0 100644
--- a/packages/reusables/src/components/ui/checkbox.tsx
+++ b/packages/reusables/src/components/ui/checkbox.tsx
@@ -4,30 +4,77 @@ import { Platform } from 'react-native';
import { Check } from '../../lib/icons/Check';
import { cn } from '../../lib/utils';
-function Checkbox({
- className,
- ...props
-}: CheckboxPrimitive.RootProps & {
- ref?: React.RefObject;
-}) {
+export interface CheckboxProps extends CheckboxPrimitive.RootProps {
+ checkedClassName?: string;
+ uncheckedClassName?: string;
+ indicatorClassName?: string;
+ iconClassName?: string;
+ iconCheckedClassName?: string;
+ iconUncheckedClassName?: string;
+ renderIcon?: (opts: { checked: boolean }) => React.ReactNode;
+}
+
+function CheckboxImpl(
+ {
+ className,
+ checkedClassName,
+ uncheckedClassName,
+ indicatorClassName,
+ iconClassName,
+ iconCheckedClassName,
+ iconUncheckedClassName,
+ renderIcon,
+ checked,
+ ...props
+ }: CheckboxProps,
+ ref: React.ForwardedRef
+) {
+ const baseRoot =
+ 'web:peer h-4 w-4 native:h-[20] native:w-[20] shrink-0 rounded-sm native:rounded ' +
+ 'border web:ring-offset-background web:focus-visible:outline-none ' +
+ 'web:focus-visible:ring-2 web:focus-visible:ring-ring web:focus-visible:ring-offset-2 ' +
+ 'disabled:cursor-not-allowed disabled:opacity-50';
+
+ // Defaults so "just checked/onCheckedChange" works
+ const defaultUnchecked = 'border-primary';
+ const defaultChecked = ['bg-primary', 'border-primary'];
+ const defaultIconChecked = 'text-primary-foreground';
+
+ const rootClasses = cn(
+ baseRoot,
+ defaultUnchecked,
+ checked && defaultChecked,
+ checked ? checkedClassName : uncheckedClassName,
+ className
+ );
+
+ const iconClasses = cn(
+ checked && defaultIconChecked,
+ iconClassName,
+ checked ? iconCheckedClassName : iconUncheckedClassName
+ );
+
return (
-
-
-
+
+
+ {renderIcon ? (
+ renderIcon({ checked: !!checked })
+ ) : (
+
+ )}
);
}
-export { Checkbox };
+const ForwardedCheckbox = React.forwardRef(CheckboxImpl);
+ForwardedCheckbox.displayName = 'Checkbox';
+export function Checkbox(props: CheckboxProps) {
+ return ;
+}