# AtlantisThemeContext

Provides a way to control the theme of Atlantis components and the design
tokens.

## Design & usage guidelines

Both the web and mobile components have the exact same API, except for one minor
difference in how you update the theme.

Each platform provides a `useAtlantisTheme` hook that you may use to access the
`theme` and `tokens` in your components.

On mobile, this hook also returns a `setTheme` function which you'll use to
update the theme for the nearest `AtlantisThemeContextProvider` ancestor.
Typically there will only be a single provider at the root, controlling the
theme for the entire app.

On web, you'll need to import the `updateTheme` function and call it with the
new theme. This is a separate function because it synchronizes the theme update
across all providers under various React trees. Synchronizing across providers
is necessary for cases where an island-based architecture is used.

### Usage for web

```tsx
import {
  AtlantisThemeContextProvider,
  updateTheme,
  useAtlantisTheme,
} from "@jobber/components/AtlantisThemeContext";

function App() {
  return (
    <AtlantisThemeContextProvider>
      <ThemedComponent />
    </AtlantisThemeContextProvider>
  );
}

function ThemedComponent() {
  const { theme, tokens } = useAtlantisTheme();
  return (
    <Content>
      <div
        style={{
          background: tokens["surface-background"],
          padding: tokens["space-base"],
        }}
      >
        <Content>
          <Text>The current theme is: {theme}.</Text>
          <Text>
            The javascript tokens can be accessed via the tokens object.
          </Text>
          <Text>The theme can be changed using `updateTheme`</Text>
          <Button
            onClick={() => updateTheme("light")}
            label="The theme can be changed to light"
          />
          <Button
            onClick={() => updateTheme("dark")}
            label="The theme can be changed to dark"
          />
        </Content>
      </div>
    </Content>
  );
}
```

### Usage for mobile

```tsx
import {
  AtlantisThemeContextProvider,
  useAtlantisTheme,
} from "@jobber/components/AtlantisThemeContext";

function App() {
  return (
    <AtlantisThemeContextProvider>
      <ThemedComponent />
    </AtlantisThemeContextProvider>
  );
}

function ThemedComponent() {
  const { theme, tokens, setTheme } = useAtlantisTheme();
  return (
    <Content>
      <View
        style={{
          background: tokens["surface-background"],
          padding: tokens["space-base"],
        }}
      >
        <Content>
          <Text>The current theme is: {theme}.</Text>
          <Text>
            The javascript tokens can be accessed via the tokens object.
          </Text>
          <Text>The theme can be changed using `setTheme`</Text>
          <Button
            onPress={() => setTheme("light")}
            label="The theme can be changed to light"
          />
          <Button
            onPress={() => setTheme("dark")}
            label="The theme can be changed to dark"
          />
        </Content>
      </View>
    </Content>
  );
}
```

### Cross-tab theme sync (Web Only)

The provider can keep the theme in sync across a user's open browser tabs. This
is opt-in: pass the `localStorage` key your app writes to as the `storageKey`
prop. The provider then listens for
[`storage` events](https://developer.mozilla.org/en-US/docs/Web/API/Window/storage_event)
on that key and applies theme changes from other tabs. The `storage` event fires
only in tabs *other* than the one that wrote the value, so there's no feedback
loop.

**Your app is responsible for writing to `localStorage`** when the theme changes
— Atlantis only handles the listen-and-react side.

```tsx
<AtlantisThemeContextProvider storageKey="my_app_theme">
  <ThemedComponent />
</AtlantisThemeContextProvider>
```

```tsx
// Theme toggle — write to the same key after calling updateTheme
import { updateTheme } from "@jobber/components/AtlantisThemeContext";

function toggleTheme(newTheme: "light" | "dark") {
  updateTheme(newTheme); // updates all providers in this tab immediately

  try {
    localStorage.setItem("my_app_theme", newTheme); // signals other tabs
  } catch {
    // localStorage unavailable (e.g. private mode) — degrades gracefully
  }
}
```

Omit `storageKey` and no `storage` listener is added, keeping the previous
within-tab-only behavior. Multiple dynamic providers on the same page all share
the same internal store, so a single provider with a `storageKey` is enough — a
cross-tab signal updates every dynamic provider at once. Providers mounted with
`dangerouslyOverrideTheme` are unaffected.

### Forcing a theme for an AtlantisThemeContextProvider

In some scenarios you may want to force a theme for specific components
regardless of the main application theme. This can be done by setting the
`dangerouslyOverrideTheme` prop to `<themeToSet>` on the
`AtlantisThemeContextProvider`.

Note: If you're using `buildThemedStyles`, any `useStyles` calls must be done
within a *child component* of the `AtlantisThemeContextProvider`, not in the
same component that renders the provider. Same goes for `useAtlantisTheme`.

```tsx
import {
  AtlantisThemeContextProvider,
  updateTheme,
  useAtlantisTheme,
} from "@jobber/components/AtlantisThemeContext";

function App() {
  return (
    <AtlantisThemeContextProvider>
      <ThemedComponent />
      <AtlantisThemeContextProvider dangerouslyOverrideTheme="dark">
        <Text>These components will always be dark themed</Text>
        <ThemedComponent />
      </AtlantisThemeContextProvider>
    </AtlantisThemeContextProvider>
  );
}

function ThemedComponent() {
  const { theme, tokens } = useAtlantisTheme();
  return (
    <Content>
      <div
        style={{
          background: tokens["surface-background"],
          padding: tokens["space-base"],
        }}
      >
        <Content>
          <Text>The current theme is: {theme}. </Text>
          <Text>
            The javascript tokens can be accessed via the tokens object.
          </Text>
          <Text>The theme can be changed using `updateTheme`</Text>
          <Button
            onClick={() => updateTheme("light")}
            label="The theme can be changed to light"
          />
          <Button
            onClick={() => updateTheme("dark")}
            label="The theme can be changed to dark"
          />
        </Content>
      </div>
    </Content>
  );
}
```

### Overriding theme tokens (Web Only)

If you need to override theme tokens, you can do so by supplying the
`dangerouslyOverrideTokens` prop to the `AtlantisThemeContextProvider`. You can
also supply custom tokens which are exposed as CSS variables to any elements
under this `AtlantisThemeContextProvider`. All overridden tokens are also
accessible via the `overrideTokens` object from the `useAtlantisTheme` hook.

This capability allows you to create custom theme providers.

```tsx
import {
  AtlantisThemeContextProvider,
  updateTheme,
  useAtlantisTheme,
} from "@jobber/components/AtlantisThemeContext";

function App() {
  return (
    <AtlantisThemeContextProvider
      dangerouslyOverrideTokens={{
        "color-text": "fuchsia",
      }}
    >
      <Text>This text will be fuchsia!</Text>
    </AtlantisThemeContextProvider>
  );
}
```

### Creating themed styles with buildThemedStyles (Mobile Only)

The `buildThemedStyles` utility is available to help create themed StyleSheets
that automatically update when the theme changes.

```tsx
// In the .style.ts file
import { buildThemedStyles } from "@jobber/components-native";

const useStyles = buildThemedStyles(tokens => ({
  container: {
    backgroundColor: tokens["color-surface"],
    padding: tokens["space-base"],
    borderRadius: tokens["radius-base"],
  },
  text: {
    color: tokens["color-text"],
    fontSize: tokens["font-size-base"],
  },
}));

// In the component file
function ThemedComponent() {
  const styles = useStyles();
  return (
    <View style={styles.container}>
      <Text style={styles.text}>Themed content</Text>
    </View>
  );
}
```

Key features:

* Automatically updates styles when the theme changes
* Works with React Native's StyleSheet system
* Memoized so that the StyleSheet is only re-created when the theme changes


## Props

### Web

| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `children` | `ReactNode` | Yes | — | The children to render. |
| `dangerouslyOverrideTheme` | `Theme` | No | — | Force the theme for this provider to always be the same as the provided theme. Useful for sections that should remain... |
| `dangerouslyOverrideTokens` | `OverrideTokens` | No | — | Overrides existing design tokens with custom values. Can also supply custom tokens which will be accessible via useAt... |
| `storageKey` | `string` | No | — | The `localStorage` key your app writes to when toggling the theme (e.g. `"jobber_theme"`). When set, the provider lis... |
