feat: lint codebase and run precommit hook
This commit is contained in:
@@ -11,13 +11,13 @@ The navigation config types now support additional properties:
|
||||
```typescript
|
||||
export interface NavItemConfig {
|
||||
// ... existing properties ...
|
||||
|
||||
|
||||
// NEW: Route-specific breadcrumb label override
|
||||
breadcrumbLabel?: string;
|
||||
|
||||
|
||||
// NEW: Custom resolver for dynamic breadcrumb titles
|
||||
breadcrumbResolver?: (params: Record<string, string>) => string | Promise<string>;
|
||||
|
||||
|
||||
// ENHANCED: Nested routes can now have nested routes (multi-level)
|
||||
nestedRoutes?: NestedRouteConfig[];
|
||||
}
|
||||
@@ -25,13 +25,13 @@ export interface NavItemConfig {
|
||||
export interface NestedRouteConfig {
|
||||
path: string;
|
||||
titleKey: string;
|
||||
|
||||
|
||||
// NEW: Custom resolver for this nested route
|
||||
titleResolver?: (params: Record<string, string>) => string | Promise<string>;
|
||||
|
||||
|
||||
// NEW: Nested routes can have nested routes
|
||||
nestedRoutes?: NestedRouteConfig[];
|
||||
|
||||
|
||||
// NEW: Support dynamic segments within nested routes
|
||||
supportsDynamicSegment?: boolean;
|
||||
}
|
||||
@@ -40,6 +40,7 @@ export interface NestedRouteConfig {
|
||||
### Step 2: Update Breadcrumb Generation Calls
|
||||
|
||||
#### Before (Org):
|
||||
|
||||
```typescript
|
||||
import { generateBreadcrumbs } from './breadcrumb';
|
||||
|
||||
@@ -53,6 +54,7 @@ const breadcrumbs = generateBreadcrumbs(
|
||||
```
|
||||
|
||||
#### After (Org - Store-Based Reactive):
|
||||
|
||||
```svelte
|
||||
<script lang="ts">
|
||||
import { page } from '$app/state';
|
||||
@@ -61,24 +63,20 @@ const breadcrumbs = generateBreadcrumbs(
|
||||
import { courseStore } from '$features/course/utils/store';
|
||||
|
||||
// Reactive config that reads from stores
|
||||
const config = $derived(createOrgBreadcrumbConfigWithStore(
|
||||
$currentOrgPath,
|
||||
() => $courseStore.title // Reads reactively - updates automatically!
|
||||
));
|
||||
|
||||
// Breadcrumbs update automatically when store changes!
|
||||
const breadcrumbs = $derived(
|
||||
generateBreadcrumbsSync(
|
||||
page.url.pathname,
|
||||
page.url.search,
|
||||
navItems,
|
||||
config
|
||||
const config = $derived(
|
||||
createOrgBreadcrumbConfigWithStore(
|
||||
$currentOrgPath,
|
||||
() => $courseStore.title // Reads reactively - updates automatically!
|
||||
)
|
||||
);
|
||||
|
||||
// Breadcrumbs update automatically when store changes!
|
||||
const breadcrumbs = $derived(generateBreadcrumbsSync(page.url.pathname, page.url.search, navItems, config));
|
||||
</script>
|
||||
```
|
||||
|
||||
#### Before (LMS):
|
||||
|
||||
```typescript
|
||||
import { generateLmsBreadcrumbs } from './lms-breadcrumb';
|
||||
import { currentCommunityQuestion } from '$features/community/utils/store';
|
||||
@@ -93,6 +91,7 @@ const breadcrumbs = generateLmsBreadcrumbs(
|
||||
```
|
||||
|
||||
#### After (LMS - Reactive Store-Based):
|
||||
|
||||
```svelte
|
||||
<script lang="ts">
|
||||
import { page } from '$app/state';
|
||||
@@ -101,19 +100,14 @@ const breadcrumbs = generateLmsBreadcrumbs(
|
||||
import { currentCommunityQuestion } from '$features/community/utils/store';
|
||||
|
||||
// Config is reactive - updates when store changes
|
||||
const config = $derived(createLmsBreadcrumbConfigWithStore(
|
||||
() => $currentCommunityQuestion.title // Reads reactively from store
|
||||
));
|
||||
|
||||
// Breadcrumbs update automatically when store changes!
|
||||
const breadcrumbs = $derived(
|
||||
generateBreadcrumbsSync(
|
||||
page.url.pathname,
|
||||
page.url.search,
|
||||
navItems,
|
||||
config
|
||||
const config = $derived(
|
||||
createLmsBreadcrumbConfigWithStore(
|
||||
() => $currentCommunityQuestion.title // Reads reactively from store
|
||||
)
|
||||
);
|
||||
|
||||
// Breadcrumbs update automatically when store changes!
|
||||
const breadcrumbs = $derived(generateBreadcrumbsSync(page.url.pathname, page.url.search, navItems, config));
|
||||
</script>
|
||||
```
|
||||
|
||||
@@ -131,6 +125,7 @@ The refactored system supports up to 5 layers of nesting through recursive trave
|
||||
#### Example: Courses → Course → Lesson → Assignment → Submission (5 Layers)
|
||||
|
||||
**Navigation Config** (in `org-navigation.ts`):
|
||||
|
||||
```typescript
|
||||
{
|
||||
titleKey: 'org_navigation.courses',
|
||||
@@ -186,20 +181,20 @@ The refactored system supports up to 5 layers of nesting through recursive trave
|
||||
```
|
||||
|
||||
**Breadcrumb Component** (in `app-breadcrumbs.svelte`):
|
||||
|
||||
```svelte
|
||||
<script lang="ts">
|
||||
import { page } from '$app/state';
|
||||
import { generateBreadcrumbsSync } from './breadcrumb-core';
|
||||
import { createOrgBreadcrumbConfigWithStore } from './breadcrumb-configs';
|
||||
import { assignmentStore, submissionStore, courseStore, lessonStore } from '$features/course/utils/store';
|
||||
|
||||
|
||||
// Create reactive config that reads from stores
|
||||
const config = $derived(createOrgBreadcrumbConfigWithStore(
|
||||
$currentOrgPath,
|
||||
() => {
|
||||
const config = $derived(
|
||||
createOrgBreadcrumbConfigWithStore($currentOrgPath, () => {
|
||||
// This function is called reactively - reads from stores
|
||||
const pathSegments = page.url.pathname.split('/').filter(Boolean);
|
||||
|
||||
|
||||
// Determine which store to read based on current route
|
||||
if (pathSegments.includes('submissions') && pathSegments.length > 5) {
|
||||
return $submissionStore.title || 'Submission';
|
||||
@@ -214,35 +209,28 @@ The refactored system supports up to 5 layers of nesting through recursive trave
|
||||
return $courseStore.title || 'Course';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
));
|
||||
|
||||
const breadcrumbs = $derived(
|
||||
generateBreadcrumbsSync(
|
||||
page.url.pathname,
|
||||
page.url.search,
|
||||
navItems,
|
||||
config
|
||||
)
|
||||
})
|
||||
);
|
||||
|
||||
const breadcrumbs = $derived(generateBreadcrumbsSync(page.url.pathname, page.url.search, navItems, config));
|
||||
</script>
|
||||
```
|
||||
|
||||
**Alternative: Per-Route Store Resolvers** (More Granular):
|
||||
|
||||
```svelte
|
||||
<script lang="ts">
|
||||
import { page } from '$app/state';
|
||||
import { generateBreadcrumbsSync } from './breadcrumb-core';
|
||||
import { createOrgBreadcrumbConfigWithStore } from './breadcrumb-configs';
|
||||
import { assignmentStore, submissionStore, courseStore, lessonStore } from '$features/course/utils/store';
|
||||
|
||||
|
||||
// Create config with store-based resolver
|
||||
const config = $derived(createOrgBreadcrumbConfigWithStore(
|
||||
$currentOrgPath,
|
||||
() => {
|
||||
const config = $derived(
|
||||
createOrgBreadcrumbConfigWithStore($currentOrgPath, () => {
|
||||
// Read from stores reactively based on path structure
|
||||
const pathSegments = page.url.pathname.split('/').filter(Boolean);
|
||||
|
||||
|
||||
// Match store to segment position
|
||||
if (pathSegments.includes('submissions')) {
|
||||
return $submissionStore.title || null;
|
||||
@@ -257,17 +245,10 @@ The refactored system supports up to 5 layers of nesting through recursive trave
|
||||
return $courseStore.title || null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
));
|
||||
|
||||
const breadcrumbs = $derived(
|
||||
generateBreadcrumbsSync(
|
||||
page.url.pathname,
|
||||
page.url.search,
|
||||
navItems,
|
||||
config
|
||||
)
|
||||
})
|
||||
);
|
||||
|
||||
const breadcrumbs = $derived(generateBreadcrumbsSync(page.url.pathname, page.url.search, navItems, config));
|
||||
</script>
|
||||
```
|
||||
|
||||
@@ -288,6 +269,7 @@ The refactored system supports up to 5 layers of nesting through recursive trave
|
||||
#### Store-Based vs Async Resolvers
|
||||
|
||||
**✅ Recommended: Store-Based (Reactive)**
|
||||
|
||||
```typescript
|
||||
// In breadcrumb component
|
||||
const config = $derived(createOrgBreadcrumbConfigWithStore(
|
||||
@@ -300,15 +282,17 @@ const breadcrumbs = $derived(generateBreadcrumbsSync(...));
|
||||
```
|
||||
|
||||
**⚠️ Alternative: Async Resolvers (API Calls)**
|
||||
|
||||
```typescript
|
||||
// In navigation config
|
||||
titleResolver: async (params) => {
|
||||
const assignment = await fetchAssignment(params.param0);
|
||||
return assignment.title;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
**Why Use Stores?**
|
||||
|
||||
- ✅ **No API calls** - data already loaded in component
|
||||
- ✅ **Automatic updates** - breadcrumb updates when store changes
|
||||
- ✅ **Better performance** - no loading states or delays
|
||||
@@ -322,7 +306,7 @@ The `extractParamsFromPath` function extracts all path segments as `param0`, `pa
|
||||
// For URL: /courses/123/lessons/456/assignments/789
|
||||
// params = {
|
||||
// param0: '123', // courseId
|
||||
// param1: '456', // lessonId
|
||||
// param1: '456', // lessonId
|
||||
// param2: '789' // assignmentId
|
||||
// }
|
||||
```
|
||||
@@ -350,41 +334,43 @@ For routes that need async data fetching:
|
||||
```typescript
|
||||
import { generateBreadcrumbs } from './breadcrumb-core'; // Note: async version
|
||||
|
||||
const breadcrumbs = await generateBreadcrumbs(
|
||||
page.url.pathname,
|
||||
page.url.search,
|
||||
navItems,
|
||||
config
|
||||
);
|
||||
const breadcrumbs = await generateBreadcrumbs(page.url.pathname, page.url.search, navItems, config);
|
||||
```
|
||||
|
||||
## Benefits of the Refactored System
|
||||
|
||||
### ✅ 1. No Code Duplication
|
||||
|
||||
- Single unified generator for all contexts
|
||||
- Context-specific configs handle differences
|
||||
|
||||
### ✅ 2. Multi-Level Nesting Support
|
||||
|
||||
- Recursive generation handles any depth
|
||||
- Configurable max depth prevents infinite loops
|
||||
|
||||
### ✅ 3. Flexible Path Prefixes
|
||||
|
||||
- Function-based prefix extraction
|
||||
- Supports dynamic prefixes like `/org/[slug]`
|
||||
|
||||
### ✅ 4. Consistent Dynamic Title Resolution
|
||||
|
||||
- Unified interface for all resolvers
|
||||
- Supports both sync and async resolution
|
||||
|
||||
### ✅ 5. Route-Specific Customization
|
||||
|
||||
- Override labels per route
|
||||
- Custom resolvers for complex cases
|
||||
|
||||
### ✅ 6. Optional Caching
|
||||
|
||||
- Performance optimization
|
||||
- Configurable cache TTL
|
||||
|
||||
### ✅ 7. Type Safety
|
||||
|
||||
- Full TypeScript support
|
||||
- Compile-time error checking
|
||||
|
||||
@@ -393,6 +379,7 @@ const breadcrumbs = await generateBreadcrumbs(
|
||||
**URL**: `/org/myorg/courses/123/lessons/456/assignments/789/submissions/101`
|
||||
|
||||
**Config**:
|
||||
|
||||
```typescript
|
||||
{
|
||||
path: '/courses',
|
||||
@@ -492,8 +479,9 @@ The recursive function processes **each path segment** separately, incrementing
|
||||
**Breadcrumb Result**: `Courses / React Fundamentals / Lessons / Components / Assignments / Build Todo App / Submissions / Submission #101`
|
||||
|
||||
**Depth Breakdown**:
|
||||
|
||||
- `depth=0`: Top-level nav item (`/courses`) → "Courses"
|
||||
- `depth=1`: First dynamic segment (`123`) → "React Fundamentals"
|
||||
- `depth=1`: First dynamic segment (`123`) → "React Fundamentals"
|
||||
- `depth=2`: Static nested route (`lessons`) → "Lessons"
|
||||
- `depth=3`: Second dynamic segment (`456`) → "Components"
|
||||
- `depth=4`: Static nested route (`assignments`) → "Assignments"
|
||||
@@ -560,13 +548,8 @@ beforeEach(() => {
|
||||
|
||||
// Test breadcrumb generation
|
||||
test('generates breadcrumbs for nested route', () => {
|
||||
const breadcrumbs = generateBreadcrumbsSync(
|
||||
'/org/test/courses/123/lessons/456',
|
||||
'',
|
||||
navItems,
|
||||
config
|
||||
);
|
||||
|
||||
const breadcrumbs = generateBreadcrumbsSync('/org/test/courses/123/lessons/456', '', navItems, config);
|
||||
|
||||
expect(breadcrumbs).toHaveLength(4);
|
||||
expect(breadcrumbs[0].label).toBe('Courses');
|
||||
});
|
||||
@@ -604,6 +587,7 @@ This allows gradual migration without breaking existing code.
|
||||
### Why Use Stores Instead of Async Requests?
|
||||
|
||||
**✅ Benefits:**
|
||||
|
||||
- **No API calls** - Data is already loaded in your component
|
||||
- **Automatic updates** - Breadcrumbs update reactively when stores change
|
||||
- **Better performance** - No loading states, delays, or network requests
|
||||
@@ -619,6 +603,7 @@ This allows gradual migration without breaking existing code.
|
||||
### Example: Community Post Title
|
||||
|
||||
**Store** (`$features/community/utils/store.ts`):
|
||||
|
||||
```typescript
|
||||
export const currentCommunityQuestion = writable<{ title: string | null }>({
|
||||
title: null
|
||||
@@ -626,19 +611,20 @@ export const currentCommunityQuestion = writable<{ title: string | null }>({
|
||||
```
|
||||
|
||||
**Page Component** (`+page.svelte`):
|
||||
|
||||
```svelte
|
||||
<script lang="ts">
|
||||
import { currentCommunityQuestion } from '$features/community/utils/store';
|
||||
|
||||
|
||||
let question = $state();
|
||||
|
||||
|
||||
async function fetchQuestion() {
|
||||
const data = await supabase.from('community_question').select('*').single();
|
||||
question = data;
|
||||
// Update store - breadcrumb will reactively update!
|
||||
currentCommunityQuestion.set({ title: data.title });
|
||||
}
|
||||
|
||||
|
||||
$effect(() => {
|
||||
fetchQuestion();
|
||||
});
|
||||
@@ -646,20 +632,21 @@ export const currentCommunityQuestion = writable<{ title: string | null }>({
|
||||
```
|
||||
|
||||
**Breadcrumb Component** (`lms-breadcrumbs.svelte`):
|
||||
|
||||
```svelte
|
||||
<script lang="ts">
|
||||
import { createLmsBreadcrumbConfigWithStore } from './breadcrumb-configs';
|
||||
import { currentCommunityQuestion } from '$features/community/utils/store';
|
||||
|
||||
|
||||
// Reactive config - updates when store changes
|
||||
const config = $derived(createLmsBreadcrumbConfigWithStore(
|
||||
() => $currentCommunityQuestion.title // Reads reactively!
|
||||
));
|
||||
|
||||
// Breadcrumbs automatically update when store changes!
|
||||
const breadcrumbs = $derived(
|
||||
generateBreadcrumbsSync(page.url.pathname, page.url.search, navItems, config)
|
||||
const config = $derived(
|
||||
createLmsBreadcrumbConfigWithStore(
|
||||
() => $currentCommunityQuestion.title // Reads reactively!
|
||||
)
|
||||
);
|
||||
|
||||
// Breadcrumbs automatically update when store changes!
|
||||
const breadcrumbs = $derived(generateBreadcrumbsSync(page.url.pathname, page.url.search, navItems, config));
|
||||
</script>
|
||||
```
|
||||
|
||||
@@ -669,11 +656,10 @@ For routes with multiple dynamic segments, use path-based logic:
|
||||
|
||||
```svelte
|
||||
<script lang="ts">
|
||||
const config = $derived(createOrgBreadcrumbConfigWithStore(
|
||||
$currentOrgPath,
|
||||
() => {
|
||||
const config = $derived(
|
||||
createOrgBreadcrumbConfigWithStore($currentOrgPath, () => {
|
||||
const pathSegments = page.url.pathname.split('/').filter(Boolean);
|
||||
|
||||
|
||||
// Determine which store to read based on route
|
||||
if (pathSegments.includes('submissions')) {
|
||||
return $submissionStore.title;
|
||||
@@ -688,8 +674,8 @@ For routes with multiple dynamic segments, use path-based logic:
|
||||
return $courseStore.title;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
));
|
||||
})
|
||||
);
|
||||
</script>
|
||||
```
|
||||
|
||||
@@ -699,4 +685,3 @@ For routes with multiple dynamic segments, use path-based logic:
|
||||
2. **Use `$derived`**: Always wrap config and breadcrumbs in `$derived` for reactivity
|
||||
3. **Store Updates**: When you update a store, breadcrumbs automatically update - no manual refresh needed!
|
||||
4. **No Async Needed**: Store-based resolvers are synchronous - just return the store value
|
||||
|
||||
|
||||
@@ -20,11 +20,13 @@ This guide explains how to create pages and routes in the dashboard application,
|
||||
The dashboard has two main route contexts:
|
||||
|
||||
### 1. Organization Routes (`/org/[slug]/...`)
|
||||
|
||||
- Uses `OrgSidebar` and `AppHeader`
|
||||
- Path pattern: `/org/[slug]/[feature]`
|
||||
- Example: `/org/acme/courses`, `/org/acme/settings`
|
||||
|
||||
### 2. LMS Routes (`/lms/...`)
|
||||
|
||||
- Uses `LMSSidebar` and `LmsHeader`
|
||||
- Path pattern: `/lms/[feature]`
|
||||
- Example: `/lms/community`, `/lms/settings`
|
||||
@@ -38,6 +40,7 @@ The dashboard has two main route contexts:
|
||||
**Location**: `apps/dashboard/src/routes/[context]/[feature]/+page.svelte`
|
||||
|
||||
**Pattern**:
|
||||
|
||||
```svelte
|
||||
<script lang="ts">
|
||||
import { FeaturePage } from '$features/[feature]/pages';
|
||||
@@ -72,6 +75,7 @@ The dashboard has two main route contexts:
|
||||
**Location**: `apps/dashboard/src/lib/features/[feature]/pages/[feature].svelte`
|
||||
|
||||
**Pattern**:
|
||||
|
||||
```svelte
|
||||
<script lang="ts">
|
||||
import { Search } from '@cio/ui/custom/search';
|
||||
@@ -230,6 +234,7 @@ Settings pages follow a specific pattern with layouts and subroutes.
|
||||
Feature pages contain all business logic and should be placed in `lib/features/[feature]/pages/`.
|
||||
|
||||
**Key Principles**:
|
||||
|
||||
- ✅ All business logic goes in the feature page
|
||||
- ✅ Use `Page.BodyHeader` for filters, search, and view toggles
|
||||
- ✅ Use `Empty` component for empty states
|
||||
@@ -363,6 +368,7 @@ Feature pages contain all business logic and should be placed in `lib/features/[
|
||||
```
|
||||
|
||||
**Key Differences**:
|
||||
|
||||
- Org routes use `AppHeader` (includes AppSetup)
|
||||
- LMS routes use `LmsHeader` (no AppSetup)
|
||||
- Org routes use `OrgSidebar`
|
||||
@@ -426,11 +432,7 @@ Feature pages contain all business logic and should be placed in `lib/features/[
|
||||
<Field.Set>
|
||||
<Field.Legend>{$t('form.another_section')}</Field.Legend>
|
||||
<Field.Field>
|
||||
<UploadImage
|
||||
bind:avatar
|
||||
src={avatarUrl}
|
||||
change={() => (hasUnsavedChanges = true)}
|
||||
/>
|
||||
<UploadImage bind:avatar src={avatarUrl} change={() => (hasUnsavedChanges = true)} />
|
||||
</Field.Field>
|
||||
</Field.Set>
|
||||
</Field.Group>
|
||||
@@ -554,7 +556,7 @@ For dynamic routes (like `community/[slug]`), add breadcrumb data in the route's
|
||||
export async function load({ params }) {
|
||||
// Fetch question data
|
||||
const question = await fetchQuestion(params.slug);
|
||||
|
||||
|
||||
return {
|
||||
breadcrumb: question.title // This will appear in breadcrumbs
|
||||
};
|
||||
@@ -568,6 +570,7 @@ export async function load({ params }) {
|
||||
### Example 1: Simple Feature Page
|
||||
|
||||
**Route**: `routes/org/[slug]/courses/+page.svelte`
|
||||
|
||||
```svelte
|
||||
<script lang="ts">
|
||||
import { CoursesPage } from '$features/courses/pages';
|
||||
@@ -596,6 +599,7 @@ export async function load({ params }) {
|
||||
### Example 2: Settings Page with Save Action
|
||||
|
||||
**Route**: `routes/org/[slug]/settings/customize-lms/+page.svelte`
|
||||
|
||||
```svelte
|
||||
<script lang="ts">
|
||||
import { CustomizeLmsPage } from '$features/settings/pages';
|
||||
@@ -642,6 +646,7 @@ export async function load({ params }) {
|
||||
### Example 3: Form Page Component
|
||||
|
||||
**Feature Page**: `lib/features/settings/pages/profile.svelte`
|
||||
|
||||
```svelte
|
||||
<script lang="ts">
|
||||
import { profile } from '$lib/utils/store/user';
|
||||
@@ -655,11 +660,14 @@ export async function load({ params }) {
|
||||
let hasUnsavedChanges = $state(false);
|
||||
|
||||
export async function handleUpdate() {
|
||||
await profileApi.submit({
|
||||
fullname: $profile.fullname,
|
||||
username: $profile.username,
|
||||
avatar
|
||||
}, $profile);
|
||||
await profileApi.submit(
|
||||
{
|
||||
fullname: $profile.fullname,
|
||||
username: $profile.username,
|
||||
avatar
|
||||
},
|
||||
$profile
|
||||
);
|
||||
|
||||
if (profileApi.success) {
|
||||
hasUnsavedChanges = false;
|
||||
@@ -708,12 +716,14 @@ export async function load({ params }) {
|
||||
When creating a new page, follow this checklist:
|
||||
|
||||
### Route Setup
|
||||
|
||||
- [ ] Create route file at `routes/[context]/[feature]/+page.svelte`
|
||||
- [ ] Add `Page.Root`, `Page.Header`, `Page.Body` structure
|
||||
- [ ] Add `<svelte:head>` with page title
|
||||
- [ ] Import and render feature page component
|
||||
|
||||
### Feature Page
|
||||
|
||||
- [ ] Create feature page at `lib/features/[feature]/pages/[feature].svelte`
|
||||
- [ ] Export from `lib/features/[feature]/pages/index.ts`
|
||||
- [ ] Use `Page.BodyHeader` for filters/search
|
||||
@@ -721,6 +731,7 @@ When creating a new page, follow this checklist:
|
||||
- [ ] Handle loading states
|
||||
|
||||
### Forms (if applicable)
|
||||
|
||||
- [ ] Use `Field.Group` pattern (NOT Row/Grid/Column)
|
||||
- [ ] Use `Field.Set` for sections
|
||||
- [ ] Use `Field.Separator` between sections
|
||||
@@ -729,6 +740,7 @@ When creating a new page, follow this checklist:
|
||||
- [ ] Add `UnsavedChanges` component if needed
|
||||
|
||||
### Navigation (if needed)
|
||||
|
||||
- [ ] Add item to `org-navigation.ts` or `lms-navigation.ts`
|
||||
- [ ] Set appropriate `matchPattern` for active state
|
||||
- [ ] Add icon component
|
||||
@@ -736,6 +748,7 @@ When creating a new page, follow this checklist:
|
||||
- [ ] Configure dynamic segments if needed
|
||||
|
||||
### Settings Pages (if applicable)
|
||||
|
||||
- [ ] Create `+layout.svelte` with `Page.Root` wrapper
|
||||
- [ ] Create default `+page.svelte` for main settings
|
||||
- [ ] Create subroute `+page.svelte` files for each subroute
|
||||
@@ -755,6 +768,7 @@ When creating a new page, follow this checklist:
|
||||
### Page Actions
|
||||
|
||||
Actions in `Page.Action` typically include:
|
||||
|
||||
- Save/Update buttons
|
||||
- Create/Add buttons
|
||||
- Export buttons
|
||||
@@ -763,6 +777,7 @@ Actions in `Page.Action` typically include:
|
||||
### Loading States
|
||||
|
||||
Use skeleton loaders or spinner components:
|
||||
|
||||
```svelte
|
||||
{#if isLoading}
|
||||
<FeatureLoader />
|
||||
@@ -776,13 +791,9 @@ Use skeleton loaders or spinner components:
|
||||
### Empty States
|
||||
|
||||
Always use the `Empty` component:
|
||||
|
||||
```svelte
|
||||
<Empty
|
||||
title={$t('feature.empty_title')}
|
||||
description={$t('feature.empty_description')}
|
||||
icon={SomeIcon}
|
||||
variant="page"
|
||||
>
|
||||
<Empty title={$t('feature.empty_title')} description={$t('feature.empty_description')} icon={SomeIcon} variant="page">
|
||||
<ActionButton />
|
||||
</Empty>
|
||||
```
|
||||
@@ -807,6 +818,7 @@ Always use the `Empty` component:
|
||||
If you encounter old patterns, migrate them:
|
||||
|
||||
### Old: Row/Grid/Column Pattern
|
||||
|
||||
```svelte
|
||||
<!-- OLD - DON'T USE -->
|
||||
<Grid>
|
||||
@@ -822,6 +834,7 @@ If you encounter old patterns, migrate them:
|
||||
```
|
||||
|
||||
### New: Field.Group Pattern
|
||||
|
||||
```svelte
|
||||
<!-- NEW - USE THIS -->
|
||||
<Field.Group class="max-w-md! w-full px-2">
|
||||
@@ -843,4 +856,3 @@ If you encounter old patterns, migrate them:
|
||||
- See existing pages in `routes/org/[slug]/` and `routes/lms/` for examples
|
||||
- See `lib/features/settings/pages/` for form examples
|
||||
- See `lib/features/community/pages/` for list page examples
|
||||
|
||||
|
||||
Reference in New Issue
Block a user