@crossworks/share-ui 0.230.43

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/LICENSE.md +135 -0
  2. package/package.json +68 -0
  3. package/src/app-bridge-protocol.ts +115 -0
  4. package/src/app-presenter.tsx +25 -0
  5. package/src/app-sandbox.tsx +552 -0
  6. package/src/appearance.ts +192 -0
  7. package/src/avatar.test.ts +229 -0
  8. package/src/avatar.ts +731 -0
  9. package/src/backgrounds.test.ts +119 -0
  10. package/src/backgrounds.ts +118 -0
  11. package/src/draw-presenter.tsx +39 -0
  12. package/src/event-presenter.tsx +62 -0
  13. package/src/file-presenter.tsx +76 -0
  14. package/src/formula-calculator.tsx +209 -0
  15. package/src/formula-presenter.test.ts +128 -0
  16. package/src/formula-presenter.tsx +301 -0
  17. package/src/help-topics.ts +104 -0
  18. package/src/lib/ink-audit.test.ts +314 -0
  19. package/src/lib/theme-css-blocks.ts +26 -0
  20. package/src/lib/theme-generator.test.ts +179 -0
  21. package/src/lib/theme-registry.gen.ts +352 -0
  22. package/src/lib/themes.test.ts +308 -0
  23. package/src/lib/themes.ts +75 -0
  24. package/src/lib/utils.ts +6 -0
  25. package/src/nav-items.ts +225 -0
  26. package/src/note-presenter.tsx +14 -0
  27. package/src/page-outline.tsx +127 -0
  28. package/src/table-presenter.tsx +226 -0
  29. package/src/task-presenter.tsx +60 -0
  30. package/src/ui/button.tsx +50 -0
  31. package/src/ui/input.tsx +18 -0
  32. package/src/ui/label.tsx +20 -0
  33. package/src/view-payload.ts +82 -0
  34. package/styles/app.css +1098 -0
  35. package/styles/themes.css +6198 -0
  36. package/themes/generate.d.mts +11 -0
  37. package/themes/generate.mjs +618 -0
  38. package/themes/model.d.mts +24 -0
  39. package/themes/model.mjs +213 -0
  40. package/themes/preview.html +145 -0
  41. package/themes/seeds.d.mts +16 -0
  42. package/themes/seeds.mjs +3694 -0
  43. package/tsconfig.json +15 -0
  44. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,60 @@
1
+ import ReactMarkdown from 'react-markdown';
2
+ import remarkGfm from 'remark-gfm';
3
+ import { Check, Circle } from 'lucide-react';
4
+ import { formatDateTime } from '@mantle/client-types/lib/format-datetime';
5
+
6
+ /** Public task render — a clean status card. */
7
+ export function TaskPresenter({
8
+ view,
9
+ }: {
10
+ view: { title: string; body: string; status: string; priority: string; dueAt: string | null };
11
+ }) {
12
+ const done = view.status === 'done';
13
+ return (
14
+ <div className="mx-auto max-w-2xl px-6 py-12 md:py-16">
15
+ <div className="rounded-xl border border-border bg-card p-6">
16
+ <div className="flex items-start gap-3">
17
+ <span
18
+ className={
19
+ 'mt-0.5 flex size-6 shrink-0 items-center justify-center rounded-full border ' +
20
+ (done
21
+ ? 'border-primary bg-primary text-primary-foreground'
22
+ : 'border-border text-transparent')
23
+ }
24
+ aria-hidden
25
+ >
26
+ {done ? <Check className="size-4" /> : <Circle className="size-3" />}
27
+ </span>
28
+ <div className="min-w-0 flex-1">
29
+ <h1
30
+ className={
31
+ 'text-2xl font-bold tracking-tight ' +
32
+ (done ? 'text-muted-foreground line-through' : '')
33
+ }
34
+ >
35
+ {view.title}
36
+ </h1>
37
+ <div className="mt-3 flex flex-wrap items-center gap-2 text-xs">
38
+ <span className="rounded-full border border-border px-2 py-0.5 capitalize text-muted-foreground">
39
+ {view.status}
40
+ </span>
41
+ <span className="rounded-full border border-border px-2 py-0.5 capitalize text-muted-foreground">
42
+ {view.priority} priority
43
+ </span>
44
+ {view.dueAt && (
45
+ <span className="rounded-full border border-border px-2 py-0.5 text-muted-foreground">
46
+ Due {formatDateTime(view.dueAt)}
47
+ </span>
48
+ )}
49
+ </div>
50
+ </div>
51
+ </div>
52
+ {view.body && (
53
+ <div className="prose prose-sm dark:prose-invert mt-5 max-w-none border-t border-border pt-5">
54
+ <ReactMarkdown remarkPlugins={[remarkGfm]}>{view.body}</ReactMarkdown>
55
+ </div>
56
+ )}
57
+ </div>
58
+ </div>
59
+ );
60
+ }
@@ -0,0 +1,50 @@
1
+ import * as React from 'react';
2
+ import { Slot } from '@radix-ui/react-slot';
3
+ import { cva, type VariantProps } from 'class-variance-authority';
4
+ import { cn } from '../lib/utils';
5
+
6
+ const buttonVariants = cva(
7
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default: 'bg-primary text-primary-foreground hover:bg-primary/90',
12
+ secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
13
+ outline: 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
14
+ ghost: 'hover:bg-accent hover:text-accent-foreground',
15
+ destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
16
+ link: 'text-primary-ink underline-offset-4 hover:underline',
17
+ // Semantic approve/deny aliases — used by sender curation buttons.
18
+ // Theme-aware: approve uses the primary action colour, deny the
19
+ // destructive colour, so they recolour with the active theme instead
20
+ // of forcing green/red.
21
+ approve: 'bg-primary text-primary-foreground hover:bg-primary/90',
22
+ deny: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
23
+ },
24
+ size: {
25
+ default: 'h-10 px-4 py-2',
26
+ sm: 'h-9 rounded-md px-3',
27
+ lg: 'h-11 rounded-md px-8',
28
+ icon: 'h-10 w-10',
29
+ },
30
+ },
31
+ defaultVariants: { variant: 'default', size: 'default' },
32
+ },
33
+ );
34
+
35
+ export interface ButtonProps
36
+ extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {
37
+ asChild?: boolean;
38
+ }
39
+
40
+ export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
41
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
42
+ const Comp = asChild ? Slot : 'button';
43
+ return (
44
+ <Comp ref={ref} className={cn(buttonVariants({ variant, size, className }))} {...props} />
45
+ );
46
+ },
47
+ );
48
+ Button.displayName = 'Button';
49
+
50
+ export { buttonVariants };
@@ -0,0 +1,18 @@
1
+ import * as React from 'react';
2
+ import { cn } from '../lib/utils';
3
+
4
+ export const Input = React.forwardRef<
5
+ HTMLInputElement,
6
+ React.InputHTMLAttributes<HTMLInputElement>
7
+ >(({ className, type, ...props }, ref) => (
8
+ <input
9
+ type={type}
10
+ ref={ref}
11
+ className={cn(
12
+ 'flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
13
+ className,
14
+ )}
15
+ {...props}
16
+ />
17
+ ));
18
+ Input.displayName = 'Input';
@@ -0,0 +1,20 @@
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ import * as LabelPrimitive from '@radix-ui/react-label';
5
+ import { cn } from '../lib/utils';
6
+
7
+ export const Label = React.forwardRef<
8
+ React.ElementRef<typeof LabelPrimitive.Root>,
9
+ React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
10
+ >(({ className, ...props }, ref) => (
11
+ <LabelPrimitive.Root
12
+ ref={ref}
13
+ className={cn(
14
+ 'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70',
15
+ className,
16
+ )}
17
+ {...props}
18
+ />
19
+ ));
20
+ Label.displayName = 'Label';
@@ -0,0 +1,82 @@
1
+ /**
2
+ * The JSON payload of GET /s/<token>/view — the share content door for the
3
+ * /team inline reader (no iframe). One shape per share kind, mirroring the
4
+ * server's ShareView except where the server must pre-compute:
5
+ *
6
+ * - `page` ships pre-rendered SANITIZED html + toc (renderPageDoc runs
7
+ * server-side only, so escaping stays on the server and katex/lowlight
8
+ * stay out of the client bundle);
9
+ * - `folder` ships the listing for the requested `?p=` sub-path (the
10
+ * listing is a DB read, re-validated against the shared root per request).
11
+ *
12
+ * Everything else is the presenter's own props, verbatim. Consumed by the
13
+ * client ShareReader and produced by server/web/app/s/[token]/view.
14
+ */
15
+ import type { TocEntry } from '@mantle/content-core/page-toc';
16
+ import type { CoverageGap, FormulaSpec } from '@mantle/content-core/formula-spec';
17
+ import type { TargetSignature } from '@mantle/content-core/formula-signature';
18
+ import type { DimensionIssue } from '@mantle/content-core/formula-dimensions';
19
+ import type { Column, Row } from '@mantle/content-core/table-model';
20
+
21
+ export type ShareFolderListing = {
22
+ /** ltree path currently being listed (the shared root or a descendant). */
23
+ currentPath: string;
24
+ folders: Array<{ id: string; path: string; slug: string; fileCount: number }>;
25
+ files: Array<{ id: string; filename: string; mimeType: string; sizeBytes: number }>;
26
+ };
27
+
28
+ export type ShareViewPayload =
29
+ | {
30
+ kind: 'page';
31
+ title: string;
32
+ icon: string | null;
33
+ width: 'narrow' | 'wide';
34
+ /** Sanitized HTML from renderPageDoc — safe to inject as-is. */
35
+ html: string;
36
+ toc: TocEntry[];
37
+ }
38
+ | { kind: 'note'; title: string; content: string }
39
+ | {
40
+ kind: 'task';
41
+ title: string;
42
+ body: string;
43
+ status: string;
44
+ priority: string;
45
+ dueAt: string | null;
46
+ }
47
+ | {
48
+ kind: 'event';
49
+ title: string;
50
+ body: string;
51
+ startsAt: string | null;
52
+ endsAt: string | null;
53
+ location: string | null;
54
+ }
55
+ | { kind: 'file'; fileId: string; filename: string; mimeType: string; size: number }
56
+ | { kind: 'app'; appId: string; title: string }
57
+ | {
58
+ kind: 'table';
59
+ tableId: string;
60
+ title: string;
61
+ icon: string | null;
62
+ tabs: Array<{
63
+ id: string;
64
+ name: string;
65
+ rowCount: number;
66
+ columns: Array<{ id: string; name: string; type: string }>;
67
+ }> | null;
68
+ legacyDoc: { columns: Column[]; rows: Row[] } | null;
69
+ }
70
+ | {
71
+ kind: 'formula';
72
+ title: string;
73
+ spec: FormulaSpec;
74
+ signature: TargetSignature[];
75
+ coverageGaps: CoverageGap[];
76
+ dimensionIssues: DimensionIssue[];
77
+ }
78
+ | { kind: 'folder'; title: string; path: string; listing: ShareFolderListing }
79
+ // Only WHETHER a committed snapshot exists. The bytes are fetched as an
80
+ // image from /s/<token>/draw, so they never ride in this payload and never
81
+ // become markup on the page.
82
+ | { kind: 'draw'; title: string; hasSvg: boolean };