@dreamtree-org/landing-kit 0.2.1 → 0.5.0

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.
package/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { ComponentType, CSSProperties, ReactElement, ReactNode } from 'react';
2
2
 
3
- /** Products with a built-in brand entry. */
3
+ /** @deprecated Products with a built-in brand entry. Keep the brand in dreamtree.brand.json instead (removed in 0.5.0). */
4
4
  export type BrandId =
5
5
  | 'billpro'
6
6
  | 'taskpro'
@@ -51,13 +51,86 @@ export interface AuthShellProps {
51
51
  /** react-router `Link` or next/link; receives both `to` and `href`. */
52
52
  linkComponent?: ComponentType<any> | undefined;
53
53
  className?: string | undefined;
54
+ /**
55
+ * Hidden demo-accounts panel: tapping the heading `taps` (default 4) times reveals a row of
56
+ * role chips under the form. `load` runs on reveal (use `fetchDemoAccounts`); `onSelect` fills
57
+ * the form. Nothing renders when `load` returns no accounts (demo mode off).
58
+ */
59
+ demoAccounts?: AuthDemoAccounts | undefined;
60
+ }
61
+
62
+ export interface DemoAccount {
63
+ /** Chip text, e.g. "Admin", "Manager". */
64
+ label: string;
65
+ /** Email, username or mobile the login form takes. */
66
+ identifier: string;
67
+ password: string;
68
+ /** Company / workspace / shop slug, for logins scoped to one. */
69
+ company?: string | undefined;
70
+ role?: string | undefined;
71
+ }
72
+
73
+ export interface DemoAccountsResponse {
74
+ accounts: DemoAccount[];
75
+ /** Shown under the chips; derived when every account has the same password. */
76
+ sharedPassword?: string | undefined;
77
+ }
78
+
79
+ export interface AuthDemoAccounts {
80
+ load: () => Promise<DemoAccountsResponse | DemoAccount[]>;
81
+ onSelect: (account: DemoAccount) => void;
82
+ taps?: number | undefined;
83
+ }
84
+
85
+ export interface DemoAccountsProps {
86
+ load: AuthDemoAccounts['load'];
87
+ onSelect?: AuthDemoAccounts['onSelect'] | undefined;
88
+ className?: string | undefined;
54
89
  }
55
90
 
56
91
  export declare function AuthShell(props: AuthShellProps): ReactElement;
92
+ /** The chip row AuthShell reveals; render it yourself for a login outside AuthShell. */
93
+ export declare function DemoAccounts(props: DemoAccountsProps): ReactElement | null;
94
+ /** `taps` clicks, each within `windowMs` of the last, set `revealed` for the page's life. */
95
+ export declare function useTapReveal(opts?: { taps?: number; windowMs?: number }): {
96
+ revealed: boolean;
97
+ tapProps: { onClick: () => void; style: CSSProperties };
98
+ };
99
+ export declare const DEMO_ACCOUNTS_URL: '/api/auth/demo-accounts';
100
+ /** GET the product's demo accounts; 404 (demo mode off) or any error resolves to `{ accounts: [] }`. */
101
+ export declare function fetchDemoAccounts(url?: string, init?: RequestInit): Promise<DemoAccountsResponse>;
102
+ export declare function normalizeDemoAccounts(data: unknown): DemoAccountsResponse;
103
+ /** Company-first logins: keep the picked account (sessionStorage) for the password step. */
104
+ export declare function stashDemoFill(account: DemoAccount): void;
105
+ /** Read and clear what `stashDemoFill` kept; null when nothing was picked. */
106
+ export declare function takeDemoFill(): { identifier: string; password: string; company?: string } | null;
57
107
  export declare function brandStyle(brand: Brand): CSSProperties;
108
+ /** @deprecated Keep the brand in dreamtree.brand.json and use toLandingBrand() (removed in 0.5.0). */
58
109
  export declare const brands: Record<string, Brand>;
110
+ /** @deprecated Keep the brand in dreamtree.brand.json and use toLandingBrand() (removed in 0.5.0). */
59
111
  export declare function getBrand(id: BrandId | (string & {})): Brand;
60
112
 
113
+ /** The parts of a product's dreamtree.brand.json (twreact-ui's brand format) that landing-kit reads. */
114
+ export interface BrandFile {
115
+ id?: string;
116
+ name?: string;
117
+ label?: string;
118
+ logo?: { mark?: string; wordmark?: string; [key: string]: string | undefined };
119
+ color?: { primary?: Partial<Record<'50' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900' | '950', string>> };
120
+ role?: { shell?: string | { light?: string; dark?: string } };
121
+ marketing?: { primary?: string; accent?: string; panel?: string };
122
+ }
123
+
124
+ /** The product's content for the brand panel and landing hero (not part of the brand file). */
125
+ export type BrandCopy = Partial<Pick<Brand, 'headline' | 'pitch' | 'points' | 'category' | 'platformHeadline' | 'platformPitch' | 'mark' | 'logoSrc'>>;
126
+
127
+ /**
128
+ * Map a product's dreamtree.brand.json to the Brand AuthShell, MarketingLayout and LandingPage take.
129
+ * primary = marketing.primary ?? color.primary.600; accent = marketing.accent ?? color.primary.300;
130
+ * panel = marketing.panel ?? role.shell; logoSrc = logo.mark.
131
+ */
132
+ export declare function toLandingBrand(file: BrandFile, copy?: BrandCopy): Brand;
133
+
61
134
  /** A lucide-react style component (`Receipt`) or an element (`<Receipt />`). */
62
135
  export type Icon = ComponentType<any> | ReactElement;
63
136
  export interface NavLink { label: string; href: string; reload?: boolean }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dreamtree-org/landing-kit",
3
- "version": "0.2.1",
3
+ "version": "0.5.0",
4
4
  "description": "The shared marketing site and auth frame (AuthShell + brands) for every Dreamtree product: content-driven landing pages, login/signup shell, Express routes, prerender and SEO files.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -36,6 +36,11 @@
36
36
  "import": "./dist/seo.js",
37
37
  "require": "./dist/seo.cjs"
38
38
  },
39
+ "./demo-server": {
40
+ "types": "./demo-server.d.ts",
41
+ "import": "./dist/demo-server.js",
42
+ "require": "./dist/demo-server.cjs"
43
+ },
39
44
  "./enhance": {
40
45
  "types": "./enhance.d.ts",
41
46
  "import": "./dist/enhance.js",
@@ -55,6 +60,7 @@
55
60
  "auth.d.ts",
56
61
  "server.d.ts",
57
62
  "seo.d.ts",
63
+ "demo-server.d.ts",
58
64
  "enhance.d.ts",
59
65
  "README.md",
60
66
  "MIGRATION.md",
package/styles.css CHANGED
@@ -808,6 +808,48 @@ html:has(.dt-landing) { scroll-behavior: smooth; scroll-padding-top: calc(var(--
808
808
  .dt-auth-legal-link { color: inherit; text-decoration: none; }
809
809
  .dt-auth-legal-link:hover { color: var(--dt-brand); text-decoration: underline; }
810
810
 
811
+ /* ---- hidden demo-accounts panel (revealed by tapping the heading) ---- */
812
+ .dt-auth-demo { margin-top: 1.5rem; padding-top: 1rem; border-top: 1px solid var(--dt-auth-border); }
813
+ .dt-auth-demo-label {
814
+ margin: 0 0 0.625rem;
815
+ font-size: 0.6875rem;
816
+ font-weight: 500;
817
+ letter-spacing: 0.14em;
818
+ text-transform: uppercase;
819
+ color: var(--dt-auth-muted);
820
+ }
821
+ .dt-auth-demo-chips { display: flex; flex-wrap: wrap; gap: 0.375rem; }
822
+ .dt-auth-demo-chip {
823
+ appearance: none;
824
+ margin: 0;
825
+ padding: 0.3125rem 0.625rem;
826
+ min-height: 1.75rem;
827
+ border: 1px solid color-mix(in oklab, var(--dt-brand) 45%, transparent);
828
+ border-radius: 999px;
829
+ background: color-mix(in oklab, var(--dt-brand) 8%, transparent);
830
+ color: color-mix(in oklab, var(--dt-brand) 80%, var(--dt-auth-text));
831
+ font: inherit;
832
+ font-size: 0.8125rem;
833
+ font-weight: 500;
834
+ line-height: 1.2;
835
+ cursor: pointer;
836
+ transition: background-color 120ms, border-color 120ms;
837
+ }
838
+ .dt-auth-demo-chip:hover { background: color-mix(in oklab, var(--dt-brand) 16%, transparent); }
839
+ .dt-auth-demo-chip:focus-visible { outline: 2px solid var(--dt-brand); outline-offset: 2px; }
840
+ .dt-auth-demo-chip[aria-pressed='true'] { background: var(--dt-brand); border-color: var(--dt-brand); color: #fff; }
841
+ .dt-auth-demo-chip--skeleton {
842
+ width: 4.5rem;
843
+ border-color: var(--dt-auth-border);
844
+ background: var(--dt-auth-border);
845
+ cursor: default;
846
+ animation: dt-auth-demo-pulse 1.2s ease-in-out infinite;
847
+ }
848
+ @keyframes dt-auth-demo-pulse { 50% { opacity: 0.5; } }
849
+ @media (prefers-reduced-motion: reduce) { .dt-auth-demo-chip--skeleton { animation: none; } }
850
+ .dt-auth-demo-note { margin: 0.625rem 0 0; font-size: 0.75rem; color: var(--dt-auth-muted); }
851
+ .dt-auth-demo-note code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 0.75rem; color: inherit; }
852
+
811
853
  /* ---- phones ---- */
812
854
  .dt-auth-column,
813
855
  .dt-auth-card { min-width: 0; }