@shipfox/client-shell 2.0.0 → 3.0.1

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 (103) hide show
  1. package/assets/apple-touch-icon.png +0 -0
  2. package/assets/favicon-96x96.png +0 -0
  3. package/assets/favicon.ico +0 -0
  4. package/assets/favicon.svg +1 -0
  5. package/assets/site.webmanifest +22 -0
  6. package/assets/web-app-manifest-192x192.png +0 -0
  7. package/assets/web-app-manifest-512x512.png +0 -0
  8. package/dist/tsconfig.test.tsbuildinfo +1 -1
  9. package/dist/vite/index.d.ts +1 -0
  10. package/dist/vite/index.d.ts.map +1 -1
  11. package/dist/vite/index.js +1 -0
  12. package/dist/vite/index.js.map +1 -1
  13. package/dist/vite/manifest.d.ts +3 -0
  14. package/dist/vite/manifest.d.ts.map +1 -0
  15. package/dist/vite/manifest.js +140 -0
  16. package/dist/vite/manifest.js.map +1 -0
  17. package/package.json +11 -5
  18. package/.turbo/turbo-build.log +0 -2
  19. package/.turbo/turbo-type$colon$emit.log +0 -1
  20. package/.turbo/turbo-type.log +0 -1
  21. package/CHANGELOG.md +0 -59
  22. package/src/components/main-layout.tsx +0 -45
  23. package/src/components/nav-bar.tsx +0 -41
  24. package/src/components/nav-tabs.test.tsx +0 -60
  25. package/src/components/nav-tabs.tsx +0 -47
  26. package/src/components/not-found-page.tsx +0 -14
  27. package/src/components/settings-nav.tsx +0 -35
  28. package/src/components/user-menu.tsx +0 -59
  29. package/src/components/workspace-crumb.tsx +0 -43
  30. package/src/components/workspace-switcher.tsx +0 -75
  31. package/src/compose/compose-routes.test.ts +0 -112
  32. package/src/compose/compose-routes.ts +0 -60
  33. package/src/compose/errors.ts +0 -54
  34. package/src/compose/merge-config.test.ts +0 -26
  35. package/src/compose/merge-config.ts +0 -27
  36. package/src/compose/normalize-route-path.ts +0 -6
  37. package/src/compose/validate-providers.test.ts +0 -31
  38. package/src/compose/validate-providers.ts +0 -36
  39. package/src/compose/validate-registries.test.ts +0 -92
  40. package/src/compose/validate-registries.ts +0 -61
  41. package/src/config/config-screen.test.tsx +0 -37
  42. package/src/config/shell-config.ts +0 -10
  43. package/src/contract.ts +0 -47
  44. package/src/index.ts +0 -1
  45. package/src/runtime/active-workspace.ts +0 -13
  46. package/src/runtime/anchor-paths.ts +0 -18
  47. package/src/runtime/anchors.tsx +0 -106
  48. package/src/runtime/assemble-route-tree.ts +0 -42
  49. package/src/runtime/auth.tsx +0 -213
  50. package/src/runtime/chrome-context.tsx +0 -22
  51. package/src/runtime/compose-client-app.tsx +0 -122
  52. package/src/runtime/define-route.test.ts +0 -15
  53. package/src/runtime/define-route.ts +0 -33
  54. package/src/runtime/index.ts +0 -19
  55. package/src/runtime/last-workspace.ts +0 -22
  56. package/src/runtime/nav-order.ts +0 -1
  57. package/src/runtime/provider-stack.tsx +0 -39
  58. package/src/runtime/providers.test.tsx +0 -51
  59. package/src/runtime/registries.test.tsx +0 -100
  60. package/src/runtime/registries.ts +0 -42
  61. package/src/runtime/router-context.ts +0 -9
  62. package/src/runtime/routes.test.tsx +0 -48
  63. package/src/runtime/workspace-setup.tsx +0 -61
  64. package/src/testing/index.test.tsx +0 -57
  65. package/src/testing/index.tsx +0 -51
  66. package/src/vite/evaluate-features.test.ts +0 -89
  67. package/src/vite/evaluate-features.ts +0 -114
  68. package/src/vite/generate.test.ts +0 -22
  69. package/src/vite/generate.ts +0 -99
  70. package/src/vite/generated-router.test.tsx +0 -58
  71. package/src/vite/index.ts +0 -1
  72. package/src/vite/plugin.test.ts +0 -231
  73. package/src/vite/plugin.ts +0 -101
  74. package/src/vite/validate.test.ts +0 -166
  75. package/test/default-route-impl.tsx +0 -3
  76. package/test/external/FINDINGS.md +0 -41
  77. package/test/external/README.md +0 -37
  78. package/test/external/fixture/index.html +0 -12
  79. package/test/external/fixture/package.json +0 -31
  80. package/test/external/fixture/src/app.fixture.tsx +0 -104
  81. package/test/external/fixture/src/features/external-settings.tsx +0 -19
  82. package/test/external/fixture/src/features/login-override.tsx +0 -9
  83. package/test/external/fixture/src/features.collision.ts +0 -15
  84. package/test/external/fixture/src/features.ts +0 -50
  85. package/test/external/fixture/src/link-typecheck.tsx +0 -10
  86. package/test/external/fixture/src/main.tsx +0 -86
  87. package/test/external/fixture/src/provider.ts +0 -45
  88. package/test/external/fixture/tsconfig.json +0 -15
  89. package/test/external/fixture/vite.config.ts +0 -21
  90. package/test/external/fixture/vitest.config.ts +0 -17
  91. package/test/external/verify.mjs +0 -488
  92. package/test/fixtures/features.ts +0 -56
  93. package/test/named-route-impl.tsx +0 -3
  94. package/test/render.tsx +0 -67
  95. package/test/search-route-impl.tsx +0 -7
  96. package/test/setup.ts +0 -3
  97. package/test/typecheck/shipfox-app.gen.ts +0 -85
  98. package/test/typecheck/types.tsx +0 -23
  99. package/tsconfig.build.json +0 -10
  100. package/tsconfig.build.tsbuildinfo +0 -1
  101. package/tsconfig.json +0 -1
  102. package/tsconfig.test.json +0 -12
  103. package/vitest.config.ts +0 -22
@@ -1,2 +1,3 @@
1
+ export { shipfoxClientManifest } from './manifest.js';
1
2
  export { shipfoxClientComposition } from './plugin.js';
2
3
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/vite/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,wBAAwB,EAAC,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/vite/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,qBAAqB,EAAC,MAAM,eAAe,CAAC;AACpD,OAAO,EAAC,wBAAwB,EAAC,MAAM,aAAa,CAAC"}
@@ -1,3 +1,4 @@
1
+ export { shipfoxClientManifest } from './manifest.js';
1
2
  export { shipfoxClientComposition } from './plugin.js';
2
3
 
3
4
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/vite/index.ts"],"sourcesContent":["export {shipfoxClientComposition} from './plugin.js';\n"],"names":["shipfoxClientComposition"],"mappings":"AAAA,SAAQA,wBAAwB,QAAO,cAAc"}
1
+ {"version":3,"sources":["../../src/vite/index.ts"],"sourcesContent":["export {shipfoxClientManifest} from './manifest.js';\nexport {shipfoxClientComposition} from './plugin.js';\n"],"names":["shipfoxClientManifest","shipfoxClientComposition"],"mappings":"AAAA,SAAQA,qBAAqB,QAAO,gBAAgB;AACpD,SAAQC,wBAAwB,QAAO,cAAc"}
@@ -0,0 +1,3 @@
1
+ import type { Plugin } from 'vite';
2
+ export declare function shipfoxClientManifest(): Plugin;
3
+ //# sourceMappingURL=manifest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../src/vite/manifest.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAC,MAAM,EAAiB,MAAM,MAAM,CAAC;AAqBjD,wBAAgB,qBAAqB,IAAI,MAAM,CAwF9C"}
@@ -0,0 +1,140 @@
1
+ import { access, readFile } from 'node:fs/promises';
2
+ import { extname, resolve } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ const assetNames = [
5
+ 'favicon.ico',
6
+ 'favicon.svg',
7
+ 'favicon-96x96.png',
8
+ 'apple-touch-icon.png',
9
+ 'site.webmanifest',
10
+ 'web-app-manifest-192x192.png',
11
+ 'web-app-manifest-512x512.png'
12
+ ];
13
+ const assetDirectory = fileURLToPath(new URL('../../assets/', import.meta.url));
14
+ const contentTypes = {
15
+ '.ico': 'image/x-icon',
16
+ '.png': 'image/png',
17
+ '.svg': 'image/svg+xml',
18
+ '.webmanifest': 'application/manifest+json'
19
+ };
20
+ export function shipfoxClientManifest() {
21
+ let config;
22
+ function assetPath(name) {
23
+ return resolve(assetDirectory, name);
24
+ }
25
+ async function assertNoPublicAssetConflicts() {
26
+ const publicDirectory = config?.publicDir;
27
+ if (!publicDirectory) return;
28
+ const conflicts = (await Promise.all(assetNames.map((name)=>{
29
+ const publicAsset = resolve(publicDirectory, name);
30
+ if (publicAsset === assetPath(name)) return undefined;
31
+ return access(publicAsset).then(()=>name).catch(()=>undefined);
32
+ }))).filter((name)=>name !== undefined);
33
+ if (conflicts.length === 0) return;
34
+ throw new Error(`shipfoxClientManifest() owns ${conflicts.map((name)=>`/${name}`).join(', ')}. Remove the conflicting files from ${publicDirectory}.`);
35
+ }
36
+ return {
37
+ name: 'shipfox-client-manifest',
38
+ async configResolved (resolvedConfig) {
39
+ config = resolvedConfig;
40
+ await assertNoPublicAssetConflicts();
41
+ },
42
+ async buildStart () {
43
+ if (config?.command !== 'build') return;
44
+ for (const name of assetNames){
45
+ this.emitFile({
46
+ type: 'asset',
47
+ fileName: name,
48
+ source: await readFile(assetPath(name))
49
+ });
50
+ }
51
+ },
52
+ configureServer (server) {
53
+ server.middlewares.use(async (request, response, next)=>{
54
+ const name = assetNames.find((assetName)=>request.url?.split('?')[0] === `/${assetName}`);
55
+ if (!name) return next();
56
+ try {
57
+ const content = await readFile(assetPath(name));
58
+ response.setHeader('Content-Type', contentTypes[extname(name)] ?? 'application/octet-stream');
59
+ response.end(content);
60
+ } catch (error) {
61
+ next(error);
62
+ }
63
+ });
64
+ },
65
+ transformIndexHtml: {
66
+ order: 'pre',
67
+ handler () {
68
+ return [
69
+ {
70
+ tag: 'title',
71
+ children: 'Shipfox',
72
+ injectTo: 'head'
73
+ },
74
+ {
75
+ tag: 'link',
76
+ attrs: {
77
+ rel: 'icon',
78
+ type: 'image/png',
79
+ href: '/favicon-96x96.png',
80
+ sizes: '96x96'
81
+ },
82
+ injectTo: 'head'
83
+ },
84
+ {
85
+ tag: 'link',
86
+ attrs: {
87
+ rel: 'icon',
88
+ type: 'image/svg+xml',
89
+ href: '/favicon.svg'
90
+ },
91
+ injectTo: 'head'
92
+ },
93
+ {
94
+ tag: 'link',
95
+ attrs: {
96
+ rel: 'shortcut icon',
97
+ href: '/favicon.ico'
98
+ },
99
+ injectTo: 'head'
100
+ },
101
+ {
102
+ tag: 'link',
103
+ attrs: {
104
+ rel: 'apple-touch-icon',
105
+ sizes: '180x180',
106
+ href: '/apple-touch-icon.png'
107
+ },
108
+ injectTo: 'head'
109
+ },
110
+ {
111
+ tag: 'link',
112
+ attrs: {
113
+ rel: 'manifest',
114
+ href: '/site.webmanifest'
115
+ },
116
+ injectTo: 'head'
117
+ },
118
+ {
119
+ tag: 'meta',
120
+ attrs: {
121
+ name: 'apple-mobile-web-app-title',
122
+ content: 'Shipfox'
123
+ },
124
+ injectTo: 'head'
125
+ },
126
+ {
127
+ tag: 'meta',
128
+ attrs: {
129
+ name: 'theme-color',
130
+ content: '#ff4b00'
131
+ },
132
+ injectTo: 'head'
133
+ }
134
+ ];
135
+ }
136
+ }
137
+ };
138
+ }
139
+
140
+ //# sourceMappingURL=manifest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/vite/manifest.ts"],"sourcesContent":["import {access, readFile} from 'node:fs/promises';\nimport {extname, resolve} from 'node:path';\nimport {fileURLToPath} from 'node:url';\nimport type {Plugin, ResolvedConfig} from 'vite';\n\nconst assetNames = [\n 'favicon.ico',\n 'favicon.svg',\n 'favicon-96x96.png',\n 'apple-touch-icon.png',\n 'site.webmanifest',\n 'web-app-manifest-192x192.png',\n 'web-app-manifest-512x512.png',\n] as const;\n\nconst assetDirectory = fileURLToPath(new URL('../../assets/', import.meta.url));\n\nconst contentTypes: Record<string, string> = {\n '.ico': 'image/x-icon',\n '.png': 'image/png',\n '.svg': 'image/svg+xml',\n '.webmanifest': 'application/manifest+json',\n};\n\nexport function shipfoxClientManifest(): Plugin {\n let config: ResolvedConfig | undefined;\n\n function assetPath(name: (typeof assetNames)[number]): string {\n return resolve(assetDirectory, name);\n }\n\n async function assertNoPublicAssetConflicts(): Promise<void> {\n const publicDirectory = config?.publicDir;\n if (!publicDirectory) return;\n const conflicts = (\n await Promise.all(\n assetNames.map((name) => {\n const publicAsset = resolve(publicDirectory, name);\n if (publicAsset === assetPath(name)) return undefined;\n return access(publicAsset)\n .then(() => name)\n .catch(() => undefined);\n }),\n )\n ).filter((name): name is (typeof assetNames)[number] => name !== undefined);\n if (conflicts.length === 0) return;\n throw new Error(\n `shipfoxClientManifest() owns ${conflicts.map((name) => `/${name}`).join(', ')}. Remove the conflicting files from ${publicDirectory}.`,\n );\n }\n\n return {\n name: 'shipfox-client-manifest',\n async configResolved(resolvedConfig) {\n config = resolvedConfig;\n await assertNoPublicAssetConflicts();\n },\n async buildStart() {\n if (config?.command !== 'build') return;\n for (const name of assetNames) {\n this.emitFile({type: 'asset', fileName: name, source: await readFile(assetPath(name))});\n }\n },\n configureServer(server) {\n server.middlewares.use(async (request, response, next) => {\n const name = assetNames.find((assetName) => request.url?.split('?')[0] === `/${assetName}`);\n if (!name) return next();\n\n try {\n const content = await readFile(assetPath(name));\n response.setHeader(\n 'Content-Type',\n contentTypes[extname(name)] ?? 'application/octet-stream',\n );\n response.end(content);\n } catch (error) {\n next(error);\n }\n });\n },\n transformIndexHtml: {\n order: 'pre',\n handler() {\n return [\n {tag: 'title', children: 'Shipfox', injectTo: 'head'},\n {\n tag: 'link',\n attrs: {rel: 'icon', type: 'image/png', href: '/favicon-96x96.png', sizes: '96x96'},\n injectTo: 'head',\n },\n {\n tag: 'link',\n attrs: {rel: 'icon', type: 'image/svg+xml', href: '/favicon.svg'},\n injectTo: 'head',\n },\n {tag: 'link', attrs: {rel: 'shortcut icon', href: '/favicon.ico'}, injectTo: 'head'},\n {\n tag: 'link',\n attrs: {rel: 'apple-touch-icon', sizes: '180x180', href: '/apple-touch-icon.png'},\n injectTo: 'head',\n },\n {tag: 'link', attrs: {rel: 'manifest', href: '/site.webmanifest'}, injectTo: 'head'},\n {\n tag: 'meta',\n attrs: {name: 'apple-mobile-web-app-title', content: 'Shipfox'},\n injectTo: 'head',\n },\n {tag: 'meta', attrs: {name: 'theme-color', content: '#ff4b00'}, injectTo: 'head'},\n ];\n },\n },\n };\n}\n"],"names":["access","readFile","extname","resolve","fileURLToPath","assetNames","assetDirectory","URL","url","contentTypes","shipfoxClientManifest","config","assetPath","name","assertNoPublicAssetConflicts","publicDirectory","publicDir","conflicts","Promise","all","map","publicAsset","undefined","then","catch","filter","length","Error","join","configResolved","resolvedConfig","buildStart","command","emitFile","type","fileName","source","configureServer","server","middlewares","use","request","response","next","find","assetName","split","content","setHeader","end","error","transformIndexHtml","order","handler","tag","children","injectTo","attrs","rel","href","sizes"],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@shipfox/client-shell",
3
3
  "license": "MIT",
4
- "version": "2.0.0",
4
+ "version": "3.0.1",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+https://github.com/ShipfoxHQ/shipfox.git",
@@ -10,6 +10,10 @@
10
10
  "type": "module",
11
11
  "main": "dist/index.js",
12
12
  "types": "dist/index.d.ts",
13
+ "files": [
14
+ "dist",
15
+ "assets"
16
+ ],
13
17
  "imports": {
14
18
  "#*": "./dist/*",
15
19
  "#test/*": "./test/*"
@@ -38,10 +42,10 @@
38
42
  "tsx": "^4.22.4",
39
43
  "framer-motion": "^12.23.24",
40
44
  "@shipfox/api-auth-dto": "6.0.0",
41
- "@shipfox/client-api": "1.0.0",
42
45
  "@shipfox/api-workspaces-dto": "6.0.0",
43
- "@shipfox/client-config": "2.0.0",
44
- "@shipfox/react-ui": "0.3.3"
46
+ "@shipfox/client-api": "1.0.0",
47
+ "@shipfox/client-config": "3.0.1",
48
+ "@shipfox/react-ui": "0.3.5"
45
49
  },
46
50
  "peerDependencies": {
47
51
  "@storybook/react": "^10.4.6",
@@ -72,11 +76,13 @@
72
76
  "react-dom": "^19.1.1",
73
77
  "vite": "^8.0.3",
74
78
  "zod": "^4.4.3",
79
+ "@shipfox/application-release": "0.1.4",
75
80
  "@shipfox/biome": "1.8.2",
76
81
  "@shipfox/client-test-setup": "0.0.3",
77
82
  "@shipfox/swc": "1.2.6",
78
83
  "@shipfox/ts-config": "1.3.8",
79
84
  "@shipfox/typescript": "1.1.7",
85
+ "@shipfox/tool-utils": "1.2.0",
80
86
  "@shipfox/vitest": "1.2.3"
81
87
  },
82
88
  "scripts": {
@@ -84,7 +90,7 @@
84
90
  "check": "shipfox-biome-check",
85
91
  "check:fix": "shipfox-biome-check --write",
86
92
  "test": "shipfox-vitest-run",
87
- "test:external": "turbo build type:emit --filter=@shipfox/client-features... --filter=@shipfox/application-release && node test/external/verify.mjs",
93
+ "test:external": "node test/external/verify.mjs",
88
94
  "test:watch": "shipfox-vitest-watch",
89
95
  "type": "shipfox-tsc-check",
90
96
  "type:emit": "shipfox-tsc-emit"
@@ -1,2 +0,0 @@
1
- $ shipfox-swc
2
- Successfully compiled: 37 files with swc (577.08ms)
@@ -1 +0,0 @@
1
- $ shipfox-tsc-emit
@@ -1 +0,0 @@
1
- $ shipfox-tsc-check
package/CHANGELOG.md DELETED
@@ -1,59 +0,0 @@
1
- # Changelog
2
-
3
- ## 2.0.0
4
-
5
- ### Patch Changes
6
-
7
- - 7ac43a4: Consolidates packed-consumer validation around stable publication and composition contracts instead of package-state snapshots.
8
- - Updated dependencies [e6eba5b]
9
- - Updated dependencies [ba2e3dc]
10
- - Updated dependencies [1820feb]
11
- - Updated dependencies [1b79cda]
12
- - Updated dependencies [c2db8c3]
13
- - Updated dependencies [112c0fa]
14
- - Updated dependencies [326f4c0]
15
- - Updated dependencies [4a91956]
16
- - @shipfox/api-auth-dto@6.0.0
17
- - @shipfox/react-ui@0.3.3
18
- - @shipfox/api-workspaces-dto@6.0.0
19
- - @shipfox/client-config@2.0.0
20
-
21
- ## 1.0.0
22
-
23
- ### Patch Changes
24
-
25
- - 47809a2: Hardens packed client composition validation against release artifacts, undeclared route packages, and optional Storybook testing peers.
26
- - bb037af: Resolves workspace packages from source during development while published consumers continue to use compiled output.
27
- - 5c63a2a: Validates the published default client composition from a clean external Vite consumer and fixes typed access to the events settings route.
28
- - d8658ba: Prevents composition builds from reading resolver IDs that include Vite version queries.
29
- - Updated dependencies [bb037af]
30
- - @shipfox/api-auth-dto@5.0.0
31
- - @shipfox/api-workspaces-dto@5.0.0
32
- - @shipfox/client-api@1.0.0
33
- - @shipfox/client-config@1.0.0
34
- - @shipfox/react-ui@0.3.2
35
-
36
- ## 0.2.0
37
-
38
- ### Minor Changes
39
-
40
- - 3d064b8: Publishes the client runtime closure with shell, feature, route, Vite, and testing contracts.
41
- - 6bc2e45: Adds the composable upstream client shell, feature catalog, and route manifests for every client feature.
42
-
43
- ### Patch Changes
44
-
45
- - Updated dependencies [3d064b8]
46
- - @shipfox/client-api@0.2.0
47
- - @shipfox/client-config@0.2.0
48
-
49
- ## 0.0.2
50
-
51
- ### Patch Changes
52
-
53
- - Updated dependencies [c18d624]
54
- - @shipfox/react-ui@0.3.1
55
- - @shipfox/client-config@0.0.2
56
-
57
- ## 0.0.1
58
-
59
- - Add the private candidate client composition shell.
@@ -1,45 +0,0 @@
1
- import {FullPageLoader} from '@shipfox/react-ui/loader';
2
- import {Outlet, useMatches, useParams} from '@tanstack/react-router';
3
- import type {NavTabEntry} from '#contract.js';
4
- import {useMaybeActiveWorkspace} from '#runtime/active-workspace.js';
5
- import {NavBar} from './nav-bar.js';
6
- import {NavTabs} from './nav-tabs.js';
7
-
8
- declare module '@tanstack/react-router' {
9
- interface StaticDataRouteOption {
10
- layout?: 'full-bleed';
11
- }
12
- }
13
-
14
- export function MainLayout({
15
- navigation,
16
- hideProjectNavigation = false,
17
- }: {
18
- navigation: readonly NavTabEntry[];
19
- hideProjectNavigation?: boolean;
20
- }) {
21
- const workspace = useMaybeActiveWorkspace();
22
- const {pid} = useParams({strict: false}) as {pid?: string};
23
- const matches = useMatches();
24
- if (!workspace) return <FullPageLoader />;
25
- const fullBleed = matches.some((match) => match.staticData.layout === 'full-bleed');
26
- return (
27
- <div className="h-screen w-full flex flex-col bg-background-subtle-base">
28
- <NavBar hideProjectNavigation={hideProjectNavigation} />
29
- {hideProjectNavigation ? undefined : (
30
- <NavTabs entries={navigation} scope={pid ? 'project' : 'workspace'} />
31
- )}
32
- {fullBleed ? (
33
- <main className="flex-1 min-h-0 flex flex-col overflow-hidden">
34
- <Outlet />
35
- </main>
36
- ) : (
37
- <main className="flex-1 overflow-auto">
38
- <div className="max-w-[1120px] mx-auto px-24 py-32">
39
- <Outlet />
40
- </div>
41
- </main>
42
- )}
43
- </div>
44
- );
45
- }
@@ -1,41 +0,0 @@
1
- import {Logo} from '@shipfox/react-ui/logo';
2
- import {Link} from '@tanstack/react-router';
3
- import {useActiveWorkspace} from '#runtime/active-workspace.js';
4
- import {useChrome} from '#runtime/chrome-context.js';
5
- import {UserMenu} from './user-menu.js';
6
- import {WorkspaceCrumb} from './workspace-crumb.js';
7
-
8
- export function NavBar({hideProjectNavigation = false}: {hideProjectNavigation?: boolean}) {
9
- const workspace = useActiveWorkspace();
10
- const {ProjectBreadcrumb} = useChrome();
11
- return (
12
- <header className="sticky top-0 z-30 h-56 px-16 flex items-center gap-12 bg-background-subtle-base border-b border-border-neutral-base shrink-0">
13
- <Link
14
- to="/"
15
- aria-label="Shipfox home"
16
- className="rounded-6 focus-visible:outline-none focus-visible:shadow-button-neutral-focus"
17
- >
18
- {hideProjectNavigation ? (
19
- <>
20
- <Logo variant="mark" alt="" className="sm:hidden" />
21
- <Logo variant="wordmark" alt="" className="hidden sm:block" />
22
- </>
23
- ) : (
24
- <Logo variant="wordmark" alt="" />
25
- )}
26
- </Link>
27
- <span className="h-20 w-px bg-border-neutral-base" aria-hidden="true" />
28
- <WorkspaceCrumb workspace={workspace} compact={hideProjectNavigation} />
29
- {hideProjectNavigation ? undefined : (
30
- <>
31
- <span className="text-foreground-neutral-muted" aria-hidden="true">
32
- /
33
- </span>
34
- <ProjectBreadcrumb />
35
- </>
36
- )}
37
- <div className="flex-1" />
38
- <UserMenu />
39
- </header>
40
- );
41
- }
@@ -1,60 +0,0 @@
1
- import {
2
- createMemoryHistory,
3
- createRootRoute,
4
- createRoute,
5
- createRouter,
6
- Outlet,
7
- RouterProvider,
8
- } from '@tanstack/react-router';
9
- import {render, screen} from '@testing-library/react';
10
- import type {NavTabEntry} from '#contract.js';
11
- import {NavTabs} from './nav-tabs.js';
12
-
13
- const entries: readonly NavTabEntry[] = [
14
- {
15
- id: 'projects',
16
- scope: 'workspace',
17
- label: 'Projects',
18
- to: '/workspaces/$wid/projects',
19
- exact: true,
20
- },
21
- {
22
- id: 'settings',
23
- scope: 'workspace',
24
- label: 'Settings',
25
- to: '/workspaces/$wid/settings',
26
- },
27
- ];
28
-
29
- function WorkspaceTabs() {
30
- return <NavTabs entries={entries} scope="workspace" />;
31
- }
32
-
33
- describe('NavTabs', () => {
34
- test('distinguishes active and inactive workspace tabs', async () => {
35
- const rootRoute = createRootRoute({component: Outlet});
36
- const projectsRoute = createRoute({
37
- getParentRoute: () => rootRoute,
38
- path: '/workspaces/$wid/projects',
39
- component: WorkspaceTabs,
40
- });
41
- const settingsRoute = createRoute({
42
- getParentRoute: () => rootRoute,
43
- path: '/workspaces/$wid/settings',
44
- component: WorkspaceTabs,
45
- });
46
- const router = createRouter({
47
- history: createMemoryHistory({initialEntries: ['/workspaces/workspace/settings']}),
48
- routeTree: rootRoute.addChildren([projectsRoute, settingsRoute]),
49
- });
50
-
51
- render(<RouterProvider router={router} />);
52
-
53
- const projectsTab = await screen.findByRole('tab', {name: 'Projects'});
54
- const settingsTab = screen.getByRole('tab', {name: 'Settings'});
55
- expect(projectsTab).toHaveClass('text-foreground-neutral-muted');
56
- expect(projectsTab).toHaveAttribute('aria-selected', 'false');
57
- expect(settingsTab).toHaveClass('text-foreground-neutral-base');
58
- expect(settingsTab).toHaveAttribute('aria-selected', 'true');
59
- });
60
- });
@@ -1,47 +0,0 @@
1
- import {Link, useParams} from '@tanstack/react-router';
2
- import {useReducedMotion} from 'framer-motion';
3
- import type {NavTabEntry} from '#contract.js';
4
-
5
- export function NavTabs({
6
- entries,
7
- scope,
8
- }: {
9
- entries: readonly NavTabEntry[];
10
- scope: NavTabEntry['scope'];
11
- }) {
12
- const params = useParams({strict: false}) as {wid?: string; pid?: string};
13
- const reduced = useReducedMotion();
14
- const tabs = entries.filter((entry) => entry.scope === scope);
15
- const tabClassName = `h-40 inline-flex items-center px-4 text-sm font-medium transition-colors ${reduced ? '' : 'transition-[border-color]'}`;
16
- const activeProps = {
17
- className: 'border-b-2 border-border-highlights-interactive text-foreground-neutral-base',
18
- 'aria-selected': 'true' as const,
19
- };
20
- const inactiveProps = {
21
- className: 'border-b-2 border-transparent text-foreground-neutral-muted',
22
- 'aria-selected': 'false' as const,
23
- };
24
-
25
- return (
26
- <div
27
- role="tablist"
28
- aria-label={`${scope === 'project' ? 'Project' : 'Workspace'} sections`}
29
- className="sticky top-56 z-20 h-40 px-16 flex items-end gap-12 bg-background-subtle-base border-b border-border-neutral-base"
30
- >
31
- {tabs.map((entry) => (
32
- <Link
33
- key={entry.id}
34
- to={entry.to as never}
35
- params={params as never}
36
- role="tab"
37
- activeOptions={{exact: entry.exact ?? false}}
38
- activeProps={activeProps}
39
- inactiveProps={inactiveProps}
40
- className={tabClassName}
41
- >
42
- {entry.label}
43
- </Link>
44
- ))}
45
- </div>
46
- );
47
- }
@@ -1,14 +0,0 @@
1
- import {Header, Text} from '@shipfox/react-ui/typography';
2
- import {Link} from '@tanstack/react-router';
3
-
4
- export function NotFoundPage() {
5
- return (
6
- <main className="mx-auto max-w-[960px] px-24 py-48">
7
- <Header variant="h1">Page not found</Header>
8
- <Text size="md" className="text-foreground-neutral-muted">
9
- This Shipfox page does not exist.
10
- </Text>
11
- <Link to="/">Go home</Link>
12
- </main>
13
- );
14
- }
@@ -1,35 +0,0 @@
1
- import {Button} from '@shipfox/react-ui/button';
2
- import {Icon} from '@shipfox/react-ui/icon';
3
- import {Link, useMatchRoute, useParams} from '@tanstack/react-router';
4
- import type {SettingsSectionEntry} from '#contract.js';
5
-
6
- export function SettingsNav({entries}: {entries: readonly SettingsSectionEntry[]}) {
7
- const params = useParams({strict: false}) as {wid?: string};
8
- const matchRoute = useMatchRoute();
9
- if (!params.wid) return null;
10
- return (
11
- <nav aria-label="Workspace settings" className="flex flex-col gap-4">
12
- {entries.map((entry) => {
13
- const to = `/workspaces/$wid/settings/${entry.pathSegment}`;
14
- const active = Boolean(matchRoute({to: to as never, params: {wid: params.wid} as never}));
15
- return (
16
- <Button
17
- key={entry.id}
18
- asChild
19
- variant={active ? 'secondary' : 'transparent'}
20
- className="w-full justify-start"
21
- >
22
- <Link
23
- to={to as never}
24
- params={{wid: params.wid} as never}
25
- aria-current={active ? 'page' : undefined}
26
- >
27
- <Icon name={entry.icon} className="size-16" />
28
- {entry.label}
29
- </Link>
30
- </Button>
31
- );
32
- })}
33
- </nav>
34
- );
35
- }
@@ -1,59 +0,0 @@
1
- import {Avatar} from '@shipfox/react-ui/avatar';
2
- import {
3
- DropdownMenu,
4
- DropdownMenuContent,
5
- DropdownMenuItem,
6
- DropdownMenuLabel,
7
- DropdownMenuRadioGroup,
8
- DropdownMenuRadioItem,
9
- DropdownMenuSeparator,
10
- DropdownMenuTrigger,
11
- } from '@shipfox/react-ui/dropdown-menu';
12
- import {useTheme} from '@shipfox/react-ui/hooks';
13
- import type {Theme} from '@shipfox/react-ui/theme';
14
- import {Link} from '@tanstack/react-router';
15
- import {useAuthState} from '#runtime/auth.js';
16
-
17
- const themeOptions: Array<{value: Theme; label: string}> = [
18
- {value: 'light', label: 'Light'},
19
- {value: 'dark', label: 'Dark'},
20
- {value: 'system', label: 'System'},
21
- ];
22
- export function UserMenu() {
23
- const {user} = useAuthState();
24
- const {theme, setTheme} = useTheme();
25
- const email = user?.email ?? '';
26
- return (
27
- <DropdownMenu>
28
- <DropdownMenuTrigger asChild>
29
- <button
30
- type="button"
31
- aria-label="User menu"
32
- className="rounded-full focus-visible:outline-none focus-visible:shadow-button-neutral-focus"
33
- >
34
- <Avatar size="sm" content="letters" fallback={email} />
35
- </button>
36
- </DropdownMenuTrigger>
37
- <DropdownMenuContent align="end" sideOffset={8} className="min-w-[220px]">
38
- <DropdownMenuLabel className="text-xs text-foreground-neutral-muted truncate">
39
- {email}
40
- </DropdownMenuLabel>
41
- <DropdownMenuSeparator />
42
- <DropdownMenuLabel className="text-xs text-foreground-neutral-muted">
43
- Theme
44
- </DropdownMenuLabel>
45
- <DropdownMenuRadioGroup value={theme} onValueChange={(value) => setTheme(value as Theme)}>
46
- {themeOptions.map((option) => (
47
- <DropdownMenuRadioItem key={option.value} value={option.value}>
48
- {option.label}
49
- </DropdownMenuRadioItem>
50
- ))}
51
- </DropdownMenuRadioGroup>
52
- <DropdownMenuSeparator />
53
- <DropdownMenuItem asChild>
54
- <Link to={'/auth/logout' as never}>Logout</Link>
55
- </DropdownMenuItem>
56
- </DropdownMenuContent>
57
- </DropdownMenu>
58
- );
59
- }
@@ -1,43 +0,0 @@
1
- import {Icon} from '@shipfox/react-ui/icon';
2
- import {Popover, PopoverContent, PopoverTrigger} from '@shipfox/react-ui/popover';
3
- import {Link} from '@tanstack/react-router';
4
- import {useState} from 'react';
5
- import type {Workspace} from '#runtime/auth.js';
6
- import {WorkspaceSwitcher} from './workspace-switcher.js';
7
-
8
- export interface WorkspaceCrumbProps {
9
- workspace: Workspace;
10
- compact?: boolean;
11
- }
12
-
13
- export function WorkspaceCrumb({workspace, compact = false}: WorkspaceCrumbProps) {
14
- const [open, setOpen] = useState(false);
15
- return (
16
- <div className="flex items-center">
17
- <Link
18
- to="/workspaces/$wid"
19
- params={{wid: workspace.id}}
20
- aria-current="page"
21
- className={`inline-block text-md font-medium text-foreground-neutral-base px-6 py-4 rounded-6 hover:bg-background-components-hover transition-colors truncate ${compact ? 'max-w-[120px] sm:max-w-[200px]' : 'max-w-[200px]'}`}
22
- >
23
- {workspace.name}
24
- </Link>
25
- <Popover open={open} onOpenChange={setOpen}>
26
- <PopoverTrigger asChild>
27
- <button
28
- type="button"
29
- aria-label="Switch workspace"
30
- aria-haspopup="listbox"
31
- aria-expanded={open}
32
- className="ml-2 grid place-items-center size-24 rounded-4 text-foreground-neutral-muted hover:bg-background-components-hover hover:text-foreground-neutral-base transition-colors"
33
- >
34
- <Icon name="arrowDownSLine" className="size-16" />
35
- </button>
36
- </PopoverTrigger>
37
- <PopoverContent className="w-[280px] p-0" align="start" sideOffset={8}>
38
- <WorkspaceSwitcher activeWorkspaceId={workspace.id} onSelect={() => setOpen(false)} />
39
- </PopoverContent>
40
- </Popover>
41
- </div>
42
- );
43
- }