Files
classroomio/packages/storybook/HOW_TO_CREATE_STORIES.md
T
Rotimi Best c7854efae3 feat: redo settings and all org pages. (#601)
* feat: new components, deploy storybook

* refactor: simplify component field definitions and enhance button loading state

- Removed unnecessary type imports and simplified the FIELDS array definitions across multiple components in Storybook.
- Updated button component to use a loading state instead of isLoading, improving clarity and consistency in props.
- Adjusted button stories to reflect the new loading prop and enhanced the button's loading visual feedback.
- Minor CSS adjustments for better layout and styling consistency.

* chore: add field, card, password components

* feat: improve page component & update AuthUI path

* chore: update bits-ui to version 2.14.4 and all org related UI's

- Updated bits-ui dependency in pnpm-lock.yaml and package.json to version 2.14.4.
- Refactored imports for RoleBasedSecurity and UpgradeBanner components to use the new ui feature directory.
- Removed unused Course components and related files to clean up the codebase.
- Enhanced various components to improve consistency and maintainability.

* chore: move all setting buttons to the top and update auth ui styles

* chore: remove svelte-loading-spinners and replace with @cio/ui/base/spinner

- Removed all instances of svelte-loading-spinners from the project.
- Updated components to use the new Spinner from @cio/ui/base/spinner.
- Cleaned up pnpm-lock.yaml and package.json to reflect the removal of svelte-loading-spinners.
- Added new CSS animations for rocket launch effects in app.css.
- Enhanced email change flow with updated verification messages in translations.

* chore: remove supabase file upload from dashboard and move to r2

* chore: update organization API and UI components

- Added new API methods for organization plan operations and team management.
- Introduced middleware for API key authentication and organization admin checks.
- Refactored organization-related components to utilize the new API structure.
- Updated various UI components to improve consistency and maintainability.
- Enhanced error handling and validation for organization-related actions.
- Removed deprecated services and streamlined the codebase.
2025-12-02 09:54:38 +01:00

459 lines
12 KiB
Markdown

# How to Create Storybook Stories
This guide documents the patterns and conventions for creating Storybook stories in the ClassroomIO UI component library.
## Table of Contents
- [File Structure](#file-structure)
- [Basic Story Structure](#basic-story-structure)
- [Import Patterns](#import-patterns)
- [Story Definition](#story-definition)
- [Template Snippets](#template-snippets)
- [Fields File](#fields-file)
- [Component Categorization](#component-categorization)
- [CSS Class Conventions](#css-class-conventions)
- [LMS Content Guidelines](#lms-content-guidelines)
- [Examples](#examples)
## File Structure
### Directory Organization
Stories are organized into two main categories:
- **Atoms** (`src/atoms/`): Basic, indivisible UI components (Button, Input, Badge, etc.)
- **Molecules** (`src/molecules/`): Composite components made of multiple atoms (Card, Field, ButtonGroup, etc.)
### File Naming
1. Create a directory matching the component name (kebab-case)
2. Create a story file: `{component-name}.stories.svelte`
3. Optionally create a fields file: `fields.ts` (for controls configuration)
**Example:**
```
src/molecules/hover-card/
├── hover-card.stories.svelte
└── fields.ts
```
## Basic Story Structure
Every story file follows this structure:
```svelte
<script module>
import { defineMeta } from '@storybook/addon-svelte-csf';
// Component imports
import * as ComponentName from '@cio/ui/base/component-name';
// Other imports (icons, utilities, etc.)
import IconName from '@lucide/svelte/icons/icon-name';
// Fields import (if using controls)
import { FIELDS } from './fields';
// Story definition
const { Story } = defineMeta({
title: 'Category/ComponentName',
component: ComponentName.Root, // or ComponentName for single components
parameters: {
layout: 'centered',
controls: {
include: FIELDS
}
},
tags: ['autodocs']
});
// State variables (if needed)
let someState = $state('');
</script>
<Story name="StoryName">
{#snippet template()}
<!-- Component usage -->
{/snippet}
</Story>
```
## Import Patterns
### Component Imports
- **Namespace imports** for multi-part components:
```svelte
import * as HoverCard from '@cio/ui/base/hover-card';
import * as Card from '@cio/ui/base/card';
import * as Field from '@cio/ui/base/field';
```
- **Named imports** for single components:
```svelte
import { Button } from '@cio/ui/base/button';
import { Input } from '@cio/ui/base/input';
import { Avatar } from '@cio/ui/base/avatar';
```
### Icon Imports
Use Lucide Svelte icons:
```svelte
import CalendarDaysIcon from '@lucide/svelte/icons/calendar-days';
import UsersIcon from '@lucide/svelte/icons/users';
import BookOpenIcon from '@lucide/svelte/icons/book-open';
```
## Story Definition
### Title Format
- **Atoms**: `'Atom/ComponentName'` (e.g., `'Atom/Button'`)
- **Molecules**: `'Molecules/ComponentName'` (e.g., `'Molecules/HoverCard'`)
### Component Reference
- For multi-part components, reference the `Root` component:
```svelte
component: HoverCard.Root
component: Card.Root
```
- For single components, reference directly:
```svelte
component: Button
component: Input
```
### Parameters
Standard parameters configuration:
```svelte
parameters: {
layout: 'centered',
controls: {
include: FIELDS // Only if using fields.ts
}
}
```
### Tags
Always include `tags: ['autodocs']` for automatic documentation generation.
## Template Snippets
### Basic Template
```svelte
<Story name="Default">
{#snippet template()}
<ComponentName.Root>
<!-- Component content -->
</ComponentName.Root>
{/snippet}
</Story>
```
### Template with Args (for controls)
For atoms that need to pass args to controls:
```svelte
<Story name="Default">
{#snippet template(args)}
<Button {...args}>Button Text</Button>
{/snippet}
</Story>
```
### Multiple Stories
Create multiple stories to showcase different use cases:
```svelte
<Story name="Default">
{#snippet template()}
<!-- Basic usage -->
{/snippet}
</Story>
<Story name="With Avatar">
{#snippet template()}
<!-- Avatar variant -->
{/snippet}
</Story>
<Story name="Custom Content">
{#snippet template()}
<!-- Custom content variant -->
{/snippet}
</Story>
```
## Fields File
Create a `fields.ts` file to control which props appear in Storybook controls:
```typescript
export const FIELDS = ['class'] as string[];
```
Common fields:
- `'class'` - CSS classes
- `'variant'` - Component variants
- `'size'` - Component sizes
- `'disabled'` - Disabled state
- Add more as needed based on component props
## Component Categorization
### Atoms
Basic, indivisible components:
- Button, Input, Badge, Avatar, Label, Checkbox, Switch, etc.
- Single-purpose components
- Usually don't compose other components
### Molecules
Composite components:
- Card, Field, ButtonGroup, HoverCard, Empty, etc.
- Made of multiple atoms or other components
- More complex functionality
**Guideline**: If a component uses multiple other components together, it's likely a molecule.
## CSS Class Conventions
### UI Prefix
Use the `ui:` prefix for utility classes to scope them properly:
```svelte
<!-- ✅ Correct -->
<div class="ui:flex ui:flex-col ui:gap-6">
<Button class="ui:w-full">
<!-- ❌ Incorrect -->
<div class="flex flex-col gap-6">
<Button class="w-full">
```
### When to Use UI Prefix
- **Always use** for utility classes (flex, grid, gap, padding, margin, etc.)
- **Don't use** for component-specific classes or data attributes
- **Don't use** for Tailwind classes that are part of component styling
### Examples
```svelte
<!-- Utility classes - use ui: prefix -->
<div class="ui:flex ui:items-center ui:gap-2">
<div class="ui:space-y-2">
<div class="ui:text-muted-foreground ui:text-sm">
<!-- Component classes - no prefix needed -->
<Card.Root class="w-full max-w-sm">
<HoverCard.Content class="w-80">
```
## LMS Content Guidelines
Since ClassroomIO is an LMS platform, all story content should be **LMS-related**:
### ✅ Good Examples
- **Courses**: "Introduction to Web Development", "Advanced React Patterns"
- **Users**: "Sarah Johnson (Instructor)", "Alex Martinez (Student)"
- **Content**: Course descriptions, assignment types, enrollment information
- **Actions**: "Create Course", "Enroll Student", "Submit Assignment"
- **States**: "No courses yet", "Student enrolled", "Assignment submitted"
### ❌ Avoid
- Generic examples (Svelte, GitHub profiles, etc.)
- Non-educational content
- Examples that don't relate to learning/teaching
### Content Patterns
**Instructor Profiles:**
```svelte
<h4 class="text-sm font-semibold">Sarah Johnson</h4>
<p class="text-sm">Senior Full-Stack Developer & Instructor</p>
<span class="text-muted-foreground text-xs">Teaching since January 2020</span>
```
**Course Information:**
```svelte
<h4 class="text-sm font-semibold">Advanced React Patterns</h4>
<p class="text-muted-foreground text-sm">
Master advanced React concepts including hooks, context, performance optimization...
</p>
```
**Student Information:**
```svelte
<h4 class="text-sm font-semibold">Alex Martinez</h4>
<p class="text-muted-foreground text-xs">5 courses enrolled</p>
<p class="text-muted-foreground text-xs">Active since March 2024</p>
```
## Examples
### Example 1: Simple Atom (Button)
```svelte
<script module>
import { defineMeta } from '@storybook/addon-svelte-csf';
import { Button } from '@cio/ui/base/button';
import { FIELDS } from './fields';
const { Story } = defineMeta({
title: 'Atom/Button',
component: Button,
parameters: {
layout: 'centered',
controls: {
include: FIELDS
}
},
tags: ['autodocs']
});
</script>
<Story name="Default">
{#snippet template(args)}
<Button {...args}>Create Course</Button>
{/snippet}
</Story>
```
### Example 2: Multi-Part Molecule (HoverCard)
```svelte
<script module>
import { defineMeta } from '@storybook/addon-svelte-csf';
import CalendarDaysIcon from '@lucide/svelte/icons/calendar-days';
import * as HoverCard from '@cio/ui/base/hover-card';
import * as Avatar from '@cio/ui/base/avatar';
import { FIELDS } from './fields';
const { Story } = defineMeta({
title: 'Molecules/HoverCard',
component: HoverCard.Root,
parameters: {
layout: 'centered',
controls: {
include: FIELDS
}
},
tags: ['autodocs']
});
</script>
<Story name="Instructor Profile">
{#snippet template()}
<HoverCard.Root>
<HoverCard.Trigger
href="/instructors/sarah-johnson"
class="ui:rounded-sm ui:underline-offset-4 ui:hover:underline"
>
Sarah Johnson
</HoverCard.Trigger>
<HoverCard.Content class="w-80">
<div class="flex justify-between space-x-4">
<Avatar.Root>
<Avatar.Image src="https://github.com/shadcn.png" />
<Avatar.Fallback>SJ</Avatar.Fallback>
</Avatar.Root>
<div class="space-y-1">
<h4 class="text-sm font-semibold">Sarah Johnson</h4>
<p class="text-sm">Senior Full-Stack Developer & Instructor</p>
<div class="flex items-center pt-2">
<CalendarDaysIcon class="me-2 size-4 opacity-70" />
<span class="text-muted-foreground text-xs">Teaching since January 2020</span>
</div>
</div>
</div>
</HoverCard.Content>
</HoverCard.Root>
{/snippet}
</Story>
```
### Example 3: Complex Molecule with State (Field)
```svelte
<script module>
import { defineMeta } from '@storybook/addon-svelte-csf';
import * as Field from '@cio/ui/base/field';
import { Input } from '@cio/ui/base/input';
import { FIELDS } from './fields';
const { Story } = defineMeta({
title: 'Molecules/Field',
component: Field.Field,
parameters: {
layout: 'centered',
controls: {
include: FIELDS
}
},
tags: ['autodocs']
});
let email = $state('');
</script>
<Story name="Course Enrollment">
{#snippet template()}
<div class="w-lg">
<Field.Set>
<Field.Legend>Enroll Student</Field.Legend>
<Field.Description>Add a new student to your course</Field.Description>
<Field.Group>
<Field.Field>
<Field.Label for="student-email">Student Email</Field.Label>
<Input id="student-email" type="email" bind:value={email} placeholder="[email protected]" />
</Field.Field>
</Field.Group>
</Field.Set>
</div>
{/snippet}
</Story>
```
## Checklist
When creating a new story, ensure:
- [ ] File is in the correct directory (`atoms/` or `molecules/`)
- [ ] File is named `{component-name}.stories.svelte`
- [ ] Imports use `@cio/ui/base/{component-name}` pattern
- [ ] Title follows `'Category/ComponentName'` format
- [ ] Component reference is correct (Root for multi-part, direct for single)
- [ ] `tags: ['autodocs']` is included
- [ ] All utility classes use `ui:` prefix
- [ ] Content is LMS-related (courses, students, instructors, etc.)
- [ ] Multiple stories showcase different use cases
- [ ] `fields.ts` is created if controls are needed
- [ ] State variables use `$state()` for reactivity
- [ ] Icons are imported from `@lucide/svelte/icons/`
## Additional Notes
- **State Management**: Use Svelte 5 runes (`$state()`, `$derived()`) for reactive state
- **Layout**: Most stories use `layout: 'centered'` in parameters
- **Styling**: Prefer Tailwind utility classes with `ui:` prefix
- **Accessibility**: Include proper ARIA labels and semantic HTML
- **Documentation**: Story names should be descriptive and reflect the use case
## Resources
- [Storybook Svelte CSF Addon](https://storybook.js.org/addons/@storybook/addon-svelte-csf)
- [Bits UI Documentation](https://bits-ui.com/docs/components)
- [Lucide Icons](https://lucide.dev/icons/)