From 0bdca0469e97283100a6d4ec2e14ed0f2e6cf69e Mon Sep 17 00:00:00 2001 From: Gavyn McKenzie Date: Thu, 30 Apr 2026 10:12:55 +0100 Subject: [PATCH 01/10] docs: add design spec for sidebar status tags option MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds the design document for issue #22 — a new `sidebarTags` config option that renders full status tags in the sidebar (slightly smaller than the toolbar tag) instead of the existing dots. --- .../2026-04-30-sidebar-status-tags-design.md | 162 ++++++++++++++++++ 1 file changed, 162 insertions(+) create mode 100644 docs/superpowers/specs/2026-04-30-sidebar-status-tags-design.md diff --git a/docs/superpowers/specs/2026-04-30-sidebar-status-tags-design.md b/docs/superpowers/specs/2026-04-30-sidebar-status-tags-design.md new file mode 100644 index 0000000..608e8f4 --- /dev/null +++ b/docs/superpowers/specs/2026-04-30-sidebar-status-tags-design.md @@ -0,0 +1,162 @@ +# Sidebar status tags option + +**Issue:** [#22 — Customize the bullet located on the left menu](https://github.com/etchteam/storybook-addon-status/issues/22) +**Date:** 2026-04-30 +**Status:** Approved + +## Problem + +The sidebar currently shows a small 6px coloured `StatusDot` next to each story, with a `title` attribute on hover. Multiple users (most recently in [#22](https://github.com/etchteam/storybook-addon-status/issues/22)) have asked for the option to display the full status as a coloured tag — like the `StatusTag` shown in the toolbar — so the status is visible at a glance without hovering. + +## Goals + +- Provide a config option to render full status tags in the sidebar instead of dots. +- Tags in the sidebar should be slightly smaller than the toolbar tag so they fit cleanly next to story names. +- Keep the existing `sidebarDots` API working unchanged for users who don't opt in. + +## Non-goals + +- Changing the default sidebar appearance. Existing users still see dots unless they opt in. +- Custom icons or alternate sidebar indicators (out of scope; raised separately in the issue). +- Configurable indicator location. +- Making the sidebar tag a link. (See [Open decisions](#open-decisions-resolved).) + +## Configuration API + +A new optional config sits alongside the existing `sidebarDots`: + +```js +import { addons } from 'storybook/manager-api'; + +addons.setConfig({ + status: { + sidebarDots: 'single', // existing, unchanged + sidebarTags: 'single' | 'multiple' | 'none', // new + }, +}); +``` + +### Precedence rules + +When `renderLabel` runs: + +1. If `sidebarTags === 'none'` → render the fallback label only (no indicator). +2. If `sidebarTags === 'single'` → render a single sidebar tag (the first matching status). +3. If `sidebarTags === 'multiple'` → render a sidebar tag for every matching status. +4. If `sidebarTags` is not set (`undefined`) → fall back to the existing `sidebarDots` behavior, unchanged. + +`sidebarTags`, when set to a non-`undefined` value, fully overrides `sidebarDots`. The two settings are not combined. + +### Why a separate setting (option A from brainstorm) + +Considered alternatives: +- **B. Extend `sidebarDots` values** (e.g. add `'tag'`) — rejected: a setting named `sidebarDots` returning `'tag'` is confusing. +- **C. New structured `sidebar` config object** — rejected for now: bigger change, requires deprecating `sidebarDots`. Can evolve to this later if more sidebar options accumulate. +- **A. Parallel `sidebarTags` config** — chosen: smallest change, fully backward-compatible, mirrors existing naming. + +## Components + +The current `StatusTag.jsx` couples the toolbar's data fetching (via `useStorybookApi` / `useParameter` hooks) with the visual rendering of a tag. To share rendering between the toolbar and sidebar, we split it. + +### `StatusTagBase` (new — presentational) + +Single-status presentational component. Stateless, takes everything it needs as props. + +**Props:** +- `label: string` — raw status label (e.g. `'releaseCandidate'`). `StatusTagBase` applies `startCase` internally so callers don't need to know about display formatting. +- `status: { background?: string, color?: string, description?: string }` — the resolved status config. +- `url?: string` — when present and `variant === 'toolbar'`, renders an anchor. +- `variant: 'toolbar' | 'sidebar'` — controls size and link rendering. + +**Behavior:** +- Renders `` only when `variant === 'toolbar'` AND `url` is set. Otherwise renders ``. +- Applies the appropriate size styles for the variant. +- Sets `title={description}` for hover tooltip. + +**Sizes:** + +| | Font size | Line height | Padding (horizontal) | Border radius | +|---|---|---|---|---| +| `toolbar` (existing) | 11px | 20px | 0.5em | 0.25em | +| `sidebar` (new) | 10px | 16px | 0.4em | 0.25em | + +The base styled-component composes shared rules; variant-specific rules are layered on top via a `variant` prop or two extending styled-components. + +### `StatusTag` (toolbar — refactored) + +Keeps current responsibilities: read current story via hooks, resolve configs via `getStatusConfigs`, render a list. Internally maps each config to ``. Public behavior unchanged. + +### `SidebarStatusTag` (new — thin wrapper) + +A minimal component used from `manager.jsx`'s `renderLabel`. Takes a single resolved status config (label + status + description) and renders `` (no `url` passed — sidebar tags are never links per [Open decisions](#open-decisions-resolved)). + +This wrapper exists so `manager.jsx` can stay clean and the sidebar component is easy to reason about. + +## `manager.jsx` flow change + +The current logic in [src/manager.jsx](src/manager.jsx) reads `sidebarDotsConfig` and short-circuits on `'none'`. The new logic, in pseudocode: + +``` +const sidebarTagsConfig = statusAddonConfig?.sidebarTags; +const sidebarDotsConfig = statusAddonConfig?.sidebarDots; + +if (sidebarTagsConfig === 'none') return fallbackLabel; +if (sidebarTagsConfig === undefined && sidebarDotsConfig === 'none') return fallbackLabel; + +// ... unchanged: resolve statusConfigs from tags + parameters ... + +if (statusConfigs.length === 0) return fallbackLabel; + +const isTagMode = sidebarTagsConfig === 'single' || sidebarTagsConfig === 'multiple'; +const showMultiple = isTagMode + ? sidebarTagsConfig === 'multiple' + : sidebarDotsConfig === 'multiple'; + +const visibleConfigs = showMultiple ? statusConfigs : [statusConfigs[0]]; + +return ( + <> + {fallbackLabel} + {visibleConfigs.map(config => + isTagMode + ? + : + )} + +); +``` + +The default-status logic that resolves `background`/`color`/`description` already lives in the dot rendering and inside `StatusTag`'s map; the sidebar tag goes through `StatusTagBase`, which uses the same defaults logic shared with the toolbar. + +## Verification + +The repo has no unit test suite. Verification will be done in the running Storybook (`npm run storybook`). + +**Test matrix:** + +| Scenario | Config | Expected | +|---|---|---| +| Default | (no `sidebarTags`, no `sidebarDots`) | Single dot per story (existing behavior). | +| Existing dots opt-in | `sidebarDots: 'multiple'` | Multiple dots per story (existing behavior). | +| New tag mode — single | `sidebarTags: 'single'` | One sidebar tag per story (first status), no dots. | +| New tag mode — multiple | `sidebarTags: 'multiple'` | All sidebar tags per story, no dots. | +| New tag mode — none | `sidebarTags: 'none'` | No indicator, regardless of `sidebarDots`. | +| Long status names | Tag mode, custom status with long name | Tag does not break the row layout; truncation or wrap looks acceptable. | +| Hover title | Any tag mode | Hovering the sidebar tag shows the description. | +| URL is ignored in sidebar | Story with `parameters.status.url` set, tag mode | Sidebar tag renders as `` not ``; toolbar tag still renders as ``. | +| Custom statuses | Tag mode + custom status from `manager.js` | Renders with custom colours. | + +The demo `.storybook/manager.js` will be updated during implementation to make the new mode easy to verify. + +## Documentation + +Update `Readme.md` configuration section to: +- Document `sidebarTags` next to `sidebarDots`. +- Note that `sidebarTags` (when set) overrides `sidebarDots`. +- Note that URLs on statuses are not used in sidebar tags (the toolbar tag is the linked one). + +## Open decisions (resolved) + +1. **Configuration API shape** — chose option A (parallel `sidebarTags` setting). [Q1] +2. **Link behavior in sidebar** — sidebar tags are always rendered as ``, never ``, even when the status has a URL. The toolbar tag remains the linked surface. Reason: a link inside Storybook's already-clickable sidebar row creates conflicting interactions, and adding `stopPropagation` was deemed unnecessary complexity. [Q2] +3. **Sidebar tag size** — slightly smaller than toolbar: 10px font, 16px line-height, 0.4em horizontal padding. [Q3] From 0b0cef8b80c5ee2db1f10619794b77a0da7ae32a Mon Sep 17 00:00:00 2001 From: Gavyn McKenzie Date: Thu, 30 Apr 2026 10:15:41 +0100 Subject: [PATCH 02/10] docs: add implementation plan for sidebar status tags Refs #22 --- .../plans/2026-04-30-sidebar-status-tags.md | 660 ++++++++++++++++++ 1 file changed, 660 insertions(+) create mode 100644 docs/superpowers/plans/2026-04-30-sidebar-status-tags.md diff --git a/docs/superpowers/plans/2026-04-30-sidebar-status-tags.md b/docs/superpowers/plans/2026-04-30-sidebar-status-tags.md new file mode 100644 index 0000000..f5b62fb --- /dev/null +++ b/docs/superpowers/plans/2026-04-30-sidebar-status-tags.md @@ -0,0 +1,660 @@ +# Sidebar Status Tags Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Add a `sidebarTags` config option that renders full status tags in the sidebar (slightly smaller than the toolbar tag) instead of the existing dots, addressing [issue #22](https://github.com/etchteam/storybook-addon-status/issues/22). + +**Architecture:** Refactor `StatusTag` into a presentational `StatusTagBase` (variant-aware: `'toolbar'` | `'sidebar'`) plus thin wrappers. The toolbar continues to map current-story configs and renders `StatusTagBase` with the `'toolbar'` variant; a new `SidebarStatusTag` wrapper renders a single config with the `'sidebar'` variant (10px font, 16px line-height, 0.4em padding, span only — no link). `manager.jsx`'s `renderLabel` reads the new `sidebarTags` config and chooses tags vs. existing dots; when unset, behavior is unchanged. + +**Tech Stack:** React 18, `storybook/theming` (styled-components), `lodash/startCase`, Storybook 10 manager API. + +**Spec:** [docs/superpowers/specs/2026-04-30-sidebar-status-tags-design.md](docs/superpowers/specs/2026-04-30-sidebar-status-tags-design.md) + +--- + +## File Structure + +| File | Action | Responsibility | +|---|---|---| +| `src/components/StatusTagBase.jsx` | Create | Presentational, variant-aware single status tag (span or link). | +| `src/components/StatusTag.jsx` | Modify | Toolbar entry point. Keeps hooks; delegates rendering to `StatusTagBase`. | +| `src/components/SidebarStatusTag.jsx` | Create | Thin wrapper that renders a single config via `StatusTagBase` with `variant="sidebar"`, no link. | +| `src/manager.jsx` | Modify | Read new `sidebarTags` config; choose tag vs. dot rendering with documented precedence. | +| `.storybook/manager.js` | Modify | Demo config switched to `sidebarTags: 'multiple'` for verification. | +| `Readme.md` | Modify | Document the new `sidebarTags` option. | + +The repo has **no unit test suite** (confirmed against `package.json` — only `prebuild`, `prepare`, `storybook`, `build-storybook`, `release` scripts). Verification is via the running Storybook dev server. Each task includes a build/lint check; a single manual-verification task at the end exercises the matrix from the spec. + +--- + +## Task 1: Create `StatusTagBase` presentational component + +**Files:** +- Create: `src/components/StatusTagBase.jsx` + +- [ ] **Step 1: Create the file** + +```jsx +import startCase from 'lodash/startCase'; +import React from 'react'; +import { styled, css } from 'storybook/theming'; + +import { + defaultStatuses, + defaultBackground, + defaultColor, +} from '../defaults'; + +const baseStyles = css` + align-self: center; + border-radius: 0.25em; + font-weight: 700; + text-decoration: none; + text-transform: uppercase; + user-select: none; +`; + +const toolbarStyles = css` + font-size: 11px; + line-height: 20px; + padding: 0 0.5em; +`; + +const sidebarStyles = css` + font-size: 10px; + line-height: 16px; + margin-left: 0.5em; + padding: 0 0.4em; +`; + +const variantStyles = ({ variant }) => + variant === 'sidebar' ? sidebarStyles : toolbarStyles; + +const LinkTag = styled.a` + ${baseStyles} + ${variantStyles} +`; + +const TextTag = styled.span` + ${baseStyles} + ${variantStyles} +`; + +const StatusTagBase = ({ label, status, url, variant = 'toolbar' }) => { + const resolvedColor = + status?.color ?? + (defaultStatuses[label] ? defaultStatuses[label].color : defaultColor); + const resolvedBackground = + status?.background ?? + (defaultStatuses[label] + ? defaultStatuses[label].background + : defaultBackground); + + const style = { + color: resolvedColor, + backgroundColor: resolvedBackground, + }; + + const description = status?.description; + const displayLabel = startCase(label); + const isLink = variant === 'toolbar' && !!url; + + if (isLink) { + return ( + + {displayLabel} + + ); + } + + return ( + + {displayLabel} + + ); +}; + +export default StatusTagBase; +``` + +**Notes:** +- `label` is the raw camelCase status name (e.g. `'releaseCandidate'`); `startCase` is applied internally for display. Defaults lookup uses the raw `label` so it correctly matches built-in keys like `releaseCandidate`. This matches `StatusDot`'s lookup (which uses the raw `type` prop). +- `align-self: center` is preserved from the existing toolbar styles. `margin-left: 0.5em` on the sidebar variant mirrors the existing `StatusDot` spacing. +- `padding: 0 0.5em` was duplicated in the original `tagStyles` block. The duplicate is dropped here; only one rule is applied per variant. + +- [ ] **Step 2: Run lint to verify the file is clean** + +Run: `npx eslint src/components/StatusTagBase.jsx` +Expected: no errors. + +- [ ] **Step 3: Run the build to verify it compiles** + +Run: `npm run prepare` +Expected: `tsup` exits successfully and writes to `dist/`. + +- [ ] **Step 4: Commit** + +```bash +git add src/components/StatusTagBase.jsx +git commit -m "feat: add StatusTagBase presentational component + +Variant-aware (toolbar | sidebar) presentational status tag. Renders +either a span or an anchor depending on variant + url. Internalises +the defaults-fallback logic and label start-casing. + +Refs #22" +``` + +--- + +## Task 2: Refactor `StatusTag` (toolbar) to use `StatusTagBase` + +**Files:** +- Modify: `src/components/StatusTag.jsx` + +- [ ] **Step 1: Replace the file contents** + +Replace the entire contents of `src/components/StatusTag.jsx` with: + +```jsx +import React from 'react'; +import { useParameter, useStorybookApi, addons } from 'storybook/manager-api'; + +import { ADDON_ID } from '../constants'; +import { getStatusConfigs } from '../getStatusConfigs'; +import StatusTagBase from './StatusTagBase'; + +const StatusTag = () => { + const api = useStorybookApi(); + const tags = api.getCurrentStoryData()?.tags ?? []; + + const parameters = useParameter(ADDON_ID, null); + const customConfigs = addons.getConfig()?.[ADDON_ID]?.statuses; + + const statusConfigs = getStatusConfigs({ + tags, + parameters, + customConfigs, + }); + + if (!statusConfigs?.length) { + return null; + } + + return ( + <> + {statusConfigs.map((statusConfig) => ( + + ))} + + ); +}; + +export default StatusTag; +``` + +**Notes:** +- Removes the local `tagStyles`, `LinkTag`, `TextTag`, and inline color/background resolution — all of that now lives in `StatusTagBase`. +- Removes unused imports (`startCase`, `styled`, `css`, default colour constants). +- Public behavior is preserved: same DOM output (`` for URL statuses, `` otherwise) at the existing toolbar size. + +- [ ] **Step 2: Run lint to verify the file is clean** + +Run: `npx eslint src/components/StatusTag.jsx` +Expected: no errors. + +- [ ] **Step 3: Run the build to verify it compiles** + +Run: `npm run prepare` +Expected: `tsup` exits successfully. + +- [ ] **Step 4: Smoke-check the toolbar in Storybook** + +Run: `npm run storybook` (in a separate terminal — leave running for later tasks). +Open http://localhost:6006, pick a story with a status (e.g. an existing `'beta'` story), and confirm the toolbar shows the same status tag as before this change. Hover should still show the description. + +- [ ] **Step 5: Commit** + +```bash +git add src/components/StatusTag.jsx +git commit -m "refactor: route StatusTag through StatusTagBase + +The toolbar StatusTag now delegates rendering to the new +StatusTagBase presentational component. No public behaviour change. + +Refs #22" +``` + +--- + +## Task 3: Create `SidebarStatusTag` wrapper + +**Files:** +- Create: `src/components/SidebarStatusTag.jsx` + +- [ ] **Step 1: Create the file** + +```jsx +import React from 'react'; + +import StatusTagBase from './StatusTagBase'; + +const SidebarStatusTag = ({ statusConfig }) => ( + +); + +export default SidebarStatusTag; +``` + +**Notes:** +- Deliberately does not pass `url`. Sidebar tags are always rendered as a `` per the spec (URL is intentionally ignored to avoid nested clickable elements inside Storybook's sidebar row). +- Takes a single `statusConfig` (not an array) so the caller controls iteration and `key`. + +- [ ] **Step 2: Run lint to verify the file is clean** + +Run: `npx eslint src/components/SidebarStatusTag.jsx` +Expected: no errors. + +- [ ] **Step 3: Run the build to verify it compiles** + +Run: `npm run prepare` +Expected: `tsup` exits successfully. + +- [ ] **Step 4: Commit** + +```bash +git add src/components/SidebarStatusTag.jsx +git commit -m "feat: add SidebarStatusTag wrapper + +Thin wrapper around StatusTagBase fixing variant=\"sidebar\" and +omitting url so sidebar tags are always rendered as spans. + +Refs #22" +``` + +--- + +## Task 4: Wire `sidebarTags` into `manager.jsx` `renderLabel` + +**Files:** +- Modify: `src/manager.jsx` + +- [ ] **Step 1: Replace the file contents** + +Replace the entire contents of `src/manager.jsx` with: + +```jsx +import startCase from 'lodash/startCase'; +import React from 'react'; +import { addons, types } from 'storybook/manager-api'; + +import SidebarStatusTag from './components/SidebarStatusTag'; +import StatusDot from './components/StatusDot'; +import StatusTag from './components/StatusTag'; +import { ADDON_ID } from './constants'; +import { getStatusConfigs } from './getStatusConfigs'; + +addons.register(ADDON_ID, (api) => { + const addonsConfig = addons.getConfig(); + const existingSidebarConfig = addonsConfig?.sidebar ?? {}; + + addons.add(ADDON_ID, { + title: 'Status', + type: types.TOOL, + render: () => , + }); + + const statusAddonConfig = addonsConfig?.[ADDON_ID] ?? {}; + + addons.setConfig({ + sidebar: { + ...existingSidebarConfig, + renderLabel: (item) => { + const { name, tags } = item; + const isLeaf = ['root', 'group', 'story'].includes(item.type); + + try { + const fallbackLabel = existingSidebarConfig?.renderLabel + ? existingSidebarConfig.renderLabel(item) + : name; + + const sidebarTagsConfig = statusAddonConfig?.sidebarTags; + const sidebarDotsConfig = statusAddonConfig?.sidebarDots; + + // sidebarTags, when set, fully overrides sidebarDots. + const isTagMode = + sidebarTagsConfig === 'single' || sidebarTagsConfig === 'multiple'; + + if (sidebarTagsConfig === 'none') { + return fallbackLabel; + } + + if (sidebarTagsConfig === undefined && sidebarDotsConfig === 'none') { + return fallbackLabel; + } + + const parameters = api.getParameters(item.id, ADDON_ID); + + // item can be a Root | Group | Story + if (!isLeaf || (tags.length === 0 && !parameters?.type)) { + return fallbackLabel; + } + + // Get custom status configurations from the current story's parameters. + // This will include any custom statuses defined in manager.js, preview.js or story parameters. + // However custom statuses from story parameters will only be available in the sidebar + // when viewing that story. This is a storybook limitation: + // https://github.com/storybookjs/storybook/discussions/24022 + const customConfigs = + statusAddonConfig?.statuses || + api.getCurrentStoryData().parameters?.status?.statuses; + + let statusConfigs = getStatusConfigs({ + tags, + parameters, + customConfigs, + }); + + if (statusConfigs.length === 0) { + return fallbackLabel; + } + + const showMultiple = isTagMode + ? sidebarTagsConfig === 'multiple' + : sidebarDotsConfig === 'multiple'; + + if (!showMultiple) { + statusConfigs = [statusConfigs[0]]; + } + + return ( + <> + {fallbackLabel} + {statusConfigs.map((statusConfig) => { + if (isTagMode) { + return ( + + ); + } + + const { + label: statusName, + status: { background, description }, + } = statusConfig; + + return ( + + ); + })} + + ); + } catch (error) { + return name; + } + }, + }, + }); +}); +``` + +**Notes on the precedence logic** (mirrors the spec's "Precedence rules" section): +- `sidebarTags === 'none'` → no indicator, regardless of `sidebarDots`. +- `sidebarTags === 'single' | 'multiple'` → tag mode; `sidebarDots` is ignored. +- `sidebarTags === undefined` AND `sidebarDots === 'none'` → no indicator (existing behavior). +- `sidebarTags === undefined` otherwise → existing dot behavior, where `sidebarDots === 'multiple'` shows all and any other value (or `undefined`) shows only the first. + +- [ ] **Step 2: Run lint** + +Run: `npx eslint src/manager.jsx` +Expected: no errors. + +- [ ] **Step 3: Run the build** + +Run: `npm run prepare` +Expected: `tsup` exits successfully. + +- [ ] **Step 4: Commit** + +```bash +git add src/manager.jsx +git commit -m "feat: support sidebarTags option in renderLabel + +Adds a new \`status.sidebarTags\` config ('single' | 'multiple' | +'none') that, when set, renders SidebarStatusTag components in the +sidebar instead of dots. When unset, the existing sidebarDots +behaviour is preserved exactly. + +Closes #22" +``` + +--- + +## Task 5: Update demo `.storybook/manager.js` for verification + +**Files:** +- Modify: `.storybook/manager.js` + +- [ ] **Step 1: Replace the file contents** + +Replace the entire contents of `.storybook/manager.js` with: + +```js +import { addons } from "storybook/manager-api"; + +addons.setConfig({ + status: { + statuses: { + customStatus: { + background: '#0000ff', + color: '#ffffff', + description: 'This component is stable and released', + }, + }, + // 'single' | 'multiple' | 'none'. When set, takes precedence over sidebarDots. + sidebarTags: 'multiple', + sidebarDots: 'multiple', // 'single' | 'multiple' | 'none'. 'single' is the default + }, +}); +``` + +**Note:** This is dev-only config used to exercise the new mode in `npm run storybook`. We keep `sidebarDots: 'multiple'` in place so it's easy to flip `sidebarTags` between values (or comment it out) and confirm the precedence rules behave as documented. + +- [ ] **Step 2: Verify Storybook reloads cleanly** + +If the Storybook dev server from Task 2 is still running, it should hot-reload. Otherwise: `npm run storybook`. +Open http://localhost:6006 and confirm the addon still loads (no console errors). + +- [ ] **Step 3: Commit** + +```bash +git add .storybook/manager.js +git commit -m "chore(demo): enable sidebarTags multiple in dev storybook + +Refs #22" +``` + +--- + +## Task 6: Manual verification matrix + +**Files:** none. + +This task exercises every row of the verification matrix in the spec. The Storybook dev server (`npm run storybook`) must be running. After each scenario, edit `.storybook/manager.js` and let Storybook hot-reload. Check each box only after observing the expected behavior. + +- [ ] **Default (no `sidebarTags`, no `sidebarDots`):** comment out both lines. + Expected: one **dot** per story (single, default). + +- [ ] **Existing dots opt-in (`sidebarDots: 'multiple'`):** comment out `sidebarTags`, keep `sidebarDots: 'multiple'`. + Expected: multiple **dots** per story for stories with multiple statuses. + +- [ ] **New tag mode — single (`sidebarTags: 'single'`):** + Expected: one **tag** per story (the first matching status), no dots. Tag is at the smaller sidebar size. + +- [ ] **New tag mode — multiple (`sidebarTags: 'multiple'`):** + Expected: a sidebar tag for **every** matching status, no dots. + +- [ ] **New tag mode — none (`sidebarTags: 'none'`, `sidebarDots: 'multiple'`):** + Expected: **no** indicator at all (sidebarTags overrides sidebarDots). Toolbar tag still shows. + +- [ ] **Long status names:** with `sidebarTags: 'multiple'`, view a story whose statuses include `customStatus` (long-ish label after `startCase` → "Custom Status"). + Expected: row layout doesn't break; long names either fit or wrap acceptably. + +- [ ] **Hover title:** with any tag mode, hover a sidebar tag. + Expected: native browser tooltip shows the status `description`. + +- [ ] **URL ignored in sidebar:** add `parameters.status.url` to a story (or pick one that already has it from `stories/`). With `sidebarTags: 'single'`: + Expected: sidebar tag is a `` (not an ``); toolbar tag is still an ``. Inspect the DOM to confirm. + +- [ ] **Custom statuses:** with `sidebarTags: 'multiple'`, navigate to a story that uses `customStatus` (defined in `.storybook/manager.js`). + Expected: sidebar tag renders with the configured `#0000ff` background. + +- [ ] **Restore demo config to a sensible default before commit:** + +After verification, set `.storybook/manager.js` back to: + +```js +import { addons } from "storybook/manager-api"; + +addons.setConfig({ + status: { + statuses: { + customStatus: { + background: '#0000ff', + color: '#ffffff', + description: 'This component is stable and released', + }, + }, + sidebarTags: 'multiple', + sidebarDots: 'multiple', + }, +}); +``` + +(This is the same as the end of Task 5 — verification-only edits should not have been committed.) + +- [ ] **Commit only if a fix was needed.** If verification surfaced no bugs, no commit is created in this task. If a bug was found and fixed, commit the fix with a descriptive message referencing the failing scenario. + +--- + +## Task 7: Update the Readme + +**Files:** +- Modify: `Readme.md` + +- [ ] **Step 1: Update the Configuration section** + +Replace the existing Configuration code example block in `Readme.md` (currently the block starting `import { addons } from "storybook/manager-api";` and ending with `});` directly under the **Configuration** heading) with: + +````markdown +```js +import { addons } from "storybook/manager-api"; + +addons.setConfig({ + status: { + statuses: { + released: { + background: '#0000ff', + color: '#ffffff', + description: 'This component is stable and released', + }, + }, + sidebarDots: 'single', // 'single' | 'multiple' | 'none'. 'single' is the default + sidebarTags: 'single', // 'single' | 'multiple' | 'none'. When set, overrides sidebarDots and renders full status tags in the sidebar instead of dots. + }, +}); +``` + +By default the sidebar shows a small coloured dot next to each story. Setting `sidebarTags` to `'single'` or `'multiple'` switches the sidebar to render the full status tag — the same colour and label as the toolbar tag, but slightly smaller. `sidebarTags: 'none'` hides the indicator entirely (and overrides `sidebarDots`). When `sidebarTags` is not set, `sidebarDots` keeps its existing behaviour. + +**Note:** Status URLs are intentionally not used by sidebar tags — clicking a story row in the sidebar should always navigate to that story. Use the toolbar tag (which still renders as a link) to follow the URL. +```` + +**Note:** The replacement preserves the existing `sidebarDots` line and adds `sidebarTags` immediately below it, then adds a short prose paragraph explaining the new option and the URL-handling caveat. + +- [ ] **Step 2: Eyeball the Readme** + +Open `Readme.md` and confirm the new section reads cleanly and matches the rest of the document's tone. + +- [ ] **Step 3: Commit** + +```bash +git add Readme.md +git commit -m "docs: document sidebarTags configuration option + +Refs #22" +``` + +--- + +## Task 8: Open the pull request + +**Files:** none. + +- [ ] **Step 1: Push the branch** + +```bash +git push -u origin feature/sidebar-status-tags +``` + +- [ ] **Step 2: Open a PR linked to issue #22** + +```bash +gh pr create --title "feat: optional status tags in sidebar (closes #22)" --body "$(cat <<'EOF' +## Summary +- Adds a new `status.sidebarTags` config (`'single' | 'multiple' | 'none'`) that renders full status tags in the sidebar instead of dots. +- Refactors `StatusTag` into a presentational `StatusTagBase` so the toolbar and sidebar share rendering. The sidebar variant is slightly smaller (10px / 16px line-height / 0.4em padding). +- When `sidebarTags` is unset, the existing `sidebarDots` behaviour is preserved exactly. + +Closes #22. + +## Test plan +- [ ] Default (no config): single dot per story. +- [ ] `sidebarDots: 'multiple'`: multiple dots per story. +- [ ] `sidebarTags: 'single'`: one sidebar tag per story. +- [ ] `sidebarTags: 'multiple'`: all sidebar tags per story. +- [ ] `sidebarTags: 'none'`: no indicator (overrides `sidebarDots`). +- [ ] Sidebar tag is rendered as `` even when the status has a `url`. +- [ ] Toolbar tag is unchanged (still renders as `` for statuses with a `url`). +- [ ] Hovering a sidebar tag shows the status description. +- [ ] Custom statuses defined in `manager.js` render with the configured colours. +EOF +)" +``` + +- [ ] **Step 3: Capture the PR URL** and report it back. + +--- + +## Self-review + +Run through the spec section-by-section and confirm coverage. (Done while writing this plan; no gaps identified.) + +- **Problem / Goals / Non-goals:** Covered by Tasks 1–4 (feature) + Task 7 (docs). +- **Configuration API + precedence rules:** Implemented in Task 4. The `if (sidebarTagsConfig === 'none')` short-circuit, the `isTagMode` branch, and the `showMultiple` choice each map to a numbered rule in the spec. +- **`StatusTagBase`:** Task 1 (matches the props table in the spec exactly: `label`, `status`, `url`, `variant`). +- **`StatusTag` (toolbar refactor):** Task 2. +- **`SidebarStatusTag`:** Task 3 (passes no `url`, fixed variant, takes a single config). +- **`manager.jsx` flow change:** Task 4 (matches the spec's pseudocode). +- **Verification matrix:** Task 6 — every row in the spec's table is a checkbox. +- **Documentation:** Task 7 — covers `sidebarTags` next to `sidebarDots`, precedence note, URL caveat. +- **Open decisions resolved (Q1 API shape, Q2 link behavior, Q3 sizing):** Q1 → Task 4 (parallel `sidebarTags` setting); Q2 → Task 3 (`SidebarStatusTag` omits `url`) + Task 1 (`isLink` only when `variant === 'toolbar'`); Q3 → Task 1 (`sidebarStyles` block sets 10px / 16px / 0.4em). + +No placeholders. Type/prop names consistent across tasks (`statusConfig`, `label`, `status`, `url`, `variant`, `sidebarTags`, `isTagMode`, `showMultiple`). From 0827703e2065b3409853ab8d9067ebf8ef266a3c Mon Sep 17 00:00:00 2001 From: Gavyn McKenzie Date: Thu, 30 Apr 2026 10:16:42 +0100 Subject: [PATCH 03/10] feat: add StatusTagBase presentational component Variant-aware (toolbar | sidebar) presentational status tag. Renders either a span or an anchor depending on variant + url. Internalises the defaults-fallback logic and label start-casing. Refs #22 --- src/components/StatusTagBase.jsx | 76 ++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 src/components/StatusTagBase.jsx diff --git a/src/components/StatusTagBase.jsx b/src/components/StatusTagBase.jsx new file mode 100644 index 0000000..d3b6470 --- /dev/null +++ b/src/components/StatusTagBase.jsx @@ -0,0 +1,76 @@ +import startCase from 'lodash/startCase'; +import React from 'react'; +import { styled, css } from 'storybook/theming'; + +import { defaultStatuses, defaultBackground, defaultColor } from '../defaults'; + +const baseStyles = css` + align-self: center; + border-radius: 0.25em; + font-weight: 700; + text-decoration: none; + text-transform: uppercase; + user-select: none; +`; + +const toolbarStyles = css` + font-size: 11px; + line-height: 20px; + padding: 0 0.5em; +`; + +const sidebarStyles = css` + font-size: 10px; + line-height: 16px; + margin-left: 0.5em; + padding: 0 0.4em; +`; + +const variantStyles = ({ variant }) => + variant === 'sidebar' ? sidebarStyles : toolbarStyles; + +const LinkTag = styled.a` + ${baseStyles} + ${variantStyles} +`; + +const TextTag = styled.span` + ${baseStyles} + ${variantStyles} +`; + +const StatusTagBase = ({ label, status, url, variant = 'toolbar' }) => { + const resolvedColor = + status?.color ?? + (defaultStatuses[label] ? defaultStatuses[label].color : defaultColor); + const resolvedBackground = + status?.background ?? + (defaultStatuses[label] + ? defaultStatuses[label].background + : defaultBackground); + + const style = { + color: resolvedColor, + backgroundColor: resolvedBackground, + }; + + const description = status?.description; + const displayLabel = startCase(label); + const isLink = variant === 'toolbar' && !!url; + + if (isLink) { + return ( + + {displayLabel} + + ); + } + + return ( + + {displayLabel} + + ); +}; + +export default StatusTagBase; From 408c5b30af0c4b4f8181899072dd07d56ceaac77 Mon Sep 17 00:00:00 2001 From: Gavyn McKenzie Date: Thu, 30 Apr 2026 10:17:30 +0100 Subject: [PATCH 04/10] refactor: route StatusTag through StatusTagBase The toolbar StatusTag now delegates rendering to the new StatusTagBase presentational component. No public behaviour change. Refs #22 --- src/components/StatusTag.jsx | 66 ++++++------------------------------ 1 file changed, 10 insertions(+), 56 deletions(-) diff --git a/src/components/StatusTag.jsx b/src/components/StatusTag.jsx index ac15287..b5dcfd1 100644 --- a/src/components/StatusTag.jsx +++ b/src/components/StatusTag.jsx @@ -1,32 +1,10 @@ -import startCase from 'lodash/startCase'; import React from 'react'; import { useParameter, useStorybookApi, addons } from 'storybook/manager-api'; -import { styled, css } from 'storybook/theming'; import { ADDON_ID } from '../constants'; -import { defaultStatuses, defaultBackground, defaultColor } from '../defaults'; import { getStatusConfigs } from '../getStatusConfigs'; -const tagStyles = css` - align-self: center; - border-radius: 0.25em; - padding: 0 0.5em; - text-decoration: none; - user-select: none; - font-size: 11px; - text-transform: uppercase; - line-height: 20px; - font-weight: 700; - padding: 0 0.5em; -`; - -const LinkTag = styled.a` - ${tagStyles} -`; - -const TextTag = styled.span` - ${tagStyles} -`; +import StatusTagBase from './StatusTagBase'; const StatusTag = () => { const api = useStorybookApi(); @@ -47,39 +25,15 @@ const StatusTag = () => { return ( <> - {statusConfigs.map((statusConfig) => { - const { background, color, description } = statusConfig.status; - const statusUrl = statusConfig.url; - const label = startCase(statusConfig.label); - - const style = { - color: - color ?? - (defaultStatuses[label] - ? defaultStatuses[label].color - : defaultColor), - backgroundColor: - background ?? - (defaultStatuses[label] - ? defaultStatuses[label].background - : defaultBackground), - }; - - return statusUrl ? ( - - {label} - - ) : ( - - {label} - - ); - })} + {statusConfigs.map((statusConfig) => ( + + ))} ); }; From 15a12780bc19ba0dc38643d93d987c937b946657 Mon Sep 17 00:00:00 2001 From: Gavyn McKenzie Date: Thu, 30 Apr 2026 10:17:58 +0100 Subject: [PATCH 05/10] feat: add SidebarStatusTag wrapper Thin wrapper around StatusTagBase fixing variant="sidebar" and omitting url so sidebar tags are always rendered as spans. Refs #22 --- src/components/SidebarStatusTag.jsx | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 src/components/SidebarStatusTag.jsx diff --git a/src/components/SidebarStatusTag.jsx b/src/components/SidebarStatusTag.jsx new file mode 100644 index 0000000..940180b --- /dev/null +++ b/src/components/SidebarStatusTag.jsx @@ -0,0 +1,13 @@ +import React from 'react'; + +import StatusTagBase from './StatusTagBase'; + +const SidebarStatusTag = ({ statusConfig }) => ( + +); + +export default SidebarStatusTag; From ffb120f18874d28b07c4fe19a4d3dfaee8db597c Mon Sep 17 00:00:00 2001 From: Gavyn McKenzie Date: Thu, 30 Apr 2026 10:18:37 +0100 Subject: [PATCH 06/10] feat: support sidebarTags option in renderLabel Adds a new \`status.sidebarTags\` config ('single' | 'multiple' | 'none') that, when set, renders SidebarStatusTag components in the sidebar instead of dots. When unset, the existing sidebarDots behaviour is preserved exactly. Closes #22 --- src/manager.jsx | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/src/manager.jsx b/src/manager.jsx index 3cd0343..09c3ac3 100644 --- a/src/manager.jsx +++ b/src/manager.jsx @@ -2,6 +2,7 @@ import startCase from 'lodash/startCase'; import React from 'react'; import { addons, types } from 'storybook/manager-api'; +import SidebarStatusTag from './components/SidebarStatusTag'; import StatusDot from './components/StatusDot'; import StatusTag from './components/StatusTag'; import { ADDON_ID } from './constants'; @@ -31,9 +32,18 @@ addons.register(ADDON_ID, (api) => { ? existingSidebarConfig.renderLabel(item) : name; + const sidebarTagsConfig = statusAddonConfig?.sidebarTags; const sidebarDotsConfig = statusAddonConfig?.sidebarDots; - if (sidebarDotsConfig === 'none') { + // sidebarTags, when set, fully overrides sidebarDots. + const isTagMode = + sidebarTagsConfig === 'single' || sidebarTagsConfig === 'multiple'; + + if (sidebarTagsConfig === 'none') { + return fallbackLabel; + } + + if (sidebarTagsConfig === undefined && sidebarDotsConfig === 'none') { return fallbackLabel; } @@ -63,7 +73,11 @@ addons.register(ADDON_ID, (api) => { return fallbackLabel; } - if (sidebarDotsConfig !== 'multiple') { + const showMultiple = isTagMode + ? sidebarTagsConfig === 'multiple' + : sidebarDotsConfig === 'multiple'; + + if (!showMultiple) { statusConfigs = [statusConfigs[0]]; } @@ -71,6 +85,15 @@ addons.register(ADDON_ID, (api) => { <> {fallbackLabel} {statusConfigs.map((statusConfig) => { + if (isTagMode) { + return ( + + ); + } + const { label: statusName, status: { background, description }, From 5137e301a523e0ec90520eeeb804a5e309a0c2a0 Mon Sep 17 00:00:00 2001 From: Gavyn McKenzie Date: Thu, 30 Apr 2026 10:18:55 +0100 Subject: [PATCH 07/10] chore(demo): enable sidebarTags multiple in dev storybook Refs #22 --- .storybook/manager.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.storybook/manager.js b/.storybook/manager.js index c67c2bf..c1f6b43 100644 --- a/.storybook/manager.js +++ b/.storybook/manager.js @@ -9,6 +9,8 @@ addons.setConfig({ description: 'This component is stable and released', }, }, + // 'single' | 'multiple' | 'none'. When set, takes precedence over sidebarDots. + sidebarTags: 'multiple', sidebarDots: 'multiple', // 'single' | 'multiple' | 'none'. 'single' is the default }, }); From b861e1187c68edb7791858b288c2e3321c65d816 Mon Sep 17 00:00:00 2001 From: Gavyn McKenzie Date: Thu, 30 Apr 2026 10:20:25 +0100 Subject: [PATCH 08/10] docs: document sidebarTags configuration option Refs #22 --- Readme.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index 2bfc708..6f29a90 100644 --- a/Readme.md +++ b/Readme.md @@ -22,7 +22,7 @@ export default { }; ``` -In `manager.js` you can globally configure custom status configurations, or overwrite the built in "beta", "deprecated", "stable" & "releaseCandidate". You can also change how status dots will appear in the sidebar with the `sidebarDots` prop. +In `manager.js` you can globally configure custom status configurations, or overwrite the built in "beta", "deprecated", "stable" & "releaseCandidate". You can also change how statuses appear in the sidebar with the `sidebarDots` and `sidebarTags` props. ```js import { addons } from "storybook/manager-api"; @@ -37,10 +37,15 @@ addons.setConfig({ }, }, sidebarDots: 'single', // 'single' | 'multiple' | 'none'. 'single' is the default + sidebarTags: 'single', // 'single' | 'multiple' | 'none'. When set, overrides sidebarDots and renders full status tags in the sidebar instead of dots. }, }); ``` +By default the sidebar shows a small coloured dot next to each story. Setting `sidebarTags` to `'single'` or `'multiple'` switches the sidebar to render the full status tag — the same colour and label as the toolbar tag, but slightly smaller. `sidebarTags: 'none'` hides the indicator entirely (and overrides `sidebarDots`). When `sidebarTags` is not set, `sidebarDots` keeps its existing behaviour. + +**Note:** Status URLs are intentionally not used by sidebar tags — clicking a story row in the sidebar should always navigate to that story. Use the toolbar tag (which still renders as a link) to follow the URL. + **IMPORTANT:** The addon was previously configured using parameters in `preview.js`. This will still work as before, however newer features such as sidebar dot customisation are not available. **NOTE:** Each key will be used as the label for the status and will convert camelCase to words. From eadd1c3521bd1366c55c81e247dd00807fca2779 Mon Sep 17 00:00:00 2001 From: Gavyn McKenzie Date: Thu, 30 Apr 2026 10:37:48 +0100 Subject: [PATCH 09/10] style: drop redundant left margin on sidebar indicators Storybook's sidebar already handles spacing between the label and right-aligned slot content, so the explicit margin-left on StatusDot and the sidebar variant of StatusTagBase was producing a double gap. Refs #22 --- src/components/StatusDot.jsx | 1 - src/components/StatusTagBase.jsx | 1 - 2 files changed, 2 deletions(-) diff --git a/src/components/StatusDot.jsx b/src/components/StatusDot.jsx index 5ec99c9..52d0a3e 100644 --- a/src/components/StatusDot.jsx +++ b/src/components/StatusDot.jsx @@ -11,7 +11,6 @@ const StatusDot = styled.span` : defaultBackground)}; border-radius: 100%; height: 6px; - margin-left: 0.5em; user-select: none; width: 6px; `; diff --git a/src/components/StatusTagBase.jsx b/src/components/StatusTagBase.jsx index d3b6470..11f4c0a 100644 --- a/src/components/StatusTagBase.jsx +++ b/src/components/StatusTagBase.jsx @@ -22,7 +22,6 @@ const toolbarStyles = css` const sidebarStyles = css` font-size: 10px; line-height: 16px; - margin-left: 0.5em; padding: 0 0.4em; `; From ac7b0ba5e1a1f3cc19c7c916c870409e0aa59d48 Mon Sep 17 00:00:00 2001 From: Gavyn McKenzie Date: Thu, 30 Apr 2026 10:38:08 +0100 Subject: [PATCH 10/10] chore: remove superpowers spec and plan from PR Local planning artefacts; not needed in the published history. Refs #22 --- .../plans/2026-04-30-sidebar-status-tags.md | 660 ------------------ .../2026-04-30-sidebar-status-tags-design.md | 162 ----- 2 files changed, 822 deletions(-) delete mode 100644 docs/superpowers/plans/2026-04-30-sidebar-status-tags.md delete mode 100644 docs/superpowers/specs/2026-04-30-sidebar-status-tags-design.md diff --git a/docs/superpowers/plans/2026-04-30-sidebar-status-tags.md b/docs/superpowers/plans/2026-04-30-sidebar-status-tags.md deleted file mode 100644 index f5b62fb..0000000 --- a/docs/superpowers/plans/2026-04-30-sidebar-status-tags.md +++ /dev/null @@ -1,660 +0,0 @@ -# Sidebar Status Tags Implementation Plan - -> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. - -**Goal:** Add a `sidebarTags` config option that renders full status tags in the sidebar (slightly smaller than the toolbar tag) instead of the existing dots, addressing [issue #22](https://github.com/etchteam/storybook-addon-status/issues/22). - -**Architecture:** Refactor `StatusTag` into a presentational `StatusTagBase` (variant-aware: `'toolbar'` | `'sidebar'`) plus thin wrappers. The toolbar continues to map current-story configs and renders `StatusTagBase` with the `'toolbar'` variant; a new `SidebarStatusTag` wrapper renders a single config with the `'sidebar'` variant (10px font, 16px line-height, 0.4em padding, span only — no link). `manager.jsx`'s `renderLabel` reads the new `sidebarTags` config and chooses tags vs. existing dots; when unset, behavior is unchanged. - -**Tech Stack:** React 18, `storybook/theming` (styled-components), `lodash/startCase`, Storybook 10 manager API. - -**Spec:** [docs/superpowers/specs/2026-04-30-sidebar-status-tags-design.md](docs/superpowers/specs/2026-04-30-sidebar-status-tags-design.md) - ---- - -## File Structure - -| File | Action | Responsibility | -|---|---|---| -| `src/components/StatusTagBase.jsx` | Create | Presentational, variant-aware single status tag (span or link). | -| `src/components/StatusTag.jsx` | Modify | Toolbar entry point. Keeps hooks; delegates rendering to `StatusTagBase`. | -| `src/components/SidebarStatusTag.jsx` | Create | Thin wrapper that renders a single config via `StatusTagBase` with `variant="sidebar"`, no link. | -| `src/manager.jsx` | Modify | Read new `sidebarTags` config; choose tag vs. dot rendering with documented precedence. | -| `.storybook/manager.js` | Modify | Demo config switched to `sidebarTags: 'multiple'` for verification. | -| `Readme.md` | Modify | Document the new `sidebarTags` option. | - -The repo has **no unit test suite** (confirmed against `package.json` — only `prebuild`, `prepare`, `storybook`, `build-storybook`, `release` scripts). Verification is via the running Storybook dev server. Each task includes a build/lint check; a single manual-verification task at the end exercises the matrix from the spec. - ---- - -## Task 1: Create `StatusTagBase` presentational component - -**Files:** -- Create: `src/components/StatusTagBase.jsx` - -- [ ] **Step 1: Create the file** - -```jsx -import startCase from 'lodash/startCase'; -import React from 'react'; -import { styled, css } from 'storybook/theming'; - -import { - defaultStatuses, - defaultBackground, - defaultColor, -} from '../defaults'; - -const baseStyles = css` - align-self: center; - border-radius: 0.25em; - font-weight: 700; - text-decoration: none; - text-transform: uppercase; - user-select: none; -`; - -const toolbarStyles = css` - font-size: 11px; - line-height: 20px; - padding: 0 0.5em; -`; - -const sidebarStyles = css` - font-size: 10px; - line-height: 16px; - margin-left: 0.5em; - padding: 0 0.4em; -`; - -const variantStyles = ({ variant }) => - variant === 'sidebar' ? sidebarStyles : toolbarStyles; - -const LinkTag = styled.a` - ${baseStyles} - ${variantStyles} -`; - -const TextTag = styled.span` - ${baseStyles} - ${variantStyles} -`; - -const StatusTagBase = ({ label, status, url, variant = 'toolbar' }) => { - const resolvedColor = - status?.color ?? - (defaultStatuses[label] ? defaultStatuses[label].color : defaultColor); - const resolvedBackground = - status?.background ?? - (defaultStatuses[label] - ? defaultStatuses[label].background - : defaultBackground); - - const style = { - color: resolvedColor, - backgroundColor: resolvedBackground, - }; - - const description = status?.description; - const displayLabel = startCase(label); - const isLink = variant === 'toolbar' && !!url; - - if (isLink) { - return ( - - {displayLabel} - - ); - } - - return ( - - {displayLabel} - - ); -}; - -export default StatusTagBase; -``` - -**Notes:** -- `label` is the raw camelCase status name (e.g. `'releaseCandidate'`); `startCase` is applied internally for display. Defaults lookup uses the raw `label` so it correctly matches built-in keys like `releaseCandidate`. This matches `StatusDot`'s lookup (which uses the raw `type` prop). -- `align-self: center` is preserved from the existing toolbar styles. `margin-left: 0.5em` on the sidebar variant mirrors the existing `StatusDot` spacing. -- `padding: 0 0.5em` was duplicated in the original `tagStyles` block. The duplicate is dropped here; only one rule is applied per variant. - -- [ ] **Step 2: Run lint to verify the file is clean** - -Run: `npx eslint src/components/StatusTagBase.jsx` -Expected: no errors. - -- [ ] **Step 3: Run the build to verify it compiles** - -Run: `npm run prepare` -Expected: `tsup` exits successfully and writes to `dist/`. - -- [ ] **Step 4: Commit** - -```bash -git add src/components/StatusTagBase.jsx -git commit -m "feat: add StatusTagBase presentational component - -Variant-aware (toolbar | sidebar) presentational status tag. Renders -either a span or an anchor depending on variant + url. Internalises -the defaults-fallback logic and label start-casing. - -Refs #22" -``` - ---- - -## Task 2: Refactor `StatusTag` (toolbar) to use `StatusTagBase` - -**Files:** -- Modify: `src/components/StatusTag.jsx` - -- [ ] **Step 1: Replace the file contents** - -Replace the entire contents of `src/components/StatusTag.jsx` with: - -```jsx -import React from 'react'; -import { useParameter, useStorybookApi, addons } from 'storybook/manager-api'; - -import { ADDON_ID } from '../constants'; -import { getStatusConfigs } from '../getStatusConfigs'; -import StatusTagBase from './StatusTagBase'; - -const StatusTag = () => { - const api = useStorybookApi(); - const tags = api.getCurrentStoryData()?.tags ?? []; - - const parameters = useParameter(ADDON_ID, null); - const customConfigs = addons.getConfig()?.[ADDON_ID]?.statuses; - - const statusConfigs = getStatusConfigs({ - tags, - parameters, - customConfigs, - }); - - if (!statusConfigs?.length) { - return null; - } - - return ( - <> - {statusConfigs.map((statusConfig) => ( - - ))} - - ); -}; - -export default StatusTag; -``` - -**Notes:** -- Removes the local `tagStyles`, `LinkTag`, `TextTag`, and inline color/background resolution — all of that now lives in `StatusTagBase`. -- Removes unused imports (`startCase`, `styled`, `css`, default colour constants). -- Public behavior is preserved: same DOM output (`` for URL statuses, `` otherwise) at the existing toolbar size. - -- [ ] **Step 2: Run lint to verify the file is clean** - -Run: `npx eslint src/components/StatusTag.jsx` -Expected: no errors. - -- [ ] **Step 3: Run the build to verify it compiles** - -Run: `npm run prepare` -Expected: `tsup` exits successfully. - -- [ ] **Step 4: Smoke-check the toolbar in Storybook** - -Run: `npm run storybook` (in a separate terminal — leave running for later tasks). -Open http://localhost:6006, pick a story with a status (e.g. an existing `'beta'` story), and confirm the toolbar shows the same status tag as before this change. Hover should still show the description. - -- [ ] **Step 5: Commit** - -```bash -git add src/components/StatusTag.jsx -git commit -m "refactor: route StatusTag through StatusTagBase - -The toolbar StatusTag now delegates rendering to the new -StatusTagBase presentational component. No public behaviour change. - -Refs #22" -``` - ---- - -## Task 3: Create `SidebarStatusTag` wrapper - -**Files:** -- Create: `src/components/SidebarStatusTag.jsx` - -- [ ] **Step 1: Create the file** - -```jsx -import React from 'react'; - -import StatusTagBase from './StatusTagBase'; - -const SidebarStatusTag = ({ statusConfig }) => ( - -); - -export default SidebarStatusTag; -``` - -**Notes:** -- Deliberately does not pass `url`. Sidebar tags are always rendered as a `` per the spec (URL is intentionally ignored to avoid nested clickable elements inside Storybook's sidebar row). -- Takes a single `statusConfig` (not an array) so the caller controls iteration and `key`. - -- [ ] **Step 2: Run lint to verify the file is clean** - -Run: `npx eslint src/components/SidebarStatusTag.jsx` -Expected: no errors. - -- [ ] **Step 3: Run the build to verify it compiles** - -Run: `npm run prepare` -Expected: `tsup` exits successfully. - -- [ ] **Step 4: Commit** - -```bash -git add src/components/SidebarStatusTag.jsx -git commit -m "feat: add SidebarStatusTag wrapper - -Thin wrapper around StatusTagBase fixing variant=\"sidebar\" and -omitting url so sidebar tags are always rendered as spans. - -Refs #22" -``` - ---- - -## Task 4: Wire `sidebarTags` into `manager.jsx` `renderLabel` - -**Files:** -- Modify: `src/manager.jsx` - -- [ ] **Step 1: Replace the file contents** - -Replace the entire contents of `src/manager.jsx` with: - -```jsx -import startCase from 'lodash/startCase'; -import React from 'react'; -import { addons, types } from 'storybook/manager-api'; - -import SidebarStatusTag from './components/SidebarStatusTag'; -import StatusDot from './components/StatusDot'; -import StatusTag from './components/StatusTag'; -import { ADDON_ID } from './constants'; -import { getStatusConfigs } from './getStatusConfigs'; - -addons.register(ADDON_ID, (api) => { - const addonsConfig = addons.getConfig(); - const existingSidebarConfig = addonsConfig?.sidebar ?? {}; - - addons.add(ADDON_ID, { - title: 'Status', - type: types.TOOL, - render: () => , - }); - - const statusAddonConfig = addonsConfig?.[ADDON_ID] ?? {}; - - addons.setConfig({ - sidebar: { - ...existingSidebarConfig, - renderLabel: (item) => { - const { name, tags } = item; - const isLeaf = ['root', 'group', 'story'].includes(item.type); - - try { - const fallbackLabel = existingSidebarConfig?.renderLabel - ? existingSidebarConfig.renderLabel(item) - : name; - - const sidebarTagsConfig = statusAddonConfig?.sidebarTags; - const sidebarDotsConfig = statusAddonConfig?.sidebarDots; - - // sidebarTags, when set, fully overrides sidebarDots. - const isTagMode = - sidebarTagsConfig === 'single' || sidebarTagsConfig === 'multiple'; - - if (sidebarTagsConfig === 'none') { - return fallbackLabel; - } - - if (sidebarTagsConfig === undefined && sidebarDotsConfig === 'none') { - return fallbackLabel; - } - - const parameters = api.getParameters(item.id, ADDON_ID); - - // item can be a Root | Group | Story - if (!isLeaf || (tags.length === 0 && !parameters?.type)) { - return fallbackLabel; - } - - // Get custom status configurations from the current story's parameters. - // This will include any custom statuses defined in manager.js, preview.js or story parameters. - // However custom statuses from story parameters will only be available in the sidebar - // when viewing that story. This is a storybook limitation: - // https://github.com/storybookjs/storybook/discussions/24022 - const customConfigs = - statusAddonConfig?.statuses || - api.getCurrentStoryData().parameters?.status?.statuses; - - let statusConfigs = getStatusConfigs({ - tags, - parameters, - customConfigs, - }); - - if (statusConfigs.length === 0) { - return fallbackLabel; - } - - const showMultiple = isTagMode - ? sidebarTagsConfig === 'multiple' - : sidebarDotsConfig === 'multiple'; - - if (!showMultiple) { - statusConfigs = [statusConfigs[0]]; - } - - return ( - <> - {fallbackLabel} - {statusConfigs.map((statusConfig) => { - if (isTagMode) { - return ( - - ); - } - - const { - label: statusName, - status: { background, description }, - } = statusConfig; - - return ( - - ); - })} - - ); - } catch (error) { - return name; - } - }, - }, - }); -}); -``` - -**Notes on the precedence logic** (mirrors the spec's "Precedence rules" section): -- `sidebarTags === 'none'` → no indicator, regardless of `sidebarDots`. -- `sidebarTags === 'single' | 'multiple'` → tag mode; `sidebarDots` is ignored. -- `sidebarTags === undefined` AND `sidebarDots === 'none'` → no indicator (existing behavior). -- `sidebarTags === undefined` otherwise → existing dot behavior, where `sidebarDots === 'multiple'` shows all and any other value (or `undefined`) shows only the first. - -- [ ] **Step 2: Run lint** - -Run: `npx eslint src/manager.jsx` -Expected: no errors. - -- [ ] **Step 3: Run the build** - -Run: `npm run prepare` -Expected: `tsup` exits successfully. - -- [ ] **Step 4: Commit** - -```bash -git add src/manager.jsx -git commit -m "feat: support sidebarTags option in renderLabel - -Adds a new \`status.sidebarTags\` config ('single' | 'multiple' | -'none') that, when set, renders SidebarStatusTag components in the -sidebar instead of dots. When unset, the existing sidebarDots -behaviour is preserved exactly. - -Closes #22" -``` - ---- - -## Task 5: Update demo `.storybook/manager.js` for verification - -**Files:** -- Modify: `.storybook/manager.js` - -- [ ] **Step 1: Replace the file contents** - -Replace the entire contents of `.storybook/manager.js` with: - -```js -import { addons } from "storybook/manager-api"; - -addons.setConfig({ - status: { - statuses: { - customStatus: { - background: '#0000ff', - color: '#ffffff', - description: 'This component is stable and released', - }, - }, - // 'single' | 'multiple' | 'none'. When set, takes precedence over sidebarDots. - sidebarTags: 'multiple', - sidebarDots: 'multiple', // 'single' | 'multiple' | 'none'. 'single' is the default - }, -}); -``` - -**Note:** This is dev-only config used to exercise the new mode in `npm run storybook`. We keep `sidebarDots: 'multiple'` in place so it's easy to flip `sidebarTags` between values (or comment it out) and confirm the precedence rules behave as documented. - -- [ ] **Step 2: Verify Storybook reloads cleanly** - -If the Storybook dev server from Task 2 is still running, it should hot-reload. Otherwise: `npm run storybook`. -Open http://localhost:6006 and confirm the addon still loads (no console errors). - -- [ ] **Step 3: Commit** - -```bash -git add .storybook/manager.js -git commit -m "chore(demo): enable sidebarTags multiple in dev storybook - -Refs #22" -``` - ---- - -## Task 6: Manual verification matrix - -**Files:** none. - -This task exercises every row of the verification matrix in the spec. The Storybook dev server (`npm run storybook`) must be running. After each scenario, edit `.storybook/manager.js` and let Storybook hot-reload. Check each box only after observing the expected behavior. - -- [ ] **Default (no `sidebarTags`, no `sidebarDots`):** comment out both lines. - Expected: one **dot** per story (single, default). - -- [ ] **Existing dots opt-in (`sidebarDots: 'multiple'`):** comment out `sidebarTags`, keep `sidebarDots: 'multiple'`. - Expected: multiple **dots** per story for stories with multiple statuses. - -- [ ] **New tag mode — single (`sidebarTags: 'single'`):** - Expected: one **tag** per story (the first matching status), no dots. Tag is at the smaller sidebar size. - -- [ ] **New tag mode — multiple (`sidebarTags: 'multiple'`):** - Expected: a sidebar tag for **every** matching status, no dots. - -- [ ] **New tag mode — none (`sidebarTags: 'none'`, `sidebarDots: 'multiple'`):** - Expected: **no** indicator at all (sidebarTags overrides sidebarDots). Toolbar tag still shows. - -- [ ] **Long status names:** with `sidebarTags: 'multiple'`, view a story whose statuses include `customStatus` (long-ish label after `startCase` → "Custom Status"). - Expected: row layout doesn't break; long names either fit or wrap acceptably. - -- [ ] **Hover title:** with any tag mode, hover a sidebar tag. - Expected: native browser tooltip shows the status `description`. - -- [ ] **URL ignored in sidebar:** add `parameters.status.url` to a story (or pick one that already has it from `stories/`). With `sidebarTags: 'single'`: - Expected: sidebar tag is a `` (not an ``); toolbar tag is still an ``. Inspect the DOM to confirm. - -- [ ] **Custom statuses:** with `sidebarTags: 'multiple'`, navigate to a story that uses `customStatus` (defined in `.storybook/manager.js`). - Expected: sidebar tag renders with the configured `#0000ff` background. - -- [ ] **Restore demo config to a sensible default before commit:** - -After verification, set `.storybook/manager.js` back to: - -```js -import { addons } from "storybook/manager-api"; - -addons.setConfig({ - status: { - statuses: { - customStatus: { - background: '#0000ff', - color: '#ffffff', - description: 'This component is stable and released', - }, - }, - sidebarTags: 'multiple', - sidebarDots: 'multiple', - }, -}); -``` - -(This is the same as the end of Task 5 — verification-only edits should not have been committed.) - -- [ ] **Commit only if a fix was needed.** If verification surfaced no bugs, no commit is created in this task. If a bug was found and fixed, commit the fix with a descriptive message referencing the failing scenario. - ---- - -## Task 7: Update the Readme - -**Files:** -- Modify: `Readme.md` - -- [ ] **Step 1: Update the Configuration section** - -Replace the existing Configuration code example block in `Readme.md` (currently the block starting `import { addons } from "storybook/manager-api";` and ending with `});` directly under the **Configuration** heading) with: - -````markdown -```js -import { addons } from "storybook/manager-api"; - -addons.setConfig({ - status: { - statuses: { - released: { - background: '#0000ff', - color: '#ffffff', - description: 'This component is stable and released', - }, - }, - sidebarDots: 'single', // 'single' | 'multiple' | 'none'. 'single' is the default - sidebarTags: 'single', // 'single' | 'multiple' | 'none'. When set, overrides sidebarDots and renders full status tags in the sidebar instead of dots. - }, -}); -``` - -By default the sidebar shows a small coloured dot next to each story. Setting `sidebarTags` to `'single'` or `'multiple'` switches the sidebar to render the full status tag — the same colour and label as the toolbar tag, but slightly smaller. `sidebarTags: 'none'` hides the indicator entirely (and overrides `sidebarDots`). When `sidebarTags` is not set, `sidebarDots` keeps its existing behaviour. - -**Note:** Status URLs are intentionally not used by sidebar tags — clicking a story row in the sidebar should always navigate to that story. Use the toolbar tag (which still renders as a link) to follow the URL. -```` - -**Note:** The replacement preserves the existing `sidebarDots` line and adds `sidebarTags` immediately below it, then adds a short prose paragraph explaining the new option and the URL-handling caveat. - -- [ ] **Step 2: Eyeball the Readme** - -Open `Readme.md` and confirm the new section reads cleanly and matches the rest of the document's tone. - -- [ ] **Step 3: Commit** - -```bash -git add Readme.md -git commit -m "docs: document sidebarTags configuration option - -Refs #22" -``` - ---- - -## Task 8: Open the pull request - -**Files:** none. - -- [ ] **Step 1: Push the branch** - -```bash -git push -u origin feature/sidebar-status-tags -``` - -- [ ] **Step 2: Open a PR linked to issue #22** - -```bash -gh pr create --title "feat: optional status tags in sidebar (closes #22)" --body "$(cat <<'EOF' -## Summary -- Adds a new `status.sidebarTags` config (`'single' | 'multiple' | 'none'`) that renders full status tags in the sidebar instead of dots. -- Refactors `StatusTag` into a presentational `StatusTagBase` so the toolbar and sidebar share rendering. The sidebar variant is slightly smaller (10px / 16px line-height / 0.4em padding). -- When `sidebarTags` is unset, the existing `sidebarDots` behaviour is preserved exactly. - -Closes #22. - -## Test plan -- [ ] Default (no config): single dot per story. -- [ ] `sidebarDots: 'multiple'`: multiple dots per story. -- [ ] `sidebarTags: 'single'`: one sidebar tag per story. -- [ ] `sidebarTags: 'multiple'`: all sidebar tags per story. -- [ ] `sidebarTags: 'none'`: no indicator (overrides `sidebarDots`). -- [ ] Sidebar tag is rendered as `` even when the status has a `url`. -- [ ] Toolbar tag is unchanged (still renders as `` for statuses with a `url`). -- [ ] Hovering a sidebar tag shows the status description. -- [ ] Custom statuses defined in `manager.js` render with the configured colours. -EOF -)" -``` - -- [ ] **Step 3: Capture the PR URL** and report it back. - ---- - -## Self-review - -Run through the spec section-by-section and confirm coverage. (Done while writing this plan; no gaps identified.) - -- **Problem / Goals / Non-goals:** Covered by Tasks 1–4 (feature) + Task 7 (docs). -- **Configuration API + precedence rules:** Implemented in Task 4. The `if (sidebarTagsConfig === 'none')` short-circuit, the `isTagMode` branch, and the `showMultiple` choice each map to a numbered rule in the spec. -- **`StatusTagBase`:** Task 1 (matches the props table in the spec exactly: `label`, `status`, `url`, `variant`). -- **`StatusTag` (toolbar refactor):** Task 2. -- **`SidebarStatusTag`:** Task 3 (passes no `url`, fixed variant, takes a single config). -- **`manager.jsx` flow change:** Task 4 (matches the spec's pseudocode). -- **Verification matrix:** Task 6 — every row in the spec's table is a checkbox. -- **Documentation:** Task 7 — covers `sidebarTags` next to `sidebarDots`, precedence note, URL caveat. -- **Open decisions resolved (Q1 API shape, Q2 link behavior, Q3 sizing):** Q1 → Task 4 (parallel `sidebarTags` setting); Q2 → Task 3 (`SidebarStatusTag` omits `url`) + Task 1 (`isLink` only when `variant === 'toolbar'`); Q3 → Task 1 (`sidebarStyles` block sets 10px / 16px / 0.4em). - -No placeholders. Type/prop names consistent across tasks (`statusConfig`, `label`, `status`, `url`, `variant`, `sidebarTags`, `isTagMode`, `showMultiple`). diff --git a/docs/superpowers/specs/2026-04-30-sidebar-status-tags-design.md b/docs/superpowers/specs/2026-04-30-sidebar-status-tags-design.md deleted file mode 100644 index 608e8f4..0000000 --- a/docs/superpowers/specs/2026-04-30-sidebar-status-tags-design.md +++ /dev/null @@ -1,162 +0,0 @@ -# Sidebar status tags option - -**Issue:** [#22 — Customize the bullet located on the left menu](https://github.com/etchteam/storybook-addon-status/issues/22) -**Date:** 2026-04-30 -**Status:** Approved - -## Problem - -The sidebar currently shows a small 6px coloured `StatusDot` next to each story, with a `title` attribute on hover. Multiple users (most recently in [#22](https://github.com/etchteam/storybook-addon-status/issues/22)) have asked for the option to display the full status as a coloured tag — like the `StatusTag` shown in the toolbar — so the status is visible at a glance without hovering. - -## Goals - -- Provide a config option to render full status tags in the sidebar instead of dots. -- Tags in the sidebar should be slightly smaller than the toolbar tag so they fit cleanly next to story names. -- Keep the existing `sidebarDots` API working unchanged for users who don't opt in. - -## Non-goals - -- Changing the default sidebar appearance. Existing users still see dots unless they opt in. -- Custom icons or alternate sidebar indicators (out of scope; raised separately in the issue). -- Configurable indicator location. -- Making the sidebar tag a link. (See [Open decisions](#open-decisions-resolved).) - -## Configuration API - -A new optional config sits alongside the existing `sidebarDots`: - -```js -import { addons } from 'storybook/manager-api'; - -addons.setConfig({ - status: { - sidebarDots: 'single', // existing, unchanged - sidebarTags: 'single' | 'multiple' | 'none', // new - }, -}); -``` - -### Precedence rules - -When `renderLabel` runs: - -1. If `sidebarTags === 'none'` → render the fallback label only (no indicator). -2. If `sidebarTags === 'single'` → render a single sidebar tag (the first matching status). -3. If `sidebarTags === 'multiple'` → render a sidebar tag for every matching status. -4. If `sidebarTags` is not set (`undefined`) → fall back to the existing `sidebarDots` behavior, unchanged. - -`sidebarTags`, when set to a non-`undefined` value, fully overrides `sidebarDots`. The two settings are not combined. - -### Why a separate setting (option A from brainstorm) - -Considered alternatives: -- **B. Extend `sidebarDots` values** (e.g. add `'tag'`) — rejected: a setting named `sidebarDots` returning `'tag'` is confusing. -- **C. New structured `sidebar` config object** — rejected for now: bigger change, requires deprecating `sidebarDots`. Can evolve to this later if more sidebar options accumulate. -- **A. Parallel `sidebarTags` config** — chosen: smallest change, fully backward-compatible, mirrors existing naming. - -## Components - -The current `StatusTag.jsx` couples the toolbar's data fetching (via `useStorybookApi` / `useParameter` hooks) with the visual rendering of a tag. To share rendering between the toolbar and sidebar, we split it. - -### `StatusTagBase` (new — presentational) - -Single-status presentational component. Stateless, takes everything it needs as props. - -**Props:** -- `label: string` — raw status label (e.g. `'releaseCandidate'`). `StatusTagBase` applies `startCase` internally so callers don't need to know about display formatting. -- `status: { background?: string, color?: string, description?: string }` — the resolved status config. -- `url?: string` — when present and `variant === 'toolbar'`, renders an anchor. -- `variant: 'toolbar' | 'sidebar'` — controls size and link rendering. - -**Behavior:** -- Renders `` only when `variant === 'toolbar'` AND `url` is set. Otherwise renders ``. -- Applies the appropriate size styles for the variant. -- Sets `title={description}` for hover tooltip. - -**Sizes:** - -| | Font size | Line height | Padding (horizontal) | Border radius | -|---|---|---|---|---| -| `toolbar` (existing) | 11px | 20px | 0.5em | 0.25em | -| `sidebar` (new) | 10px | 16px | 0.4em | 0.25em | - -The base styled-component composes shared rules; variant-specific rules are layered on top via a `variant` prop or two extending styled-components. - -### `StatusTag` (toolbar — refactored) - -Keeps current responsibilities: read current story via hooks, resolve configs via `getStatusConfigs`, render a list. Internally maps each config to ``. Public behavior unchanged. - -### `SidebarStatusTag` (new — thin wrapper) - -A minimal component used from `manager.jsx`'s `renderLabel`. Takes a single resolved status config (label + status + description) and renders `` (no `url` passed — sidebar tags are never links per [Open decisions](#open-decisions-resolved)). - -This wrapper exists so `manager.jsx` can stay clean and the sidebar component is easy to reason about. - -## `manager.jsx` flow change - -The current logic in [src/manager.jsx](src/manager.jsx) reads `sidebarDotsConfig` and short-circuits on `'none'`. The new logic, in pseudocode: - -``` -const sidebarTagsConfig = statusAddonConfig?.sidebarTags; -const sidebarDotsConfig = statusAddonConfig?.sidebarDots; - -if (sidebarTagsConfig === 'none') return fallbackLabel; -if (sidebarTagsConfig === undefined && sidebarDotsConfig === 'none') return fallbackLabel; - -// ... unchanged: resolve statusConfigs from tags + parameters ... - -if (statusConfigs.length === 0) return fallbackLabel; - -const isTagMode = sidebarTagsConfig === 'single' || sidebarTagsConfig === 'multiple'; -const showMultiple = isTagMode - ? sidebarTagsConfig === 'multiple' - : sidebarDotsConfig === 'multiple'; - -const visibleConfigs = showMultiple ? statusConfigs : [statusConfigs[0]]; - -return ( - <> - {fallbackLabel} - {visibleConfigs.map(config => - isTagMode - ? - : - )} - -); -``` - -The default-status logic that resolves `background`/`color`/`description` already lives in the dot rendering and inside `StatusTag`'s map; the sidebar tag goes through `StatusTagBase`, which uses the same defaults logic shared with the toolbar. - -## Verification - -The repo has no unit test suite. Verification will be done in the running Storybook (`npm run storybook`). - -**Test matrix:** - -| Scenario | Config | Expected | -|---|---|---| -| Default | (no `sidebarTags`, no `sidebarDots`) | Single dot per story (existing behavior). | -| Existing dots opt-in | `sidebarDots: 'multiple'` | Multiple dots per story (existing behavior). | -| New tag mode — single | `sidebarTags: 'single'` | One sidebar tag per story (first status), no dots. | -| New tag mode — multiple | `sidebarTags: 'multiple'` | All sidebar tags per story, no dots. | -| New tag mode — none | `sidebarTags: 'none'` | No indicator, regardless of `sidebarDots`. | -| Long status names | Tag mode, custom status with long name | Tag does not break the row layout; truncation or wrap looks acceptable. | -| Hover title | Any tag mode | Hovering the sidebar tag shows the description. | -| URL is ignored in sidebar | Story with `parameters.status.url` set, tag mode | Sidebar tag renders as `` not ``; toolbar tag still renders as ``. | -| Custom statuses | Tag mode + custom status from `manager.js` | Renders with custom colours. | - -The demo `.storybook/manager.js` will be updated during implementation to make the new mode easy to verify. - -## Documentation - -Update `Readme.md` configuration section to: -- Document `sidebarTags` next to `sidebarDots`. -- Note that `sidebarTags` (when set) overrides `sidebarDots`. -- Note that URLs on statuses are not used in sidebar tags (the toolbar tag is the linked one). - -## Open decisions (resolved) - -1. **Configuration API shape** — chose option A (parallel `sidebarTags` setting). [Q1] -2. **Link behavior in sidebar** — sidebar tags are always rendered as ``, never ``, even when the status has a URL. The toolbar tag remains the linked surface. Reason: a link inside Storybook's already-clickable sidebar row creates conflicting interactions, and adding `stopPropagation` was deemed unnecessary complexity. [Q2] -3. **Sidebar tag size** — slightly smaller than toolbar: 10px font, 16px line-height, 0.4em horizontal padding. [Q3]