# ThemeProvider and feedback

ThemeProvider hands every component its theme and an optional feedback handler, where your app plays its own haptics and sounds.

## Usage

Mount one `ThemeProvider` at the root of your app and pass it the theme for the current appearance:

```tsx
import { ThemeProvider, defaultDark, defaultLight, type FeedbackHandler } from "@terratcg/ui";
import * as Haptics from "expo-haptics";
import { useColorScheme } from "react-native";

const feedback: FeedbackHandler = (intent) => {
  if (intent === "press") void Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
  else void Haptics.selectionAsync();
};

export function Root({ children }: { children: React.ReactNode }) {
  const dark = useColorScheme() === "dark";
  return (
    <ThemeProvider theme={dark ? defaultDark : defaultLight} feedback={feedback}>
      {children}
    </ThemeProvider>
  );
}
```

Keep `feedback` stable, defined outside the component or in `useCallback`, so the theme context doesn't change on every render.

## Props

### ThemeProviderProps

| Prop        | Type                | Description                                                                                                                                                                     |
| ----------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `theme?`    | `Theme`             | Active theme. The host chooses the appearance (for example `defaultLight` or `defaultDark` from `useColorScheme()`) and owns any stored preference. Defaults to `defaultLight`. |
| `feedback?` | `FeedbackHandler`   | Optional feedback sink. Missing support is a no-op and never blocks an action.                                                                                                  |
| `labels?`   | `Partial<UiLabels>` | Default words in the host's language. Each one left out keeps the surrounding provider's, so a nested theme switch need not repeat them.                                        |
| `children?` | `ReactNode`         |                                                                                                                                                                                 |

## The theme

- **One theme at a time:** `ThemeProvider` takes a single `theme`. Your app decides between light and dark, from `useColorScheme()` or a stored preference it owns. There is no light and dark pair prop.
- **Fallback:** a component rendered with no `ThemeProvider` above it uses `defaultLight`.
- **Nesting:** an inner `ThemeProvider` overrides the theme for everything inside it. Pass its `feedback` again if that part of the screen should keep it.
- **Reading it:** `useTheme()` returns the active `Theme`, for your own components that should match: `useTheme().colors.ink`, `useTheme().spacing.md`.

See [Theming](/docs/theming) for every token and how to build your own theme.

## Labels

TerraUI ships no translations. A few controls speak a word when you give them none of their own, such as a `Spinner`'s accessibility label. Pass those words in your app's language through `labels`:

```tsx
<ThemeProvider theme={theme} labels={{ loading: t("loading") }}>
```

- **Default:** English ("Loading") when no provider supplies a label.
- **Nesting:** an inner `ThemeProvider` that leaves a label out keeps the surrounding one, so a theme switch on part of a screen need not repeat them.
- **Reading them:** `useUiLabels()` returns the active `UiLabels`, for your own controls that should say the same words.

## Feedback

TerraUI plays no haptic and no sound. When a user deliberately acts on a control, the control calls your `feedback` handler with an intent, and your app decides what to play.

| Intent | Reported by |
| --- | --- |
| `"press"` | A Button (unless `selected` is set), IconButton, Shutter, the tab bar's central action, a pressable Cell, a pressable Row. |
| `"select"` | A Button with `selected` set, Switch, SwitchRow, SelectableCell, a tab bar item. |

- **Deliberate only:** a control reports on activation, never while disabled or loading.
- **Never blocking:** without a handler, nothing happens and the action still runs. A missing haptics module must not stop a press.
- **Outcomes are yours:** feedback is the intent of a press, not the result of an operation. Report success and failure from your own code, for example with a toast.
- **In overlays:** `OverlayHost` passes the surrounding handler to the keys of its Dialogs and Menus.

`useFeedback()` returns the handler, so your own controls can report the same intents.

## Rules

- Mount one `ThemeProvider` at the root, around your navigator and your `OverlayHost`.
- Let your app own the appearance and any stored preference; pass `ThemeProvider` the resulting theme.
- Keep haptics and sounds in your app, in the `feedback` handler. Don't call them from `onPress` as well, or each press plays twice.
- Respect the user's settings in your handler, such as a sound setting or the silent switch.
