mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-29 16:57:42 +02:00
feat: major update with network graphs, themes, todo extensions, and more
## New Features ### Network Graph Visualization (Contacts, Calendar, Todo) - D3.js force simulation for physics-based layout - Zoom & pan with mouse/touchpad - Keyboard shortcuts: +/- zoom, 0 reset, Esc deselect, / search, F focus - Filtering by tags, company/location/project, connection strength - Shared components in @manacore/shared-ui ### Central Tags API (mana-core-auth) - CRUD endpoints for tags - Schema: tags table with userId, name, color, app - Shared tag components in @manacore/shared-ui ### Custom Themes System - Theme editor with live preview and color picker - Community theme gallery - Theme sharing (public, unlisted, private) - Backend API in mana-core-auth ### Todo App Extensions - Glass-pill design for task input and items - Settings page with 20+ preferences - Task edit modal with inline editing - Statistics page with visualizations - PWA support with offline capabilities - Multiple kanban boards ### Contacts App Features - Duplicate detection - Photo upload - Batch operations - Enhanced favorites page with multiple view modes - Alphabet view improvements - Search modal ### Help System - @manacore/shared-help-content - @manacore/shared-help-ui - @manacore/shared-help-types ### Other Features - Themes page for all apps - Referral system frontend - CommandBar (global search) - Skeleton loaders - Settings page improvements ## Bug Fixes - Network graph simulation initialization - Database schema TEXT for user_id columns (Better Auth compatibility) - Various styling fixes ## Documentation - Daily report for 2025-12-10 - CI/CD deployment guide 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
e84371aa94
commit
ee42b6cc76
381 changed files with 39284 additions and 6275 deletions
|
|
@ -0,0 +1,24 @@
|
|||
<script lang="ts">
|
||||
/**
|
||||
* SkeletonAvatar - Circular skeleton for profile pictures/avatars
|
||||
*
|
||||
* @example
|
||||
* ```svelte
|
||||
* <SkeletonAvatar size="40px" />
|
||||
* <SkeletonAvatar size="64px" />
|
||||
* ```
|
||||
*/
|
||||
|
||||
import SkeletonBox from './SkeletonBox.svelte';
|
||||
|
||||
interface Props {
|
||||
/** Size of the avatar (width & height) */
|
||||
size?: string;
|
||||
/** Additional CSS classes */
|
||||
class?: string;
|
||||
}
|
||||
|
||||
let { size = '40px', class: className = '' }: Props = $props();
|
||||
</script>
|
||||
|
||||
<SkeletonBox width={size} height={size} circle class={className} />
|
||||
69
packages/shared-ui/src/molecules/loaders/SkeletonCard.svelte
Normal file
69
packages/shared-ui/src/molecules/loaders/SkeletonCard.svelte
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
<script lang="ts">
|
||||
/**
|
||||
* SkeletonCard - Configurable card skeleton with avatar, title, body, footer
|
||||
*
|
||||
* @example
|
||||
* ```svelte
|
||||
* <SkeletonCard showAvatar titleLines={1} bodyLines={2} />
|
||||
* <SkeletonCard showFooter />
|
||||
* ```
|
||||
*/
|
||||
|
||||
import SkeletonBox from './SkeletonBox.svelte';
|
||||
import SkeletonText from './SkeletonText.svelte';
|
||||
import SkeletonAvatar from './SkeletonAvatar.svelte';
|
||||
|
||||
interface Props {
|
||||
/** Show avatar/image placeholder */
|
||||
showAvatar?: boolean;
|
||||
/** Avatar size */
|
||||
avatarSize?: string;
|
||||
/** Number of title lines */
|
||||
titleLines?: number;
|
||||
/** Number of body text lines */
|
||||
bodyLines?: number;
|
||||
/** Show footer section */
|
||||
showFooter?: boolean;
|
||||
/** Opacity for fade effect in lists */
|
||||
opacity?: number;
|
||||
/** Additional CSS classes */
|
||||
class?: string;
|
||||
}
|
||||
|
||||
let {
|
||||
showAvatar = false,
|
||||
avatarSize = '48px',
|
||||
titleLines = 1,
|
||||
bodyLines = 2,
|
||||
showFooter = false,
|
||||
opacity = 1,
|
||||
class: className = '',
|
||||
}: Props = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="skeleton-card rounded-lg border border-border bg-card p-4 {className}"
|
||||
style="opacity: {opacity};"
|
||||
>
|
||||
<div class="flex gap-3">
|
||||
{#if showAvatar}
|
||||
<SkeletonAvatar size={avatarSize} />
|
||||
{/if}
|
||||
<div class="flex-1 min-w-0">
|
||||
{#if titleLines > 0}
|
||||
<SkeletonText lines={titleLines} lineHeight="18px" gap="6px" lastLineWidth="60%" />
|
||||
{/if}
|
||||
{#if bodyLines > 0}
|
||||
<div class="mt-2">
|
||||
<SkeletonText lines={bodyLines} lineHeight="14px" gap="6px" lastLineWidth="80%" />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{#if showFooter}
|
||||
<div class="mt-4 flex items-center justify-between border-t border-border pt-4">
|
||||
<SkeletonBox width="80px" height="14px" />
|
||||
<SkeletonBox width="60px" height="14px" />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
61
packages/shared-ui/src/molecules/loaders/SkeletonGrid.svelte
Normal file
61
packages/shared-ui/src/molecules/loaders/SkeletonGrid.svelte
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
<script lang="ts">
|
||||
/**
|
||||
* SkeletonGrid - Grid of skeleton cards with fade effect
|
||||
*
|
||||
* @example
|
||||
* ```svelte
|
||||
* <SkeletonGrid count={6} columns={3} />
|
||||
* <SkeletonGrid count={8} columns={4} showAvatar />
|
||||
* ```
|
||||
*/
|
||||
|
||||
import SkeletonCard from './SkeletonCard.svelte';
|
||||
|
||||
interface Props {
|
||||
/** Number of cards to show */
|
||||
count?: number;
|
||||
/** Number of columns (CSS grid) */
|
||||
columns?: number;
|
||||
/** Show avatar in cards */
|
||||
showAvatar?: boolean;
|
||||
/** Avatar size */
|
||||
avatarSize?: string;
|
||||
/** Number of body lines per card */
|
||||
bodyLines?: number;
|
||||
/** Apply cascading fade effect */
|
||||
fadeEffect?: boolean;
|
||||
/** Minimum opacity for fade effect */
|
||||
minOpacity?: number;
|
||||
/** Gap between cards */
|
||||
gap?: string;
|
||||
/** Additional CSS classes */
|
||||
class?: string;
|
||||
}
|
||||
|
||||
let {
|
||||
count = 6,
|
||||
columns = 3,
|
||||
showAvatar = true,
|
||||
avatarSize = '48px',
|
||||
bodyLines = 2,
|
||||
fadeEffect = true,
|
||||
minOpacity = 0.4,
|
||||
gap = '1rem',
|
||||
class: className = '',
|
||||
}: Props = $props();
|
||||
|
||||
function calculateOpacity(index: number): number {
|
||||
if (!fadeEffect) return 1;
|
||||
const fadeStep = (1 - minOpacity) / Math.max(count - 1, 1);
|
||||
return Math.max(minOpacity, 1 - index * fadeStep);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="skeleton-grid grid {className}"
|
||||
style="grid-template-columns: repeat({columns}, minmax(0, 1fr)); gap: {gap};"
|
||||
>
|
||||
{#each Array(count) as _, i}
|
||||
<SkeletonCard {showAvatar} {avatarSize} {bodyLines} opacity={calculateOpacity(i)} />
|
||||
{/each}
|
||||
</div>
|
||||
52
packages/shared-ui/src/molecules/loaders/SkeletonList.svelte
Normal file
52
packages/shared-ui/src/molecules/loaders/SkeletonList.svelte
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
<script lang="ts">
|
||||
/**
|
||||
* SkeletonList - List of skeleton rows with cascading fade effect
|
||||
*
|
||||
* @example
|
||||
* ```svelte
|
||||
* <SkeletonList count={5} />
|
||||
* <SkeletonList count={10} showAvatar fadeEffect />
|
||||
* ```
|
||||
*/
|
||||
|
||||
import SkeletonRow from './SkeletonRow.svelte';
|
||||
|
||||
interface Props {
|
||||
/** Number of rows to show */
|
||||
count?: number;
|
||||
/** Show avatar in each row */
|
||||
showAvatar?: boolean;
|
||||
/** Avatar size */
|
||||
avatarSize?: string;
|
||||
/** Apply cascading fade effect (rows fade out towards bottom) */
|
||||
fadeEffect?: boolean;
|
||||
/** Minimum opacity for fade effect */
|
||||
minOpacity?: number;
|
||||
/** Gap between rows */
|
||||
gap?: string;
|
||||
/** Additional CSS classes */
|
||||
class?: string;
|
||||
}
|
||||
|
||||
let {
|
||||
count = 5,
|
||||
showAvatar = true,
|
||||
avatarSize = '40px',
|
||||
fadeEffect = true,
|
||||
minOpacity = 0.3,
|
||||
gap = '0',
|
||||
class: className = '',
|
||||
}: Props = $props();
|
||||
|
||||
function calculateOpacity(index: number): number {
|
||||
if (!fadeEffect) return 1;
|
||||
const fadeStep = (1 - minOpacity) / Math.max(count - 1, 1);
|
||||
return Math.max(minOpacity, 1 - index * fadeStep);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="skeleton-list flex flex-col {className}" style="gap: {gap};">
|
||||
{#each Array(count) as _, i}
|
||||
<SkeletonRow {showAvatar} {avatarSize} opacity={calculateOpacity(i)} />
|
||||
{/each}
|
||||
</div>
|
||||
60
packages/shared-ui/src/molecules/loaders/SkeletonRow.svelte
Normal file
60
packages/shared-ui/src/molecules/loaders/SkeletonRow.svelte
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
<script lang="ts">
|
||||
/**
|
||||
* SkeletonRow - Single row skeleton with avatar and text
|
||||
*
|
||||
* @example
|
||||
* ```svelte
|
||||
* <SkeletonRow showAvatar />
|
||||
* <SkeletonRow opacity={0.5} />
|
||||
* ```
|
||||
*/
|
||||
|
||||
import SkeletonBox from './SkeletonBox.svelte';
|
||||
import SkeletonAvatar from './SkeletonAvatar.svelte';
|
||||
|
||||
interface Props {
|
||||
/** Show avatar placeholder */
|
||||
showAvatar?: boolean;
|
||||
/** Avatar size */
|
||||
avatarSize?: string;
|
||||
/** Opacity for fade effect */
|
||||
opacity?: number;
|
||||
/** Show secondary line */
|
||||
showSecondaryLine?: boolean;
|
||||
/** Show right-side content */
|
||||
showRightContent?: boolean;
|
||||
/** Additional CSS classes */
|
||||
class?: string;
|
||||
}
|
||||
|
||||
let {
|
||||
showAvatar = true,
|
||||
avatarSize = '40px',
|
||||
opacity = 1,
|
||||
showSecondaryLine = true,
|
||||
showRightContent = true,
|
||||
class: className = '',
|
||||
}: Props = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="skeleton-row flex items-center gap-3 border-b border-border px-4 py-3 {className}"
|
||||
style="opacity: {opacity};"
|
||||
role="status"
|
||||
aria-label="Loading"
|
||||
>
|
||||
{#if showAvatar}
|
||||
<SkeletonAvatar size={avatarSize} />
|
||||
{/if}
|
||||
<div class="flex-1 min-w-0">
|
||||
<SkeletonBox width="45%" height="16px" />
|
||||
{#if showSecondaryLine}
|
||||
<div class="mt-1.5">
|
||||
<SkeletonBox width="65%" height="13px" />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{#if showRightContent}
|
||||
<SkeletonBox width="70px" height="13px" />
|
||||
{/if}
|
||||
</div>
|
||||
|
|
@ -1,5 +1,25 @@
|
|||
/**
|
||||
* Loading state components
|
||||
*
|
||||
* Primitives:
|
||||
* - SkeletonBox: Base rectangular skeleton with shimmer
|
||||
* - SkeletonText: Multi-line text skeleton
|
||||
* - SkeletonAvatar: Circular avatar skeleton
|
||||
*
|
||||
* Composites:
|
||||
* - SkeletonRow: Single list row with avatar + text
|
||||
* - SkeletonList: Multiple rows with fade effect
|
||||
* - SkeletonCard: Card with avatar, title, body, footer
|
||||
* - SkeletonGrid: Grid of cards with fade effect
|
||||
*/
|
||||
|
||||
// Primitives
|
||||
export { default as SkeletonBox } from './SkeletonBox.svelte';
|
||||
export { default as SkeletonText } from './SkeletonText.svelte';
|
||||
export { default as SkeletonAvatar } from './SkeletonAvatar.svelte';
|
||||
|
||||
// Composites
|
||||
export { default as SkeletonRow } from './SkeletonRow.svelte';
|
||||
export { default as SkeletonList } from './SkeletonList.svelte';
|
||||
export { default as SkeletonCard } from './SkeletonCard.svelte';
|
||||
export { default as SkeletonGrid } from './SkeletonGrid.svelte';
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue