@_linked/cli 1.4.2 → 1.4.4
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/CHANGELOG.md +16 -0
- package/defaults/app-static/src/index-static.tsx +22 -0
- package/defaults/app-with-backend/src/App.module.css +32 -0
- package/defaults/app-with-backend/src/App.tsx +30 -0
- package/defaults/app-with-backend/src/backend.ts +9 -0
- package/defaults/app-with-backend/src/components/Error.module.css +9 -0
- package/defaults/app-with-backend/src/components/Error.tsx +11 -0
- package/defaults/app-with-backend/src/components/PersonOverview.module.css +100 -0
- package/defaults/app-with-backend/src/components/PersonOverview.tsx +97 -0
- package/defaults/app-with-backend/src/components/PersonOverviewContext.tsx +12 -0
- package/defaults/app-with-backend/src/components/PersonPreview.module.css +97 -0
- package/defaults/app-with-backend/src/components/PersonPreview.tsx +107 -0
- package/defaults/app-with-backend/src/components/Spinner.module.css +17 -0
- package/defaults/app-with-backend/src/components/Spinner.tsx +12 -0
- package/defaults/app-with-backend/src/index.tsx +34 -0
- package/defaults/app-with-backend/src/layout/DefaultLayout.module.css +15 -0
- package/defaults/app-with-backend/src/layout/DefaultLayout.tsx +12 -0
- package/defaults/app-with-backend/src/layout/Header.module.css +46 -0
- package/defaults/app-with-backend/src/layout/Header.tsx +27 -0
- package/defaults/app-with-backend/src/linked.frontend.datasets.json +12 -0
- package/defaults/app-with-backend/src/linked.frontend.storage.ts +36 -0
- package/defaults/app-with-backend/src/package.ts +12 -0
- package/defaults/app-with-backend/src/pages/Home.module.css +23 -0
- package/defaults/app-with-backend/src/pages/Home.tsx +14 -0
- package/defaults/app-with-backend/src/pages/Page1.module.css +18 -0
- package/defaults/app-with-backend/src/pages/Page1.tsx +16 -0
- package/defaults/app-with-backend/src/pages/PageNotFound.module.css +19 -0
- package/defaults/app-with-backend/src/pages/PageNotFound.tsx +14 -0
- package/defaults/app-with-backend/src/pages/Signin.module.css +20 -0
- package/defaults/app-with-backend/src/pages/Signin.tsx +16 -0
- package/defaults/app-with-backend/src/routes.tsx +85 -0
- package/defaults/app-with-backend/src/theme.css +91 -0
- package/defaults/app-with-backend/src/types.ts +14 -0
- package/defaults/app-with-backend/src/utils/lazyWithPreload.tsx +42 -0
- package/defaults/app-with-backend/src/utils/preloadRoutes.ts +38 -0
- package/defaults/app-with-backend/tsconfig.json +24 -0
- package/defaults/package/package.json +7 -3
- package/lib/cjs/cli-deprecated.d.ts +2 -0
- package/lib/cjs/cli-methods.d.ts +67 -0
- package/lib/cjs/cli.d.ts +3 -0
- package/lib/cjs/commands/build-package.d.ts +9 -0
- package/lib/cjs/commands/safe-yarn.d.ts +10 -0
- package/lib/cjs/commands/setup-publish.d.ts +23 -0
- package/lib/cjs/config-webpack-app.d.ts +112 -0
- package/lib/cjs/config-webpack.d.ts +55 -0
- package/lib/cjs/defineConfig.d.ts +8 -0
- package/lib/cjs/index.d.ts +12 -0
- package/lib/cjs/interfaces.d.ts +131 -0
- package/lib/cjs/loaders/register-css-only.d.ts +2 -0
- package/lib/cjs/loaders/register.d.ts +2 -0
- package/lib/cjs/metadata.d.ts +1 -0
- package/lib/cjs/package.json +1 -1
- package/lib/cjs/plugins/check-imports.d.ts +2 -0
- package/lib/cjs/plugins/declaration-plugin.d.ts +15 -0
- package/lib/cjs/plugins/externalise-modules.d.ts +7 -0
- package/lib/cjs/plugins/lincd-tailwind-sources.d.ts +3 -0
- package/lib/cjs/plugins/watch-run.d.ts +5 -0
- package/lib/cjs/tailwind.config.d.ts +8 -0
- package/lib/cjs/utils.d.ts +77 -0
- package/lib/esm/cli-deprecated.d.ts +2 -0
- package/lib/esm/cli-methods.d.ts +67 -0
- package/lib/esm/cli.d.ts +3 -0
- package/lib/esm/commands/build-package.d.ts +9 -0
- package/lib/esm/commands/safe-yarn.d.ts +10 -0
- package/lib/esm/commands/setup-publish.d.ts +23 -0
- package/lib/esm/config-webpack-app.d.ts +112 -0
- package/lib/esm/config-webpack.d.ts +55 -0
- package/lib/esm/defineConfig.d.ts +8 -0
- package/lib/esm/index.d.ts +12 -0
- package/lib/esm/interfaces.d.ts +131 -0
- package/lib/esm/launch.d.ts +3 -0
- package/lib/esm/loaders/register-css-only.d.ts +2 -0
- package/lib/esm/loaders/register.d.ts +2 -0
- package/lib/esm/metadata.d.ts +1 -0
- package/lib/esm/package.json +1 -1
- package/lib/esm/plugins/check-imports.d.ts +2 -0
- package/lib/esm/plugins/declaration-plugin.d.ts +15 -0
- package/lib/esm/plugins/externalise-modules.d.ts +7 -0
- package/lib/esm/plugins/lincd-tailwind-sources.d.ts +3 -0
- package/lib/esm/plugins/watch-run.d.ts +5 -0
- package/lib/esm/tailwind.config.d.ts +8 -0
- package/lib/esm/utils.d.ts +77 -0
- package/package.json +1 -1
- package/tests/e2e/create-app.spec.ts +157 -0
- package/tests/unit/safeYarn.test.ts +55 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
@import '@_linked/css/package.css';
|
|
2
|
+
|
|
3
|
+
.header {
|
|
4
|
+
display: flex;
|
|
5
|
+
align-items: center;
|
|
6
|
+
justify-content: space-between;
|
|
7
|
+
gap: --spacing(4);
|
|
8
|
+
padding: --spacing(4) 0 --spacing(6);
|
|
9
|
+
flex-wrap: wrap;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.header h1 {
|
|
13
|
+
color: var(--color-primary-700);
|
|
14
|
+
margin: 0;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.menu {
|
|
18
|
+
display: flex;
|
|
19
|
+
gap: --spacing(1);
|
|
20
|
+
flex-wrap: wrap;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.menu > a {
|
|
24
|
+
font-weight: 500;
|
|
25
|
+
font-size: 0.95rem;
|
|
26
|
+
color: var(--color-gray-700);
|
|
27
|
+
padding: --spacing(2) --spacing(3);
|
|
28
|
+
border-radius: var(--radius-element);
|
|
29
|
+
text-decoration: none;
|
|
30
|
+
transition: background 120ms ease, color 120ms ease;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.menu > a:hover {
|
|
34
|
+
background: var(--color-gray-100);
|
|
35
|
+
color: var(--color-gray-900);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.menu > a.active {
|
|
39
|
+
background: var(--color-primary-500);
|
|
40
|
+
color: var(--color-white);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.menu > a.active:hover {
|
|
44
|
+
background: var(--color-primary-600);
|
|
45
|
+
color: var(--color-white);
|
|
46
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Link, useLocation } from 'react-router-dom';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import style from './Header.module.css';
|
|
4
|
+
import { ROUTES } from '../routes';
|
|
5
|
+
|
|
6
|
+
export function Header() {
|
|
7
|
+
let path = useLocation();
|
|
8
|
+
return (
|
|
9
|
+
<header className={style.header}>
|
|
10
|
+
<h1>${name}</h1>
|
|
11
|
+
<nav className={style.menu}>
|
|
12
|
+
{Object.keys(ROUTES).map((key) => {
|
|
13
|
+
if (ROUTES[key].excludeFromMenu) return null;
|
|
14
|
+
return (
|
|
15
|
+
<Link
|
|
16
|
+
key={key}
|
|
17
|
+
to={ROUTES[key].path}
|
|
18
|
+
className={ROUTES[key].path === path.pathname ? style.active : ''}
|
|
19
|
+
>
|
|
20
|
+
{ROUTES[key].label || key}
|
|
21
|
+
</Link>
|
|
22
|
+
);
|
|
23
|
+
})}
|
|
24
|
+
</nav>
|
|
25
|
+
</header>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_note": "Frontend datasets config. Public values only — this file ships in the browser bundle. Each entry: `store` (npm import path to the IDataset class) + `config` (a single JSON object passed verbatim to `new StoreClass(config)`). The default BackendAPIStore entry proxies queries to this app's own backend; swap in a different store class (e.g. @_linked/fuseki/shapes/FusekiStore) to talk directly to a public SPARQL endpoint.",
|
|
3
|
+
|
|
4
|
+
"datasets": {
|
|
5
|
+
"appData": {
|
|
6
|
+
"store": "@_linked/server/shapes/quadstores/BackendAPIStore",
|
|
7
|
+
"config": {
|
|
8
|
+
"name": "appData"
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// Frontend storage for ${name}.
|
|
2
|
+
//
|
|
3
|
+
// Reads linked.frontend.datasets.json and instantiates one store per
|
|
4
|
+
// alias. Webpack can't bundle dynamic imports of arbitrary npm paths,
|
|
5
|
+
// so on the frontend we import each store class explicitly and
|
|
6
|
+
// construct it per-alias here.
|
|
7
|
+
//
|
|
8
|
+
// Aliases on this side are independent from backend aliases — the
|
|
9
|
+
// framework re-routes by shape on each side.
|
|
10
|
+
import datasetsConfig from './linked.frontend.datasets.json' assert { type: 'json' };
|
|
11
|
+
import { parseDatasetsConfig } from '@_linked/core/utils/parseDatasetsConfig';
|
|
12
|
+
import { LinkedFileStorage } from '@_linked/core/utils/LinkedFileStorage';
|
|
13
|
+
import { LinkedStorage } from '@_linked/core/utils/LinkedStorage';
|
|
14
|
+
import { BackendAPIStore } from '@_linked/server/shapes/quadstores/BackendAPIStore';
|
|
15
|
+
import { getAccessUrlLocalFileStore } from '@_linked/server/utils/accessUrl';
|
|
16
|
+
// Direct-to-Fuseki on the frontend? Uncomment:
|
|
17
|
+
// import { FusekiStore } from '@_linked/fuseki/shapes/FusekiStore';
|
|
18
|
+
|
|
19
|
+
// Parse JSON; pass empty env since the browser has no runtime process.env.
|
|
20
|
+
// Per-env values for the frontend should be hardcoded `process.env.X`
|
|
21
|
+
// references (webpack inlines them at build time).
|
|
22
|
+
const config = parseDatasetsConfig(datasetsConfig, {});
|
|
23
|
+
|
|
24
|
+
// One store per alias. Each line imports the class above and constructs it
|
|
25
|
+
// with the alias's `config`. Adding a new alias = add an import + a line.
|
|
26
|
+
const appData = new BackendAPIStore(config.datasets.appData.config);
|
|
27
|
+
// Direct-to-Fuseki alternative (no creds in browser):
|
|
28
|
+
// const appData = new FusekiStore(config.datasets.appData.config);
|
|
29
|
+
|
|
30
|
+
// Shape → alias. For multi-alias setups add per-shape pins, e.g.:
|
|
31
|
+
// import { PageView } from './shapes/PageView';
|
|
32
|
+
// LinkedStorage.setDatasetForShapes(analytics, [PageView]);
|
|
33
|
+
LinkedStorage.setDefaultDataset(appData);
|
|
34
|
+
|
|
35
|
+
// File asset URLs.
|
|
36
|
+
LinkedFileStorage.setDefaultAccessURL(getAccessUrlLocalFileStore());
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { linkedPackage } from '@_linked/core/utils/Package';
|
|
2
|
+
|
|
3
|
+
export const {
|
|
4
|
+
linkedComponent,
|
|
5
|
+
linkedSetComponent,
|
|
6
|
+
linkedShape,
|
|
7
|
+
linkedUtil,
|
|
8
|
+
linkedOntology,
|
|
9
|
+
registerPackageExport,
|
|
10
|
+
packageExports,
|
|
11
|
+
packageName,
|
|
12
|
+
} = linkedPackage('${hyphen_name}');
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
@import '@_linked/css/package.css';
|
|
2
|
+
|
|
3
|
+
.Home {
|
|
4
|
+
display: block;
|
|
5
|
+
margin: --spacing(4) 0;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.Home code {
|
|
9
|
+
font-family: var(--font-mono);
|
|
10
|
+
background: var(--bg-section);
|
|
11
|
+
padding: 0 --spacing(1);
|
|
12
|
+
border-radius: var(--radius-element);
|
|
13
|
+
font-size: 0.85em;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.Home pre {
|
|
17
|
+
padding: --spacing(4);
|
|
18
|
+
background: var(--bg-section);
|
|
19
|
+
border-radius: var(--radius-element);
|
|
20
|
+
overflow-x: auto;
|
|
21
|
+
font-family: var(--font-mono);
|
|
22
|
+
margin: --spacing(2) 0;
|
|
23
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { DefaultLayout } from '../layout/DefaultLayout';
|
|
3
|
+
import { PersonOverview } from '../components/PersonOverview';
|
|
4
|
+
import style from './Home.module.css';
|
|
5
|
+
|
|
6
|
+
export default function Home() {
|
|
7
|
+
return (
|
|
8
|
+
<DefaultLayout>
|
|
9
|
+
<div className={style.Home}>
|
|
10
|
+
<PersonOverview />
|
|
11
|
+
</div>
|
|
12
|
+
</DefaultLayout>
|
|
13
|
+
);
|
|
14
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
@import '@_linked/css/package.css';
|
|
2
|
+
|
|
3
|
+
.Card {
|
|
4
|
+
background: var(--bg-card);
|
|
5
|
+
border-radius: var(--radius-card);
|
|
6
|
+
box-shadow: var(--shadow-card);
|
|
7
|
+
padding: --spacing(6);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.Card h2 {
|
|
11
|
+
margin: 0 0 --spacing(3);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.Card p {
|
|
15
|
+
color: var(--color-gray-700);
|
|
16
|
+
line-height: 1.5;
|
|
17
|
+
margin: 0;
|
|
18
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { DefaultLayout } from '../layout/DefaultLayout';
|
|
2
|
+
import style from './Page1.module.css';
|
|
3
|
+
|
|
4
|
+
export default function Page1() {
|
|
5
|
+
return (
|
|
6
|
+
<DefaultLayout>
|
|
7
|
+
<div className={style.Card}>
|
|
8
|
+
<h2>Page 1</h2>
|
|
9
|
+
<p>
|
|
10
|
+
A second route to show how navigation works. Edit{' '}
|
|
11
|
+
<code>src/pages/Page1.tsx</code> to make this your own.
|
|
12
|
+
</p>
|
|
13
|
+
</div>
|
|
14
|
+
</DefaultLayout>
|
|
15
|
+
);
|
|
16
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
@import '@_linked/css/package.css';
|
|
2
|
+
|
|
3
|
+
.Card {
|
|
4
|
+
background: var(--bg-card);
|
|
5
|
+
border-radius: var(--radius-card);
|
|
6
|
+
box-shadow: var(--shadow-card);
|
|
7
|
+
padding: --spacing(6);
|
|
8
|
+
text-align: center;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.Card h2 {
|
|
12
|
+
margin: 0 0 --spacing(3);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.Card p {
|
|
16
|
+
color: var(--color-gray-700);
|
|
17
|
+
line-height: 1.5;
|
|
18
|
+
margin: 0;
|
|
19
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { DefaultLayout } from '../layout/DefaultLayout';
|
|
3
|
+
import style from './PageNotFound.module.css';
|
|
4
|
+
|
|
5
|
+
export default function PageNotFound() {
|
|
6
|
+
return (
|
|
7
|
+
<DefaultLayout>
|
|
8
|
+
<div className={style.Card}>
|
|
9
|
+
<h2>Page not found</h2>
|
|
10
|
+
<p>The page you were looking for does not exist.</p>
|
|
11
|
+
</div>
|
|
12
|
+
</DefaultLayout>
|
|
13
|
+
);
|
|
14
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
@import '@_linked/css/package.css';
|
|
2
|
+
|
|
3
|
+
.Card {
|
|
4
|
+
background: var(--bg-card);
|
|
5
|
+
border-radius: var(--radius-card);
|
|
6
|
+
box-shadow: var(--shadow-card);
|
|
7
|
+
padding: --spacing(6);
|
|
8
|
+
max-width: 480px;
|
|
9
|
+
margin: --spacing(8) auto;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.Card h2 {
|
|
13
|
+
margin: 0 0 --spacing(3);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.Card p {
|
|
17
|
+
color: var(--color-gray-700);
|
|
18
|
+
line-height: 1.5;
|
|
19
|
+
margin: 0;
|
|
20
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { DefaultLayout } from '../layout/DefaultLayout';
|
|
2
|
+
import style from './Signin.module.css';
|
|
3
|
+
|
|
4
|
+
export default function Signin() {
|
|
5
|
+
return (
|
|
6
|
+
<DefaultLayout>
|
|
7
|
+
<div className={style.Card}>
|
|
8
|
+
<h2>Sign in</h2>
|
|
9
|
+
<p>
|
|
10
|
+
See <a href="https://linked.cm">linked.cm</a> for different ways to
|
|
11
|
+
sign in.
|
|
12
|
+
</p>
|
|
13
|
+
</div>
|
|
14
|
+
</DefaultLayout>
|
|
15
|
+
);
|
|
16
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { RequireAuth } from '@_linked/auth/components/RequireAuth';
|
|
2
|
+
import type { RoutesConfig } from '@_linked/server/types/RouteConfig';
|
|
3
|
+
import React, { lazy, Suspense } from 'react';
|
|
4
|
+
import { Route, Routes } from 'react-router-dom';
|
|
5
|
+
import { Spinner } from './components/Spinner';
|
|
6
|
+
import PageNotFound from './pages/PageNotFound';
|
|
7
|
+
import { lazyWithPreload } from './utils/lazyWithPreload';
|
|
8
|
+
|
|
9
|
+
// Create preloadable lazy components for direct-entry routes
|
|
10
|
+
const HomePage = lazyWithPreload(
|
|
11
|
+
() => import(/* webpackChunkName: "home" */ './pages/Home')
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
const SigninPage = lazyWithPreload(
|
|
15
|
+
() => import(/* webpackChunkName: "signin" */ './pages/Signin')
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
// Export preloadable components for server-side preloading
|
|
19
|
+
export const PRELOADABLE_ROUTES = {
|
|
20
|
+
home: HomePage,
|
|
21
|
+
signin: SigninPage,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
export const ROUTES: RoutesConfig = {
|
|
26
|
+
home: {
|
|
27
|
+
path: '/',
|
|
28
|
+
component: HomePage.Component,
|
|
29
|
+
label: 'Home',
|
|
30
|
+
preloadChunks: ['home'],
|
|
31
|
+
},
|
|
32
|
+
page1: {
|
|
33
|
+
path: '/page1',
|
|
34
|
+
component: lazy(
|
|
35
|
+
() => import(/* webpackChunkName: "page1" */ './pages/Page1')
|
|
36
|
+
),
|
|
37
|
+
label: 'Protected page',
|
|
38
|
+
requireAuth: true,
|
|
39
|
+
preloadChunks: ['page1'],
|
|
40
|
+
},
|
|
41
|
+
signin: {
|
|
42
|
+
path: '/signin',
|
|
43
|
+
component: SigninPage.Component,
|
|
44
|
+
label: 'Sign In',
|
|
45
|
+
excludeFromMenu: true,
|
|
46
|
+
preloadChunks: ['signin'],
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export default function AppRoutes() {
|
|
51
|
+
return (
|
|
52
|
+
<Routes>
|
|
53
|
+
{Object.keys(ROUTES).map((routeName) => {
|
|
54
|
+
const route = ROUTES[routeName];
|
|
55
|
+
const Component = route.component;
|
|
56
|
+
|
|
57
|
+
//if a route is marked as requireAuth, wrap it in the RequireAuth component and pass the signinRoute
|
|
58
|
+
const AuthGuard = route.requireAuth ? RequireAuth : React.Fragment;
|
|
59
|
+
const authProps = route.requireAuth
|
|
60
|
+
? { signinRoute: ROUTES.signin.path }
|
|
61
|
+
: {};
|
|
62
|
+
|
|
63
|
+
// define a render function that determines what to render based on the component and route.render
|
|
64
|
+
const renderRoute = () =>
|
|
65
|
+
// if a Component is defined, render it using JSX syntax (<Component />)
|
|
66
|
+
// if not, check if a route.render function is defined and call that render function if available.
|
|
67
|
+
// if neither Component nor route.render is defined, return null
|
|
68
|
+
Component ? <Component /> : route.render ? route.render() : null;
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<Route
|
|
72
|
+
key={route.path}
|
|
73
|
+
path={route.path}
|
|
74
|
+
element={
|
|
75
|
+
<AuthGuard {...authProps}>
|
|
76
|
+
<Suspense fallback={<Spinner />}>{renderRoute()}</Suspense>
|
|
77
|
+
</AuthGuard>
|
|
78
|
+
}
|
|
79
|
+
/>
|
|
80
|
+
);
|
|
81
|
+
})}
|
|
82
|
+
<Route path="*" element={<PageNotFound />} />
|
|
83
|
+
</Routes>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Theme overrides for ${name}.
|
|
3
|
+
*
|
|
4
|
+
* The default palette below is a teal-blue primary + mint-green secondary.
|
|
5
|
+
* Override --color-primary-* / --color-secondary-* to your own brand.
|
|
6
|
+
*
|
|
7
|
+
* Available tokens (palette, surfaces, shadows, radii, spacing) are documented in:
|
|
8
|
+
* https://github.com/linked-cm/css/blob/main/README.md
|
|
9
|
+
* https://github.com/linked-cm/css/blob/main/docs/styling-and-themes.md
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/* Tailwind v4 layers — split-import pattern to substitute the Linked preflight.
|
|
13
|
+
See @_linked/css README "Why the split-import?" for details. */
|
|
14
|
+
@layer theme, base, components, utilities;
|
|
15
|
+
@import 'tailwindcss/theme.css' layer(theme);
|
|
16
|
+
@import '@_linked/css/preflight.css';
|
|
17
|
+
@import 'tailwindcss/utilities.css' layer(utilities);
|
|
18
|
+
|
|
19
|
+
/* Apply Tailwind config from @_linked/cli — ensures all @_linked/* shapes
|
|
20
|
+
are scanned for utility classes. */
|
|
21
|
+
@config "@_linked/cli/tailwind.config.js";
|
|
22
|
+
|
|
23
|
+
/* @_linked/css: framework theme defaults + utility mixins.
|
|
24
|
+
App-specific @theme overrides below extend / replace these defaults. */
|
|
25
|
+
@import '@_linked/css/theme-defaults.css';
|
|
26
|
+
@import '@_linked/css/utilities.css';
|
|
27
|
+
|
|
28
|
+
@theme {
|
|
29
|
+
/* === Primary palette — Linked teal-blue === */
|
|
30
|
+
--color-primary-50: #f7fbfd;
|
|
31
|
+
--color-primary-100: #eef7fb;
|
|
32
|
+
--color-primary-200: #d3ebf5;
|
|
33
|
+
--color-primary-300: #a8d7ec;
|
|
34
|
+
--color-primary-400: #71bbdd;
|
|
35
|
+
--color-primary-500: #116d8d; /* base */
|
|
36
|
+
--color-primary-600: #0a5f7d;
|
|
37
|
+
--color-primary-700: #005970;
|
|
38
|
+
--color-primary-800: #004d61;
|
|
39
|
+
--color-primary-900: #001f28;
|
|
40
|
+
|
|
41
|
+
/* === Secondary palette — mint green === */
|
|
42
|
+
--color-secondary-50: #f9fdfc;
|
|
43
|
+
--color-secondary-100: #f4fbf8;
|
|
44
|
+
--color-secondary-200: #e6f5ef;
|
|
45
|
+
--color-secondary-300: #ccebe0;
|
|
46
|
+
--color-secondary-400: #b1e0cd;
|
|
47
|
+
--color-secondary-500: #52a181; /* base */
|
|
48
|
+
--color-secondary-600: #297a5c;
|
|
49
|
+
--color-secondary-700: #146b4f;
|
|
50
|
+
--color-secondary-800: #005d43;
|
|
51
|
+
--color-secondary-900: #00201a;
|
|
52
|
+
|
|
53
|
+
/* === Shadows === */
|
|
54
|
+
--shadow-surface: var(--shadow-sm); /* Background panels, sections */
|
|
55
|
+
--shadow-card: var(--shadow-md); /* Cards, organized content blocks */
|
|
56
|
+
--shadow-popover: var(--shadow-lg); /* Tooltips, dropdowns, popovers */
|
|
57
|
+
--shadow-modal: var(--shadow-2xl); /* Modals, overlays */
|
|
58
|
+
--shadow-floating-action: var(--shadow-lg);
|
|
59
|
+
--shadow-content: none; /* Buttons, inputs inside surfaces */
|
|
60
|
+
|
|
61
|
+
/* === Backgrounds / Surfaces === */
|
|
62
|
+
--bg-page: var(--color-gray-50);
|
|
63
|
+
--bg-section: var(--color-gray-100);
|
|
64
|
+
--bg-card: var(--color-white);
|
|
65
|
+
--bg-popover: var(--color-white);
|
|
66
|
+
--bg-modal: var(--color-white);
|
|
67
|
+
--bg-sidebar: var(--color-gray-200);
|
|
68
|
+
--bg-footer: var(--color-gray-300);
|
|
69
|
+
--bg-overlay: var(--color-gray-900);
|
|
70
|
+
--color-card: var(--color-white);
|
|
71
|
+
|
|
72
|
+
/* === Border radius === */
|
|
73
|
+
--radius-surface: var(--radius-sm);
|
|
74
|
+
--radius-card: var(--radius-lg);
|
|
75
|
+
--radius-element: var(--radius-md);
|
|
76
|
+
--radius-popover: var(--radius-md);
|
|
77
|
+
--radius-modal: var(--radius-2xl);
|
|
78
|
+
--radius-floating-action: var(--radius-full);
|
|
79
|
+
--radius-none: 0px;
|
|
80
|
+
|
|
81
|
+
/* === Z-index === */
|
|
82
|
+
--z-base: 0;
|
|
83
|
+
--z-surface: 10;
|
|
84
|
+
--z-popover: 30;
|
|
85
|
+
--z-modal: 50;
|
|
86
|
+
--z-overlay: 75;
|
|
87
|
+
--z-tooltip: 100;
|
|
88
|
+
|
|
89
|
+
/* === Spacing tokens === */
|
|
90
|
+
--spacing-card: calc(var(--spacing) * 4);
|
|
91
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
declare module '*.css' {
|
|
2
|
+
const styles: {
|
|
3
|
+
readonly [key: string]: string;
|
|
4
|
+
};
|
|
5
|
+
// export type ClassName = string;
|
|
6
|
+
export default styles;
|
|
7
|
+
}
|
|
8
|
+
declare module '*.scss' {
|
|
9
|
+
const styles: {
|
|
10
|
+
readonly [key: string]: string;
|
|
11
|
+
};
|
|
12
|
+
// export type ClassName = string;
|
|
13
|
+
export default styles;
|
|
14
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { ComponentType, lazy } from 'react';
|
|
2
|
+
|
|
3
|
+
export interface PreloadableComponent<T extends ComponentType<any>> {
|
|
4
|
+
preload: () => Promise<{ default: T }>;
|
|
5
|
+
Component: React.LazyExoticComponent<T>;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Creates a lazy-loaded component with a preload function
|
|
10
|
+
* This allows preloading the component before React needs it
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* const SigninPage = lazyWithPreload(() => import('./pages/Signin'));
|
|
14
|
+
* // Later, before navigation or on server:
|
|
15
|
+
* SigninPage.preload();
|
|
16
|
+
*/
|
|
17
|
+
export function lazyWithPreload<T extends ComponentType<any>>(
|
|
18
|
+
importFunc: () => Promise<{ default: T }>
|
|
19
|
+
): PreloadableComponent<T> {
|
|
20
|
+
let modulePromise: Promise<{ default: T }> | null = null;
|
|
21
|
+
|
|
22
|
+
const preload = () => {
|
|
23
|
+
if (!modulePromise) {
|
|
24
|
+
modulePromise = importFunc();
|
|
25
|
+
}
|
|
26
|
+
return modulePromise;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const Component = lazy(() => {
|
|
30
|
+
// If preload was called, reuse the same promise
|
|
31
|
+
// This ensures React uses the already-loaded module
|
|
32
|
+
if (!modulePromise) {
|
|
33
|
+
modulePromise = importFunc();
|
|
34
|
+
}
|
|
35
|
+
return modulePromise;
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
preload,
|
|
40
|
+
Component,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-side script to preload lazy routes before hydration
|
|
3
|
+
* This should be called BEFORE React's hydrateRoot()
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// This will be populated by the server with the routes that need preloading
|
|
7
|
+
declare global {
|
|
8
|
+
interface Window {
|
|
9
|
+
__PRELOAD_ROUTES__?: string[];
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export async function preloadMatchedRoute() {
|
|
14
|
+
// Get the list of routes to preload from the server
|
|
15
|
+
const routesToPreload = window.__PRELOAD_ROUTES__ || [];
|
|
16
|
+
|
|
17
|
+
if (routesToPreload.length === 0) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Dynamically import the routes module to access PRELOADABLE_ROUTES
|
|
22
|
+
const routesModule = await import('../routes');
|
|
23
|
+
const { PRELOADABLE_ROUTES } = routesModule;
|
|
24
|
+
|
|
25
|
+
// Preload each route
|
|
26
|
+
const preloadPromises = routesToPreload
|
|
27
|
+
.map((routeKey) => {
|
|
28
|
+
const preloadableRoute = PRELOADABLE_ROUTES[routeKey];
|
|
29
|
+
if (preloadableRoute && preloadableRoute.preload) {
|
|
30
|
+
return preloadableRoute.preload();
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
})
|
|
34
|
+
.filter(Boolean);
|
|
35
|
+
|
|
36
|
+
// Wait for all preloads to complete
|
|
37
|
+
await Promise.all(preloadPromises);
|
|
38
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"module": "esnext",
|
|
4
|
+
"moduleResolution": "node",
|
|
5
|
+
"sourceMap": true,
|
|
6
|
+
"target": "es2018",
|
|
7
|
+
"resolveJsonModule": true,
|
|
8
|
+
"declaration": false,
|
|
9
|
+
"experimentalDecorators": true,
|
|
10
|
+
"emitDecoratorMetadata": true,
|
|
11
|
+
"esModuleInterop": true,
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"jsx": "react-jsx",
|
|
14
|
+
"types": ["node", "react", "react-dom"],
|
|
15
|
+
"plugins": [
|
|
16
|
+
{
|
|
17
|
+
"name": "typescript-plugin-css-modules"
|
|
18
|
+
}
|
|
19
|
+
],
|
|
20
|
+
"pretty": true
|
|
21
|
+
},
|
|
22
|
+
"files": ["./src/index.tsx", "./src/types.ts"],
|
|
23
|
+
"include": ["./src/backend.ts"]
|
|
24
|
+
}
|
|
@@ -18,9 +18,11 @@
|
|
|
18
18
|
"license": "ISC",
|
|
19
19
|
"scripts": {
|
|
20
20
|
"start": "yarn linked dev",
|
|
21
|
-
"build": "yarn
|
|
22
|
-
"
|
|
23
|
-
"
|
|
21
|
+
"build": "rimraf ./lib && yarn build-esm && yarn build-cjs && yarn copy-to-lib && yarn dual-package",
|
|
22
|
+
"build-esm": "echo \"compiling ESM\" && rimraf ./lib/esm && yarn tsc -p tsconfig-esm.json",
|
|
23
|
+
"build-cjs": "echo \"compiling CJS\" && rimraf ./lib/cjs && yarn tsc -p tsconfig-cjs.json",
|
|
24
|
+
"copy-to-lib": "echo 'copying CSS + JSON assets to lib folder' && yarn copyfiles -u 1 'src/**/*.css' 'src/**/*.json' lib/esm && yarn copyfiles -u 1 'src/**/*.css' 'src/**/*.json' lib/cjs",
|
|
25
|
+
"dual-package": "echo 'adding package.json to each lib folder for dual output support' && yarn tsconfig-to-dual-package ./tsconfig-cjs.json ./tsconfig-esm.json"
|
|
24
26
|
},
|
|
25
27
|
"keywords": [
|
|
26
28
|
"linked",
|
|
@@ -58,6 +60,8 @@
|
|
|
58
60
|
"devDependencies": {
|
|
59
61
|
"@types/node": "^22.13.14",
|
|
60
62
|
"@_linked/cli": "^1.2",
|
|
63
|
+
"copyfiles": "^2.4.1",
|
|
64
|
+
"rimraf": "^5.0.7",
|
|
61
65
|
"tsconfig-to-dual-package": "^1.2.0",
|
|
62
66
|
"typescript": "^5.7.3"
|
|
63
67
|
}
|