Browse Source

Format files

master
buttercat1791 5 months ago
parent
commit
10195301df
  1. 78
      src/app.css
  2. 180
      src/lib/a/AGENTS.md
  3. 37
      src/lib/a/README.md
  4. 24
      src/lib/a/parse-components.js
  5. 64
      src/styles/a/cards.css
  6. 6
      src/styles/a/forms.css

78
src/app.css

@ -156,43 +156,67 @@
/* === HEADINGS === */ /* === HEADINGS === */
/* Base heading styles - gray-900 (light) / gray-100 (dark) for high contrast */ /* Base heading styles - gray-900 (light) / gray-100 (dark) for high contrast */
h1, h2, h3, h4, h5, h6, h1,
h1.h-leather, h2.h-leather, h3.h-leather, h2,
h4.h-leather, h5.h-leather, h6.h-leather { h3,
h4,
h5,
h6,
h1.h-leather,
h2.h-leather,
h3.h-leather,
h4.h-leather,
h5.h-leather,
h6.h-leather {
@apply text-gray-900 dark:text-gray-100; @apply text-gray-900 dark:text-gray-100;
} }
/* Heading sizes and weights */ /* Heading sizes and weights */
h1, h1.h-leather { h1,
h1.h-leather {
@apply text-4xl font-bold; @apply text-4xl font-bold;
} }
h2, h2.h-leather { h2,
h2.h-leather {
@apply text-3xl font-bold; @apply text-3xl font-bold;
} }
h3, h3.h-leather { h3,
h3.h-leather {
@apply text-2xl font-bold; @apply text-2xl font-bold;
} }
h4, h4.h-leather { h4,
h4.h-leather {
@apply text-xl font-bold; @apply text-xl font-bold;
} }
h5, h5.h-leather { h5,
h5.h-leather {
@apply text-lg font-semibold; @apply text-lg font-semibold;
} }
h6, h6.h-leather { h6,
h6.h-leather {
@apply text-base font-semibold; @apply text-base font-semibold;
} }
/* Heading links - primary-600 (light) / primary-400 (dark) for hover */ /* Heading links - primary-600 (light) / primary-400 (dark) for hover */
h1 a, h2 a, h3 a, h4 a, h5 a, h6 a, h1 a,
h1.h-leather a, h2.h-leather a, h3.h-leather a, h2 a,
h4.h-leather a, h5.h-leather a, h6.h-leather a { h3 a,
@apply text-gray-900 dark:text-gray-100 h4 a,
hover:text-primary-600 dark:hover:text-primary-400; h5 a,
h6 a,
h1.h-leather a,
h2.h-leather a,
h3.h-leather a,
h4.h-leather a,
h5.h-leather a,
h6.h-leather a {
@apply text-gray-900 dark:text-gray-100 hover:text-primary-600
dark:hover:text-primary-400;
} }
/* === LEATHER COMPONENTS === */ /* === LEATHER COMPONENTS === */
@ -248,8 +272,7 @@
div.note-leather, div.note-leather,
p.note-leather, p.note-leather,
section.note-leather { section.note-leather {
@apply text-gray-900 dark:text-gray-100 @apply text-gray-900 dark:text-gray-100 p-2 rounded;
p-2 rounded;
} }
.edit div.note-leather:hover:not(:has(.note-leather:hover)), .edit div.note-leather:hover:not(:has(.note-leather:hover)),
@ -407,14 +430,14 @@
/* Lists */ /* Lists */
.ol-leather li a, .ol-leather li a,
.ul-leather li a { .ul-leather li a {
@apply text-gray-900 dark:text-gray-100 @apply text-gray-900 dark:text-gray-100 hover:text-primary-600
hover:text-primary-600 dark:hover:text-primary-400; dark:hover:text-primary-400;
} }
/* Links - consistent hover colors */ /* Links - consistent hover colors */
.link { .link {
@apply underline cursor-pointer @apply underline cursor-pointer hover:text-primary-600
hover:text-primary-600 dark:hover:text-primary-400; dark:hover:text-primary-400;
} }
.npub-badge { .npub-badge {
@ -506,8 +529,8 @@
/* All links - consistent hover behavior */ /* All links - consistent hover behavior */
a { a {
@apply underline cursor-pointer @apply underline cursor-pointer hover:text-primary-600
hover:text-primary-600 dark:hover:text-primary-400; dark:hover:text-primary-400;
} }
.imageblock { .imageblock {
@ -694,10 +717,9 @@
} }
} }
.icon-wiki { .icon-wiki {
font-size: 20px; font-size: 20px;
line-height: 20px; line-height: 20px;
vertical-align: text-bottom; vertical-align: text-bottom;
font-weight: 500; font-weight: 500;
}
} }

180
src/lib/a/AGENTS.md

@ -1,23 +1,30 @@
# Alexandria Component Library - AI Agent Guide # Alexandria Component Library - AI Agent Guide
**Version:** 1.0.0 **Version:** 1.0.0\
**Last Updated:** October 4, 2025 **Last Updated:** October 4, 2025
This guide provides comprehensive instructions for AI agents working with the Alexandria UI component library. Following these guidelines ensures consistency, maintainability, and proper integration with the existing codebase. This guide provides comprehensive instructions for AI agents working with the
Alexandria UI component library. Following these guidelines ensures consistency,
maintainability, and proper integration with the existing codebase.
--- ---
## Core Principles ## Core Principles
### 1. Always Check Before Creating ### 1. Always Check Before Creating
Before creating any new UI component, **you must**: Before creating any new UI component, **you must**:
1. Search the existing component library in `src/lib/a/` 1. Search the existing component library in `src/lib/a/`
2. Review `alexandria-components.json` for available components 2. Review `alexandria-components.json` for available components
3. Check if an existing component can be reused or extended 3. Check if an existing component can be reused or extended
4. Only create new components when absolutely necessary 4. Only create new components when absolutely necessary
### 2. Use alexandria-components.json as Your Reference ### 2. Use alexandria-components.json as Your Reference
The `alexandria-components.json` file is the **single source of truth** for all available components. It contains:
The `alexandria-components.json` file is the **single source of truth** for all
available components. It contains:
- Complete component inventory (18 components across 5 categories) - Complete component inventory (18 components across 5 categories)
- Full prop definitions with types and defaults - Full prop definitions with types and defaults
- Usage examples and patterns - Usage examples and patterns
@ -26,13 +33,16 @@ The `alexandria-components.json` file is the **single source of truth** for all
**Always consult this file first** when selecting components. **Always consult this file first** when selecting components.
### 3. Maintain TSDoc Documentation Standards ### 3. Maintain TSDoc Documentation Standards
All components use **TSDoc format** for documentation. You must maintain this standard when creating or modifying components.
All components use **TSDoc format** for documentation. You must maintain this
standard when creating or modifying components.
--- ---
## Component Inventory ## Component Inventory
### Available Categories ### Available Categories
1. **Primitives** (8 components) - Basic UI building blocks 1. **Primitives** (8 components) - Basic UI building blocks
2. **Navigation** (2 components) - App navigation elements 2. **Navigation** (2 components) - App navigation elements
3. **Forms** (4 components) - Input and editing interfaces 3. **Forms** (4 components) - Input and editing interfaces
@ -44,7 +54,9 @@ All components use **TSDoc format** for documentation. You must maintain this st
## Component Selection Workflow ## Component Selection Workflow
### Step 1: Identify the Need ### Step 1: Identify the Need
Determine what UI functionality is required: Determine what UI functionality is required:
- User input? → Check **Forms** category - User input? → Check **Forms** category
- Display content? → Check **Cards** category - Display content? → Check **Cards** category
- Navigation? → Check **Navigation** category - Navigation? → Check **Navigation** category
@ -59,7 +71,9 @@ For example: Finding a component for user profiles
- Result: AProfilePreview and ANostrUser are available - Result: AProfilePreview and ANostrUser are available
### Step 3: Review Component Props ### Step 3: Review Component Props
Check the component's props in `alexandria-components.json`: Check the component's props in `alexandria-components.json`:
```json ```json
{ {
"name": "AEventPreview", "name": "AEventPreview",
@ -82,6 +96,7 @@ Check the component's props in `alexandria-components.json`:
``` ```
### Step 4: Review Examples ### Step 4: Review Examples
Check the `examples` array in `alexandria-components.json` for usage patterns. Check the `examples` array in `alexandria-components.json` for usage patterns.
--- ---
@ -111,7 +126,6 @@ Every component **must** include TSDoc comments following this exact format:
* <ComponentName requiredProp={value} optionalProp={value} ></ComponentName> * <ComponentName requiredProp={value} optionalProp={value} ></ComponentName>
* ``` * ```
* *
*
* @features * @features
* - Feature 1 * - Feature 1
* - Feature 2 * - Feature 2
@ -126,24 +140,25 @@ Every component **must** include TSDoc comments following this exact format:
### TSDoc Tags Reference ### TSDoc Tags Reference
| Tag | Purpose | Required | Example | | Tag | Purpose | Required | Example |
|-----|---------|----------|---------| | ---------------- | ------------------------------ | ----------- | --------------------------------------------- |
| `@fileoverview` | Component name and description | ✅ Yes | `@fileoverview AAlert Component - Alexandria` | | `@fileoverview` | Component name and description | ✅ Yes | `@fileoverview AAlert Component - Alexandria` |
| `@component` | Marks file as Svelte component | ✅ Yes | `@component` | | `@component` | Marks file as Svelte component | ✅ Yes | `@component` |
| `@category` | Component category | ✅ Yes | `@category Primitives` | | `@category` | Component category | ✅ Yes | `@category Primitives` |
| `@prop` | Define component properties | ✅ Yes | `@prop {string} [color] - Alert color theme` | | `@prop` | Define component properties | ✅ Yes | `@prop {string} [color] - Alert color theme` |
| `@example` | Usage examples with code | ✅ Yes | See format above | | `@example` | Usage examples with code | ✅ Yes | See format above |
| `@features` | List key functionality | ✅ Yes | `- Responsive layout` | | `@features` | List key functionality | ✅ Yes | `- Responsive layout` |
| `@accessibility` | Accessibility notes | ✅ Yes | `- ARIA compliant` | | `@accessibility` | Accessibility notes | ✅ Yes | `- ARIA compliant` |
| `@since` | Version introduced | ⚠ Optional | `@since 1.0.0` | | `@since` | Version introduced | ⚠ Optional | `@since 1.0.0` |
--- ---
## Component Import Patterns ## Component Import Patterns
### Import ### Import
```typescript ```typescript
import { AAlert, AEventPreview, AMarkupForm } from '$lib/a'; import { AAlert, AEventPreview, AMarkupForm } from "$lib/a";
``` ```
--- ---
@ -153,12 +168,14 @@ import { AAlert, AEventPreview, AMarkupForm } from '$lib/a';
### When to Create a New Component ### When to Create a New Component
**DO create a new component when:** **DO create a new component when:**
- No existing component provides the required functionality - No existing component provides the required functionality
- The component will be reused in 3+ places - The component will be reused in 3+ places
- It represents a distinct, standalone UI pattern - It represents a distinct, standalone UI pattern
- It follows the Alexandria design system - It follows the Alexandria design system
**DON'T create a new component when:** **DON'T create a new component when:**
- An existing component can be configured to meet needs - An existing component can be configured to meet needs
- It's only used once (keep in parent component) - It's only used once (keep in parent component)
- It's too generic (use Flowbite components directly) - It's too generic (use Flowbite components directly)
@ -171,7 +188,8 @@ import { AAlert, AEventPreview, AMarkupForm } from '$lib/a';
- [ ] **Name** following convention: `A[ComponentName].svelte` - [ ] **Name** following convention: `A[ComponentName].svelte`
- [ ] **Add TSDoc** documentation following standard format - [ ] **Add TSDoc** documentation following standard format
- [ ] **Create** component file in correct category folder - [ ] **Create** component file in correct category folder
- [ ] **Add styles** to appropriate `/src/styles/a/` CSS file (or create new file if needed) - [ ] **Add styles** to appropriate `/src/styles/a/` CSS file (or create new
file if needed)
- [ ] **Import new CSS file** in `app.css` if created - [ ] **Import new CSS file** in `app.css` if created
- [ ] **Export** component in `index.ts` - [ ] **Export** component in `index.ts`
- [ ] **Update** `alexandria-components.json` (run parse script) - [ ] **Update** `alexandria-components.json` (run parse script)
@ -227,7 +245,9 @@ let {
## Updating alexandria-components.json ## Updating alexandria-components.json
### When to Update ### When to Update
You **must** update `alexandria-components.json` whenever: You **must** update `alexandria-components.json` whenever:
- A new component is created - A new component is created
- Component props are added, modified, or removed - Component props are added, modified, or removed
- Component examples are updated - Component examples are updated
@ -246,12 +266,15 @@ You **must** update `alexandria-components.json` whenever:
4. **Commit** both the component file and updated JSON 4. **Commit** both the component file and updated JSON
### Parser Script Location ### Parser Script Location
- **File:** `src/lib/a/parse-components.js` - **File:** `src/lib/a/parse-components.js`
- **Purpose:** Extracts TSDoc from all `.svelte` files in the library - **Purpose:** Extracts TSDoc from all `.svelte` files in the library
- **Output:** `alexandria-components.json` - **Output:** `alexandria-components.json`
### Manual Updates (When Necessary) ### Manual Updates (When Necessary)
If the parser doesn't capture something correctly: If the parser doesn't capture something correctly:
1. First, try to fix the TSDoc format 1. First, try to fix the TSDoc format
2. Re-run the parser 2. Re-run the parser
3. Only manually edit JSON as last resort 3. Only manually edit JSON as last resort
@ -262,7 +285,9 @@ If the parser doesn't capture something correctly:
## Design System Integration ## Design System Integration
### Theme Compatibility ### Theme Compatibility
All Alexandria components support: All Alexandria components support:
- Light and dark themes - Light and dark themes
- "Leather" aesthetic (warm browns, tans) - "Leather" aesthetic (warm browns, tans)
- Flowbite-based styling - Flowbite-based styling
@ -271,6 +296,7 @@ All Alexandria components support:
### Styling Guidelines ### Styling Guidelines
**DO:** **DO:**
- Use Tailwind CSS classes - Use Tailwind CSS classes
- Leverage Flowbite Svelte components as base - Leverage Flowbite Svelte components as base
- Follow existing color patterns in library - Follow existing color patterns in library
@ -278,12 +304,14 @@ All Alexandria components support:
- Test in both light and dark modes - Test in both light and dark modes
**DON'T:** **DON'T:**
- Add arbitrary custom CSS without justification - Add arbitrary custom CSS without justification
- Override Flowbite's accessibility features - Override Flowbite's accessibility features
- Hard-code colors (use theme tokens) - Hard-code colors (use theme tokens)
- Create layout inconsistencies - Create layout inconsistencies
### Component Styling Pattern ### Component Styling Pattern
```svelte ```svelte
<!-- Use Flowbite component as base --> <!-- Use Flowbite component as base -->
<Alert color="info" class="leather-theme"> <Alert color="info" class="leather-theme">
@ -297,9 +325,11 @@ All Alexandria components support:
### **Mandatory Styling Rules** ### **Mandatory Styling Rules**
**ALL custom styles for Alexandria components MUST go in the `/src/styles/a/` folder.** **ALL custom styles for Alexandria components MUST go in the `/src/styles/a/`
folder.**
**DO NOT add `<style>` blocks inside component `.svelte` files unless absolutely necessary for component-scoped styles that cannot be reused.** **DO NOT add `<style>` blocks inside component `.svelte` files unless absolutely
necessary for component-scoped styles that cannot be reused.**
### Directory Structure ### Directory Structure
@ -325,7 +355,9 @@ src/
### Styling Workflow ### Styling Workflow
#### 1. **Determine Component Category** #### 1. **Determine Component Category**
Identify which category your component belongs to: Identify which category your component belongs to:
- `primitives/``styles/a/primitives.css` - `primitives/``styles/a/primitives.css`
- `forms/``styles/a/forms.css` - `forms/``styles/a/forms.css`
- `cards/``styles/a/cards.css` - `cards/``styles/a/cards.css`
@ -333,14 +365,18 @@ Identify which category your component belongs to:
- `reader/``styles/a/reader.css` - `reader/``styles/a/reader.css`
#### 2. **Check if Style File Exists** #### 2. **Check if Style File Exists**
Before adding styles, verify the corresponding CSS file exists in `src/styles/a/`:
Before adding styles, verify the corresponding CSS file exists in
`src/styles/a/`:
**Existing files:** **Existing files:**
- ✅ `styles/a/cards.css` - ✅ `styles/a/cards.css`
- ✅ `styles/a/forms.css` - ✅ `styles/a/forms.css`
- ✅ `styles/a/primitives.css` - ✅ `styles/a/primitives.css`
**Files to create when needed:** **Files to create when needed:**
- ⚠ `styles/a/nav.css` (create for navigation components) - ⚠ `styles/a/nav.css` (create for navigation components)
- ⚠ `styles/a/reader.css` (create for reader components) - ⚠ `styles/a/reader.css` (create for reader components)
@ -349,12 +385,14 @@ Before adding styles, verify the corresponding CSS file exists in `src/styles/a/
If the CSS file doesn't exist for your category: If the CSS file doesn't exist for your category:
**Step A: Create the file** **Step A: Create the file**
```bash ```bash
# Create the new CSS file # Create the new CSS file
type nul > src\styles\a\nav.css type nul > src\styles\a\nav.css
``` ```
**Step B: Add the CSS structure** **Step B: Add the CSS structure**
```css ```css
/* src/styles/a/nav.css */ /* src/styles/a/nav.css */
/* Alexandria Navigation Component Styles */ /* Alexandria Navigation Component Styles */
@ -362,19 +400,18 @@ type nul > src\styles\a\nav.css
@layer components { @layer components {
/* ANavbar styles */ /* ANavbar styles */
.navbar-alexandria { .navbar-alexandria {
@apply /* your Tailwind classes */; @apply; /* your Tailwind classes */
} }
/* AFooter styles */ /* AFooter styles */
.footer-alexandria { .footer-alexandria {
@apply /* your Tailwind classes */; @apply; /* your Tailwind classes */
} }
} }
``` ```
**Step C: Import in app.css** **Step C: Import in app.css** Add the import to `src/app.css` in the correct
Add the import to `src/app.css` in the correct location, after existing imports. location, after existing imports.
#### 4. **Add Component Styles** #### 4. **Add Component Styles**
@ -431,6 +468,7 @@ let { color = "info", dismissable = false } = $props();
### CSS Organization Best Practices ### CSS Organization Best Practices
#### Naming Conventions #### Naming Conventions
- Use descriptive, component-specific class names - Use descriptive, component-specific class names
- Prefix with component name or category - Prefix with component name or category
- Use `-leather` or `-alexandria` suffix for themed styles - Use `-leather` or `-alexandria` suffix for themed styles
@ -441,6 +479,7 @@ let { color = "info", dismissable = false } = $props();
- `.form-input-alexandria` - `.form-input-alexandria`
#### File Structure Within CSS Files #### File Structure Within CSS Files
Organize styles by component within each file: Organize styles by component within each file:
```css ```css
@ -451,38 +490,39 @@ Organize styles by component within each file:
* AMarkupForm Component * AMarkupForm Component
* ======================================== */ * ======================================== */
.markup-form-container { .markup-form-container {
@apply /* styles */; @apply; /* styles */
} }
.markup-form-header { .markup-form-header {
@apply /* styles */; @apply; /* styles */
} }
/* ======================================== /* ========================================
* ACommentForm Component * ACommentForm Component
* ======================================== */ * ======================================== */
.comment-form-wrapper { .comment-form-wrapper {
@apply /* styles */; @apply; /* styles */
} }
.comment-form-textarea { .comment-form-textarea {
@apply /* styles */; @apply; /* styles */
} }
/* ======================================== /* ========================================
* ATextareaWithPreview Component * ATextareaWithPreview Component
* ======================================== */ * ======================================== */
.textarea-preview-container { .textarea-preview-container {
@apply /* styles */; @apply; /* styles */
} }
.textarea-toolbar { .textarea-toolbar {
@apply /* styles */; @apply; /* styles */
} }
} }
``` ```
#### Use Tailwind @apply Directive #### Use Tailwind @apply Directive
Prefer `@apply` with Tailwind utilities over custom CSS: Prefer `@apply` with Tailwind utilities over custom CSS:
```css ```css
@ -505,6 +545,7 @@ Prefer `@apply` with Tailwind utilities over custom CSS:
### When Component-Scoped Styles Are Acceptable ### When Component-Scoped Styles Are Acceptable
Use `<style>` blocks in `.svelte` files ONLY when: Use `<style>` blocks in `.svelte` files ONLY when:
1. **Truly unique** - Style is used nowhere else and never will be 1. **Truly unique** - Style is used nowhere else and never will be
2. **Animation keyframes** - Component-specific animations 2. **Animation keyframes** - Component-specific animations
3. **CSS variables** - Component-specific custom properties 3. **CSS variables** - Component-specific custom properties
@ -543,11 +584,13 @@ Use `<style>` blocks in `.svelte` files ONLY when:
**Step 1:** Check if `styles/a/nav.css` exists (it doesn't) **Step 1:** Check if `styles/a/nav.css` exists (it doesn't)
**Step 2:** Create the file **Step 2:** Create the file
```bash ```bash
type nul > src\styles\a\nav.css type nul > src\styles\a\nav.css
``` ```
**Step 3:** Add styles to the new file **Step 3:** Add styles to the new file
```css ```css
/* src/styles/a/nav.css */ /* src/styles/a/nav.css */
/* Alexandria Navigation Component Styles */ /* Alexandria Navigation Component Styles */
@ -583,15 +626,17 @@ type nul > src\styles\a\nav.css
``` ```
**Step 4:** Import in app.css **Step 4:** Import in app.css
```css ```css
/* src/app.css */ /* src/app.css */
@import "./styles/a/cards.css"; @import "./styles/a/cards.css";
@import "./styles/a/forms.css"; @import "./styles/a/forms.css";
@import "./styles/a/primitives.css"; @import "./styles/a/primitives.css";
@import "./styles/a/nav.css"; /* ← NEW */ @import "./styles/a/nav.css"; /* ← NEW */
``` ```
**Step 5:** Use in component **Step 5:** Use in component
```svelte ```svelte
<!-- src/lib/a/nav/ANavbar.svelte --> <!-- src/lib/a/nav/ANavbar.svelte -->
<script lang="ts"> <script lang="ts">
@ -612,6 +657,7 @@ import { Navbar } from "flowbite-svelte";
## Testing Requirements ## Testing Requirements
### Component Testing ### Component Testing
When creating or modifying components: When creating or modifying components:
1. **Visual Testing** 1. **Visual Testing**
@ -640,11 +686,13 @@ When creating or modifying components:
## Common Patterns & Best Practices ## Common Patterns & Best Practices
### Bindable Props ### Bindable Props
Use `$bindable` for two-way data binding: Use `$bindable` for two-way data binding:
```typescript ```typescript
let { let {
value = $bindable(""), value = $bindable(""),
isOpen = $bindable(false) isOpen = $bindable(false),
} = $props<{ } = $props<{
value?: string; value?: string;
isOpen?: boolean; isOpen?: boolean;
@ -652,11 +700,13 @@ let {
``` ```
### Event Handlers ### Event Handlers
Use optional function props for callbacks: Use optional function props for callbacks:
```typescript ```typescript
let { let {
onSubmit = async () => {}, onSubmit = async () => {},
onClick onClick,
} = $props<{ } = $props<{
onSubmit?: (data: string) => Promise<void>; onSubmit?: (data: string) => Promise<void>;
onClick?: () => void; onClick?: () => void;
@ -664,6 +714,7 @@ let {
``` ```
### Conditional Rendering ### Conditional Rendering
```svelte ```svelte
{#if showContent} {#if showContent}
<div>Content</div> <div>Content</div>
@ -671,7 +722,9 @@ let {
``` ```
### Snippets (Svelte 5) ### Snippets (Svelte 5)
For flexible content slots: For flexible content slots:
```typescript ```typescript
@prop {snippet} children - Main content (required) @prop {snippet} children - Main content (required)
@prop {snippet} [title] - Optional title section @prop {snippet} [title] - Optional title section
@ -688,18 +741,21 @@ For flexible content slots:
## Common Mistakes to Avoid ## Common Mistakes to Avoid
### DON'T: Create duplicate components ### DON'T: Create duplicate components
```typescript ```typescript
// Bad: Creating new component without checking // Bad: Creating new component without checking
export default AUserCard.svelte // AProfilePreview already exists! export default AUserCard.svelte; // AProfilePreview already exists!
``` ```
### DO: Use existing components ### DO: Use existing components
```typescript ```typescript
// Good: Use existing component // Good: Use existing component
import { AProfilePreview } from '$lib/a'; import { AProfilePreview } from "$lib/a";
``` ```
### DON'T: Skip TSDoc documentation ### DON'T: Skip TSDoc documentation
```svelte ```svelte
<!-- Bad: No documentation --> <!-- Bad: No documentation -->
<script> <script>
@ -708,6 +764,7 @@ let { prop } = $props();
``` ```
### DO: Add complete TSDoc ### DO: Add complete TSDoc
```svelte ```svelte
<!-- Good: Complete documentation --> <!-- Good: Complete documentation -->
<script> <script>
@ -720,12 +777,14 @@ let { prop } = $props();
``` ```
### DON'T: Forget to update JSON ### DON'T: Forget to update JSON
```bash ```bash
# Bad: Only editing component, not updating JSON # Bad: Only editing component, not updating JSON
# (Other agents won't know about your changes!) # (Other agents won't know about your changes!)
``` ```
### DO: Always run the parser ### DO: Always run the parser
```bash ```bash
# Good: Update JSON after changes # Good: Update JSON after changes
cd src/lib/a cd src/lib/a
@ -733,12 +792,14 @@ node parse-components.js
``` ```
### DON'T: Use generic component names ### DON'T: Use generic component names
```typescript ```typescript
// Bad: Could be anything // Bad: Could be anything
export UserDisplay.svelte export UserDisplay.svelte
``` ```
### DO: Follow naming convention ### DO: Follow naming convention
```typescript ```typescript
// Good: Clearly part of Alexandria library // Good: Clearly part of Alexandria library
export ANostrUser.svelte export ANostrUser.svelte
@ -749,6 +810,7 @@ export ANostrUser.svelte
## Integration with Application ## Integration with Application
### Directory Structure ### Directory Structure
``` ```
src/lib/a/ src/lib/a/
├── AGENTS.md ← This file ├── AGENTS.md ← This file
@ -778,9 +840,11 @@ src/lib/a/
## Examples: Component Selection Scenarios ## Examples: Component Selection Scenarios
### Scenario 1: Displaying User Information ### Scenario 1: Displaying User Information
**Need:** Show user profile with avatar and bio **Need:** Show user profile with avatar and bio
**Process:** **Process:**
1. Check `alexandria-components.json` for "user" or "profile" 1. Check `alexandria-components.json` for "user" or "profile"
2. Find: `ANostrUser` (compact) and `AProfilePreview` (full card) 2. Find: `ANostrUser` (compact) and `AProfilePreview` (full card)
3. Choose based on needs: 3. Choose based on needs:
@ -788,6 +852,7 @@ src/lib/a/
- Full profile card: `AProfilePreview` - Full profile card: `AProfilePreview`
**Solution:** **Solution:**
```svelte ```svelte
<AProfilePreview <AProfilePreview
{npub} {npub}
@ -797,14 +862,17 @@ src/lib/a/
``` ```
### Scenario 2: Creating a Comment Form ### Scenario 2: Creating a Comment Form
**Need:** Allow users to write and submit comments **Need:** Allow users to write and submit comments
**Process:** **Process:**
1. Check `alexandria-components.json` under "Forms" 1. Check `alexandria-components.json` under "Forms"
2. Find: `ACommentForm` - "Comment creation with markup support" 2. Find: `ACommentForm` - "Comment creation with markup support"
3. Review props: `content`, `placeholder`, `onSubmit` 3. Review props: `content`, `placeholder`, `onSubmit`
**Solution:** **Solution:**
```svelte ```svelte
<ACommentForm <ACommentForm
bind:content={commentText} bind:content={commentText}
@ -814,14 +882,17 @@ src/lib/a/
``` ```
### Scenario 3: Showing Event Cards ### Scenario 3: Showing Event Cards
**Need:** Display nostr events in a feed **Need:** Display nostr events in a feed
**Process:** **Process:**
1. Check `alexandria-components.json` under "Cards" 1. Check `alexandria-components.json` under "Cards"
2. Find: `AEventPreview` - "Event preview cards with metadata and actions" 2. Find: `AEventPreview` - "Event preview cards with metadata and actions"
3. Review configurable options: `showContent`, `truncateContentAt`, etc. 3. Review configurable options: `showContent`, `truncateContentAt`, etc.
**Solution:** **Solution:**
```svelte ```svelte
{#each events as event} {#each events as event}
<AEventPreview <AEventPreview
@ -834,14 +905,17 @@ src/lib/a/
``` ```
### Scenario 4: Alert/Notification ### Scenario 4: Alert/Notification
**Need:** Show success message after save **Need:** Show success message after save
**Process:** **Process:**
1. Check "Primitives" category 1. Check "Primitives" category
2. Find: `AAlert` - "Themed alert messages with dismissal options" 2. Find: `AAlert` - "Themed alert messages with dismissal options"
3. Review color options and dismissable prop 3. Review color options and dismissable prop
**Solution:** **Solution:**
```svelte ```svelte
{#if saveSuccessful} {#if saveSuccessful}
<AAlert color="success" dismissable={true}> <AAlert color="success" dismissable={true}>
@ -855,24 +929,28 @@ src/lib/a/
## Troubleshooting ## Troubleshooting
### Component Not Found ### Component Not Found
1. Verify component exists in `alexandria-components.json` 1. Verify component exists in `alexandria-components.json`
2. Check import statement syntax 2. Check import statement syntax
3. Ensure component is exported in `index.ts` 3. Ensure component is exported in `index.ts`
4. Check file path matches category structure 4. Check file path matches category structure
### Props Not Working ### Props Not Working
1. Check prop name spelling in `alexandria-components.json` 1. Check prop name spelling in `alexandria-components.json`
2. Verify prop type matches expected type 2. Verify prop type matches expected type
3. Check if prop is bindable (use `bind:` prefix if needed) 3. Check if prop is bindable (use `bind:` prefix if needed)
4. Review component TSDoc for usage examples 4. Review component TSDoc for usage examples
### Styling Issues ### Styling Issues
1. Verify theme mode (light/dark) is set correctly 1. Verify theme mode (light/dark) is set correctly
2. Check if custom classes conflict with Flowbite 2. Check if custom classes conflict with Flowbite
3. Ensure Tailwind classes are being applied 3. Ensure Tailwind classes are being applied
4. Test in isolation to identify conflicts 4. Test in isolation to identify conflicts
### Documentation Out of Sync ### Documentation Out of Sync
1. Run parser script: `node parse-components.js` 1. Run parser script: `node parse-components.js`
2. Verify TSDoc format is correct 2. Verify TSDoc format is correct
3. Check for syntax errors in TSDoc comments 3. Check for syntax errors in TSDoc comments
@ -883,20 +961,25 @@ src/lib/a/
## Additional Resources ## Additional Resources
### Related Files ### Related Files
- **README.md**: User-facing library documentation - **README.md**: User-facing library documentation
- **index.ts**: Component export definitions - **index.ts**: Component export definitions
- **parse-components.js**: TSDoc extraction script - **parse-components.js**: TSDoc extraction script
- **alexandria-components.json**: Generated component registry - **alexandria-components.json**: Generated component registry
### External Documentation ### External Documentation
- [Flowbite Svelte](https://flowbite-svelte.com/) - Base component library - [Flowbite Svelte](https://flowbite-svelte.com/) - Base component library
- [Tailwind CSS](https://tailwindcss.com/) - Styling framework - [Tailwind CSS](https://tailwindcss.com/) - Styling framework
- [TSDoc](https://tsdoc.org/) - Documentation standard - [TSDoc](https://tsdoc.org/) - Documentation standard
- [Svelte 5 Docs](https://svelte.dev/docs) - Svelte framework - [Svelte 5 Docs](https://svelte.dev/docs) - Svelte framework
### Nostr-Specific ### Nostr-Specific
- [NDK Documentation](https://github.com/nostr-dev-kit/ndk) - Nostr Development Kit
- [NIPs](https://github.com/nostr-protocol/nips) - Nostr Implementation Possibilities - [NDK Documentation](https://github.com/nostr-dev-kit/ndk) - Nostr Development
Kit
- [NIPs](https://github.com/nostr-protocol/nips) - Nostr Implementation
Possibilities
--- ---
@ -911,7 +994,8 @@ Before creating or modifying any UI component:
- [ ] Named component with `A` prefix (e.g., `AComponentName`) - [ ] Named component with `A` prefix (e.g., `AComponentName`)
- [ ] Added complete TSDoc documentation - [ ] Added complete TSDoc documentation
- [ ] Followed TSDoc format exactly as documented - [ ] Followed TSDoc format exactly as documented
- [ ] Included all required tags (@fileoverview, @component, @category, @prop, @example, @features, @accessibility) - [ ] Included all required tags (@fileoverview, @component, @category, @prop,
@example, @features, @accessibility)
- [ ] Added component export to `index.ts` - [ ] Added component export to `index.ts`
- [ ] Ran `node parse-components.js` to update JSON - [ ] Ran `node parse-components.js` to update JSON
- [ ] Tested component in light and dark themes - [ ] Tested component in light and dark themes
@ -922,17 +1006,21 @@ Before creating or modifying any UI component:
## Summary ## Summary
1. **Always search first** - Check `alexandria-components.json` before creating anything 1. **Always search first** - Check `alexandria-components.json` before creating
2. **Use TSDoc religiously** - Every component must have complete TSDoc documentation anything
2. **Use TSDoc religiously** - Every component must have complete TSDoc
documentation
3. **Update the JSON** - Run parser script after any component changes 3. **Update the JSON** - Run parser script after any component changes
4. **Follow naming conventions** - `A` prefix, PascalCase, descriptive names 4. **Follow naming conventions** - `A` prefix, PascalCase, descriptive names
5. **Maintain consistency** - Match existing patterns and style 5. **Maintain consistency** - Match existing patterns and style
7. **Don't reinvent** - Reuse existing components whenever possible 6. **Don't reinvent** - Reuse existing components whenever possible
**Remember:** The goal is to maintain a cohesive, well-documented, and easily discoverable **Remember:** The goal is to maintain a cohesive, well-documented, and easily
component library that serves the entire Alexandria application ecosystem. discoverable component library that serves the entire Alexandria application
ecosystem.
--- ---
**Questions or Issues?** **Questions or Issues?**\
Refer to this guide, review existing components for patterns, or check the main README.md for additional context. Refer to this guide, review existing components for patterns, or check the main
README.md for additional context.

37
src/lib/a/README.md

@ -1,8 +1,12 @@
# Alexandria Component Library # Alexandria Component Library
A comprehensive, project-scoped component library for the Alexandria nostr application. All components are built on Flowbite Svelte and Tailwind CSS, providing consistent theming and accessibility across the application. A comprehensive, project-scoped component library for the Alexandria nostr
application. All components are built on Flowbite Svelte and Tailwind CSS,
providing consistent theming and accessibility across the application.
> **For AI Agents & Detailed Guidelines:** See [AGENTS.md](./AGENTS.md) for complete workflow instructions, styling architecture, and component creation guidelines. > **For AI Agents & Detailed Guidelines:** See [AGENTS.md](./AGENTS.md) for
> complete workflow instructions, styling architecture, and component creation
> guidelines.
## Quick Start ## Quick Start
@ -19,28 +23,38 @@ import { AAlert, AEventPreview, AMarkupForm } from '$lib/a';
## Component Categories ## Component Categories
### 🧱 Primitives (8) ### 🧱 Primitives (8)
Basic building blocks: `AAlert`, `ADetails`, `AInput`, `ANostrBadge`, `ANostrBadgeRow`, `ANostrUser`, `APagination`, `AThemeToggleMini`
Basic building blocks: `AAlert`, `ADetails`, `AInput`, `ANostrBadge`,
`ANostrBadgeRow`, `ANostrUser`, `APagination`, `AThemeToggleMini`
### 🧭 Navigation (2) ### 🧭 Navigation (2)
App navigation: `ANavbar`, `AFooter` App navigation: `ANavbar`, `AFooter`
### 📝 Forms (4) ### 📝 Forms (4)
Input interfaces: `ACommentForm`, `AMarkupForm`, `ASearchForm`, `ATextareaWithPreview`
Input interfaces: `ACommentForm`, `AMarkupForm`, `ASearchForm`,
`ATextareaWithPreview`
### 🃏 Cards (2) ### 🃏 Cards (2)
Content display: `AEventPreview`, `AProfilePreview` Content display: `AEventPreview`, `AProfilePreview`
### 👁 Reader (2) ### 👁 Reader (2)
Technical content controls: `ATechBlock`, `ATechToggle` Technical content controls: `ATechBlock`, `ATechToggle`
## Component Reference ## Component Reference
All components are documented in `alexandria-components.json`. This file contains: All components are documented in `alexandria-components.json`. This file
contains:
- Complete prop definitions with types and defaults - Complete prop definitions with types and defaults
- Usage examples and patterns - Usage examples and patterns
- Features and accessibility information - Features and accessibility information
**View component details:** **View component details:**
```bash ```bash
# Generate/update the component reference # Generate/update the component reference
cd src/lib/a cd src/lib/a
@ -50,6 +64,7 @@ node parse-components.js
## Usage Examples ## Usage Examples
### Display a user profile ### Display a user profile
```svelte ```svelte
<ANostrUser <ANostrUser
{npub} {npub}
@ -61,6 +76,7 @@ node parse-components.js
``` ```
### Show an event card ### Show an event card
```svelte ```svelte
<AEventPreview <AEventPreview
{event} {event}
@ -71,6 +87,7 @@ node parse-components.js
``` ```
### Rich text editor with preview ### Rich text editor with preview
```svelte ```svelte
<ATextareaWithPreview <ATextareaWithPreview
bind:value={content} bind:value={content}
@ -81,6 +98,7 @@ node parse-components.js
``` ```
### Alert notification ### Alert notification
```svelte ```svelte
{#if saveSuccessful} {#if saveSuccessful}
<AAlert color="success" dismissable={true}> <AAlert color="success" dismissable={true}>
@ -92,7 +110,8 @@ node parse-components.js
## Key Features ## Key Features
- ✅ **Consistent theming** - Automatic light/dark mode support - ✅ **Consistent theming** - Automatic light/dark mode support
- ✅ **Accessibility first** - ARIA attributes, keyboard navigation, screen reader friendly - ✅ **Accessibility first** - ARIA attributes, keyboard navigation, screen
reader friendly
- ✅ **TypeScript support** - Full type definitions for all props - ✅ **TypeScript support** - Full type definitions for all props
- ✅ **TSDoc documented** - Machine-readable documentation for AI tools - ✅ **TSDoc documented** - Machine-readable documentation for AI tools
- ✅ **Flexible APIs** - Sensible defaults with extensive customization options - ✅ **Flexible APIs** - Sensible defaults with extensive customization options
@ -100,6 +119,7 @@ node parse-components.js
## Documentation ## Documentation
All components follow TSDoc format with these tags: All components follow TSDoc format with these tags:
- `@fileoverview` - Component description - `@fileoverview` - Component description
- `@category` - Component category - `@category` - Component category
- `@prop` - Property definitions with types - `@prop` - Property definitions with types
@ -107,11 +127,13 @@ All components follow TSDoc format with these tags:
- `@features` - Key functionality - `@features` - Key functionality
- `@accessibility` - Accessibility notes - `@accessibility` - Accessibility notes
The `parse-components.js` script extracts this documentation into `alexandria-components.json` for automated tooling and AI agents. The `parse-components.js` script extracts this documentation into
`alexandria-components.json` for automated tooling and AI agents.
## Contributing ## Contributing
When adding components: When adding components:
1. Follow the `A[ComponentName]` naming convention 1. Follow the `A[ComponentName]` naming convention
2. Add complete TSDoc documentation 2. Add complete TSDoc documentation
3. Place in the appropriate category folder 3. Place in the appropriate category folder
@ -119,6 +141,7 @@ When adding components:
5. Run `node parse-components.js` to update the JSON reference 5. Run `node parse-components.js` to update the JSON reference
**See [AGENTS.md](./AGENTS.md) for detailed guidelines on:** **See [AGENTS.md](./AGENTS.md) for detailed guidelines on:**
- Component creation workflow - Component creation workflow
- Styling architecture (mandatory `/src/styles/a/` folder structure) - Styling architecture (mandatory `/src/styles/a/` folder structure)
- TSDoc documentation standards - TSDoc documentation standards

24
src/lib/a/parse-components.js

@ -1,8 +1,8 @@
#!/usr/bin/env node #!/usr/bin/env node
import fs from 'fs'; import fs from "fs";
import path from 'path'; import path from "path";
import { fileURLToPath } from 'url'; import { fileURLToPath } from "url";
const __filename = fileURLToPath(import.meta.url); const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename); const __dirname = path.dirname(__filename);
@ -240,8 +240,8 @@ class ComponentParser {
default: defaultValue default: defaultValue
? defaultValue.trim() ? defaultValue.trim()
: isOptional : isOptional
? undefined ? undefined
: null, : null,
description: description.trim(), description: description.trim(),
required: !isOptional, required: !isOptional,
}; };
@ -271,7 +271,9 @@ class ComponentParser {
return component; return component;
} catch (error) { } catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error); const errorMessage = error instanceof Error
? error.message
: String(error);
console.error(`Error processing ${filePath}:`, errorMessage); console.error(`Error processing ${filePath}:`, errorMessage);
return null; return null;
} }
@ -329,7 +331,7 @@ function main() {
const parser = new ComponentParser(); const parser = new ComponentParser();
const aFolderPath = __dirname; const aFolderPath = __dirname;
console.log('Parsing Alexandria components...'); console.log("Parsing Alexandria components...");
console.log(`Source directory: ${aFolderPath}`); console.log(`Source directory: ${aFolderPath}`);
if (!fs.existsSync(aFolderPath)) { if (!fs.existsSync(aFolderPath)) {
@ -344,18 +346,18 @@ function main() {
const output = parser.generateOutput(); const output = parser.generateOutput();
// Write to file in the same directory (/a folder) // Write to file in the same directory (/a folder)
const outputPath = path.join(__dirname, 'alexandria-components.json'); const outputPath = path.join(__dirname, "alexandria-components.json");
fs.writeFileSync(outputPath, JSON.stringify(output, null, 2)); fs.writeFileSync(outputPath, JSON.stringify(output, null, 2));
console.log(`\n✅ Successfully parsed ${output.totalComponents} components`); console.log(`\n✅ Successfully parsed ${output.totalComponents} components`);
console.log(`📁 Categories: ${output.categories.join(', ')}`); console.log(`📁 Categories: ${output.categories.join(", ")}`);
console.log(`💾 Output saved to: ${outputPath}`); console.log(`💾 Output saved to: ${outputPath}`);
// Print summary // Print summary
console.log('\n📊 Component Summary:'); console.log("\n📊 Component Summary:");
/** @type {Record<string, number>} */ /** @type {Record<string, number>} */
const categoryCounts = {}; const categoryCounts = {};
output.components.forEach(c => { output.components.forEach((c) => {
categoryCounts[c.category] = (categoryCounts[c.category] || 0) + 1; categoryCounts[c.category] = (categoryCounts[c.category] || 0) + 1;
}); });

64
src/styles/a/cards.css

@ -86,29 +86,25 @@
/* Event preview card hover state */ /* Event preview card hover state */
.event-preview-card { .event-preview-card {
@apply hover:bg-highlight dark:bg-primary-900/70 bg-primary-50 @apply hover:bg-highlight dark:bg-primary-900/70 bg-primary-50
dark:hover:bg-primary-800 border-primary-400 border-s-4 dark:hover:bg-primary-800 border-primary-400 border-s-4 transition-colors
transition-colors cursor-pointer cursor-pointer focus:outline-none focus:ring-2 focus:ring-primary-500
focus:outline-none focus:ring-2 focus:ring-primary-500 shadow-none; shadow-none;
} }
/* Event metadata badges */ /* Event metadata badges */
.event-kind-badge { .event-kind-badge {
@apply text-[10px] px-1.5 py-0.5 rounded @apply text-[10px] px-1.5 py-0.5 rounded bg-gray-200 dark:bg-gray-700
bg-gray-200 dark:bg-gray-700
text-gray-700 dark:text-gray-300; text-gray-700 dark:text-gray-300;
} }
.event-label { .event-label {
@apply text-xs uppercase tracking-wide @apply text-xs uppercase tracking-wide text-gray-500 dark:text-gray-400;
text-gray-500 dark:text-gray-400;
} }
/* Community badge */ /* Community badge */
.community-badge { .community-badge {
@apply inline-flex items-center gap-1 @apply inline-flex items-center gap-1 text-[10px] px-1.5 py-0.5 rounded
text-[10px] px-1.5 py-0.5 rounded bg-yellow-100 dark:bg-yellow-900 text-yellow-700 dark:text-yellow-300;
bg-yellow-100 dark:bg-yellow-900
text-yellow-700 dark:text-yellow-300;
} }
/* ======================================== /* ========================================
@ -117,33 +113,28 @@
/* Profile verification badge (NIP-05) */ /* Profile verification badge (NIP-05) */
.profile-nip05-badge { .profile-nip05-badge {
@apply px-2 py-0.5 !mb-0 rounded @apply px-2 py-0.5 !mb-0 rounded bg-green-100 dark:bg-green-900
bg-green-100 dark:bg-green-900
text-green-700 dark:text-green-300 text-xs; text-green-700 dark:text-green-300 text-xs;
} }
/* Community status indicator */ /* Community status indicator */
.community-status-indicator { .community-status-indicator {
@apply flex-shrink-0 w-4 h-4 @apply flex-shrink-0 w-4 h-4 bg-yellow-100 dark:bg-yellow-900 rounded-full
bg-yellow-100 dark:bg-yellow-900 flex items-center justify-center;
rounded-full flex items-center justify-center;
} }
.community-status-icon { .community-status-icon {
@apply w-3 h-3 @apply w-3 h-3 text-yellow-600 dark:text-yellow-400;
text-yellow-600 dark:text-yellow-400;
} }
/* User list status indicator (heart) */ /* User list status indicator (heart) */
.user-list-indicator { .user-list-indicator {
@apply flex-shrink-0 w-4 h-4 @apply flex-shrink-0 w-4 h-4 bg-red-100 dark:bg-red-900 rounded-full flex
bg-red-100 dark:bg-red-900 items-center justify-center;
rounded-full flex items-center justify-center;
} }
.user-list-icon { .user-list-icon {
@apply w-3 h-3 @apply w-3 h-3 text-red-600 dark:text-red-400;
text-red-600 dark:text-red-400;
} }
/* ======================================== /* ========================================
@ -160,8 +151,7 @@
} }
.card-footer { .card-footer {
@apply px-4 pt-2 pb-3 @apply px-4 pt-2 pb-3 border-t border-primary-200 dark:border-primary-700
border-t border-primary-200 dark:border-primary-700
flex items-center gap-2 flex-wrap; flex items-center gap-2 flex-wrap;
} }
@ -171,8 +161,8 @@
} }
.card-content { .card-content {
@apply text-sm text-gray-800 dark:text-gray-200 @apply text-sm text-gray-800 dark:text-gray-200 line-clamp-3 break-words
line-clamp-3 break-words mb-4; mb-4;
} }
.card-about { .card-about {
@ -181,10 +171,9 @@
/* Deferral link styling */ /* Deferral link styling */
.deferral-link { .deferral-link {
@apply underline @apply underline text-primary-700 dark:text-primary-400
text-primary-700 dark:text-primary-400 hover:text-primary-600 dark:hover:text-primary-400 break-all
hover:text-primary-600 dark:hover:text-primary-400 cursor-pointer;
break-all cursor-pointer;
} }
/* ======================================== /* ========================================
@ -192,10 +181,8 @@
======================================== */ ======================================== */
.tags span { .tags span {
@apply bg-primary-50 text-primary-800 @apply bg-primary-50 text-primary-800 text-sm font-medium me-2 px-2.5 py-0.5
text-sm font-medium me-2 px-2.5 py-0.5 rounded-sm dark:bg-primary-900 dark:text-primary-200;
rounded-sm
dark:bg-primary-900 dark:text-primary-200;
} }
/* ======================================== /* ========================================
@ -220,8 +207,7 @@
/* Prose styling within cards - extends prose class when applied */ /* Prose styling within cards - extends prose class when applied */
.card-prose { .card-prose {
@apply max-w-none text-gray-900 dark:text-gray-100 @apply max-w-none text-gray-900 dark:text-gray-100 break-words min-w-0;
break-words min-w-0;
overflow-wrap: anywhere; overflow-wrap: anywhere;
} }
@ -244,8 +230,8 @@
/* Clickable card states */ /* Clickable card states */
.card-clickable { .card-clickable {
@apply cursor-pointer transition-colors @apply cursor-pointer transition-colors focus:outline-none focus:ring-2
focus:outline-none focus:ring-2 focus:ring-primary-500; focus:ring-primary-500;
} }
.card-clickable:hover { .card-clickable:hover {

6
src/styles/a/forms.css

@ -1,5 +1,5 @@
@layer components { @layer components {
/* ======================================== /* ========================================
Base Form Styles Base Form Styles
======================================== */ ======================================== */
} }
Loading…
Cancel
Save