import {
  ExampleCodeBlock,
  SymbolDoc,
  Specifications,
  StorybookStatusIndicator,
} from '@workday/canvas-kit-docs';
import Basic from './examples/Basic';
import Heading from './examples/Heading';
import AlternatePanel from './examples/Variant';
import ExternalControl from './examples/ExternalControl';
import RightOrigin from './examples/RightOrigin';
import AlwaysOpen from './examples/AlwaysOpen';
import OnStateTransition from './examples/OnStateTransition';


# Canvas Kit Side Panel <StorybookStatusIndicator type="new" />

`SidePanel` is a collapsible container that anchors to the left or right side of the screen. It uses
the model pattern for state management and is fully accessible.

[> Workday Design Reference](https://design.workday.com/components/containers/side-panel)

## Installation

```sh
yarn add @workday/canvas-kit-react
```

## Migrating from Preview

If you're migrating from `@workday/canvas-kit-preview-react/side-panel`, here are the key API
changes:

### Import Changes

```tsx
// Before (preview-react)
import {SidePanel, useSidePanel} from '@workday/canvas-kit-preview-react/side-panel';

// After (react)
import {SidePanel, useSidePanelModel} from '@workday/canvas-kit-react/side-panel';
```

### Hook API Changes

| Preview (`useSidePanel`)         | Main (`useSidePanelModel`)                              |
| -------------------------------- | ------------------------------------------------------- |
| `initialExpanded: boolean`       | `initialTransitionState: 'expanded' \| 'collapsed'`     |
| `origin: 'left' \| 'right'`      | `origin: 'start' \| 'end'`                              |
| Returns `expanded: boolean`      | Returns `model.state.transitionState`                   |
| Returns `setExpanded(bool)`      | Use `model.events.expand()` / `model.events.collapse()` |
| Returns `panelProps` to spread   | Props applied automatically via `elemPropsHook`         |
| Returns `labelProps` to spread   | Use `id={model.state.labelId}` on label element         |
| Returns `controlProps` to spread | Props applied automatically to `SidePanel.ToggleButton` |

### Component API Changes

| Preview                                        | Main                                              |
| ---------------------------------------------- | ------------------------------------------------- |
| `<SidePanel {...panelProps}>`                  | `<SidePanel model={model}>` or just `<SidePanel>` |
| `<SidePanel.ToggleButton {...controlProps} />` | `<SidePanel.ToggleButton />`                      |
| `<Heading {...labelProps}>`                    | `<SidePanel.Heading>Panel Title</SidePanel.Heading>`              |
| `expanded` prop on SidePanel                   | Managed by model's `transitionState`              |
| `touched` prop on SidePanel                    | Managed internally                                |
| `onExpandedChange` callback                    | Use `onStateTransition` and derive expanded state |
| `onStateTransition` on component               | `onStateTransition` on model config               |

### Code Migration Example

```tsx
// Before (preview-react)
const {expanded, panelProps, labelProps, controlProps} = useSidePanel({
  initialExpanded: false,
});

<SidePanel {...panelProps} origin="right" onExpandedChange={exp => console.log(exp)}>
  <SidePanel.ToggleButton {...controlProps} />
  <Heading {...labelProps}>Panel Title</Heading>
  {expanded && <Content />}
</SidePanel>;

// After (react)
const model = useSidePanelModel({
  initialTransitionState: 'collapsed',
  origin: 'end',
  onStateTransition: state => {
    const isExpanded = state === 'expanded' || state === 'expanding';
    console.log(isExpanded);
  },
});

<SidePanel model={model}>
  <SidePanel.ToggleButton />
  <SidePanel.Heading>Panel Title</SidePanel.Heading>
  {model.state.transitionState === 'expanded' && <Content />}
</SidePanel>;
```

### Checking Expanded State

```tsx
// Before (preview-react)
if (expanded) {
  /* ... */
}

// After (react) - for exact state
if (model.state.transitionState === 'expanded') {
  /* ... */
}

// After (react) - including animation states
const isExpanded =
  model.state.transitionState === 'expanded' || model.state.transitionState === 'expanding';
```

## Usage

### Basic Example

`SidePanel` is composed of three parts:

- The panel container (with an optional `model` prop)
- A heading (`SidePanel.Heading`) for the panel that is visually hidden when the panel is collapsed
- A toggle button (`SidePanel.ToggleButton`) to control the expand / collapse states

Bidirectional support is built into `SidePanel`. As seen in the example below, CSS Flexbox flips the
page layout and the panel's contents. `SidePanel` also has logic to flip the position and direction
of the `ToggleButton` as well as the direction of the expand / collapse animation. If you're using
CSS Flexbox for layouts and using the provided components, you shouldn't have to provide any custom
logic or styling for bidirectional support.

<ExampleCodeBlock code={Basic} />

### Hidden Name

`SidePanel`'s `<section>` element container should always have an accessible name to help screen
reader users understand the purpose of the panel. For this reason, we recommend using the
`SidePanel.Heading` component and setting the `hidden` prop to `true`. This will visually hide the
heading while keeping it accessible to screen readers.

<ExampleCodeBlock code={Heading} />

### Alternate Variant

`SidePanel` has one variant, `alternate`, which you can supply as a top-level prop. Default depth of
`alternate` variant is 5, if `alternate` SidePanel has an overlay behavior the depth 6 should be
used (this case is covered in the Examples section).

<ExampleCodeBlock code={AlternatePanel} />

### External Control

Sometimes you'll want to control `SidePanel`'s expand / collapse behavior from outside the
component. You can use the model's events (`model.events.expand()` and `model.events.collapse()`) to
programmatically control the panel.

#### Notes about accessibility

When using external controls, be mindful of accessibility:

- Use `aria-pressed` on toggle buttons to indicate the current state
- The `SidePanel.ToggleButton` inside the panel automatically receives the correct ARIA attributes
- External buttons should have their own accessible labels (don't rely on `aria-labelledby` pointing
  to the panel's label)

In the following example, we use the model's `transitionState` to determine the button's pressed
state and call `model.events.expand()` or `model.events.collapse()` on click.

<ExampleCodeBlock code={ExternalControl} />

### Right Origin

By default, `SidePanel` uses a `start` origin (left in LTR, right in RTL). This sets the
`ToggleButton`'s position and direction as well as the direction of the animation. You can set the
origin to `"end"` to flip these. The origin uses logical properties (`start`/`end`) for proper
bidirectional support.

<ExampleCodeBlock code={RightOrigin} />

### Always Open

If you do not need `SidePanel`'s expand / collapse behavior, you can simply omit the `ToggleButton`.

<ExampleCodeBlock code={AlwaysOpen} />

### Deriving Expanded State

If you need a simple boolean `expanded` state (similar to the preview-react `onExpandedChange`
callback), you can derive it from the `transitionState` using the `onStateTransition` callback on
the model.

### onStateTransition

The `onStateTransition` callback is called whenever the panel's transition state changes. This
includes all four states: `expanding`, `expanded`, `collapsing`, and `collapsed`. You can pass this
callback directly to the `SidePanel` component or to the `useSidePanelModel` hook.

The transition flow is:

1. **Collapsing**: `expanded` → `collapsing` → `collapsed`
2. **Expanding**: `collapsed` → `expanding` → `expanded`

This is useful for:

- Triggering side effects when the panel state changes
- Syncing the panel state with external state management
- Animating child components based on the transition state

<ExampleCodeBlock code={OnStateTransition} />

### Accessibility

`SidePanel` renders a `<section>` element with an accessible name provided by `aria-labelledby`,
which references the `SidePanel.Heading` component. This ensures screen reader users understand the
purpose of the panel.

#### Panel and Heading

- The `SidePanel.Heading` provides the accessible name for the panel via `aria-labelledby`
- When the panel is collapsed, the heading is automatically hidden visually but remains accessible
  to screen readers
- Use the `hidden` prop on `SidePanel.Heading` if you want the heading always visually hidden

#### Toggle Button

- `SidePanel.ToggleButton` automatically includes `aria-controls` (references the panel's `id`),
  `aria-pressed` (indicates current state), and `aria-describedby` (references the panel's heading)
- Developers must provide a static `aria-label` string on `SidePanel.ToggleButton` to describe the
  button's purpose (e.g., "Collapse View"). Avoid using ambiguous terms like "Toggle" in the label.
  Since `aria-pressed` communicates the state, avoid dynamically updating `aria-label`
- The button includes a Tooltip with customizable text via `tooltipTextExpand` and
  `tooltipTextCollapse` props (defaults: "Expand View" and "Collapse View")
- For optimal keyboard navigation, place `SidePanel.ToggleButton` as the first focusable element in
  the panel

## Component API

<SymbolDoc name="SidePanel" fileName="/react/" />

## Hooks

### useSidePanelModel

The `useSidePanelModel` hook creates a model for managing the SidePanel's state and events. You can
pass this model to the `SidePanel` component, or let the component create one internally.

```tsx
import {useSidePanelModel} from '@workday/canvas-kit-react/side-panel';

// Create a model with custom configuration
const model = useSidePanelModel({
  initialTransitionState: 'collapsed',
  origin: 'end',
  onStateTransition: state => console.log('State:', state),
});

// Access state
model.state.transitionState; // 'expanded' | 'expanding' | 'collapsed' | 'collapsing'
model.state.panelId; // unique ID for the panel
model.state.labelId; // unique ID for the label

// Trigger events
model.events.expand(); // Set to expanded (no animation)
model.events.collapse(); // Set to collapsed (no animation)
model.events.handleAnimationStart(); // Start expand/collapse animation
```

<SymbolDoc name="useSidePanelModel" fileName="/react/" />

### useSidePanelContainer

The `useSidePanelContainer` elemProps hook provides the necessary props for the SidePanel container
element, including `id`, `aria-labelledby`, and `onTransitionEnd`.

<SymbolDoc name="useSidePanelContainer" fileName="/react/" />

### useSidePanelToggleButton

The `useSidePanelToggleButton` elemProps hook provides ARIA attributes for the toggle
button, including `aria-controls`, `aria-pressed`, and `aria-describedby`.

<SymbolDoc name="useSidePanelToggleButton" fileName="/react/" />

## Specifications

<Specifications file="SidePanel.spec.ts" name="Side Panel" />