Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"@fluentui/react-charts": "^9.3.22",
"@fluentui/react-components": "^9.74.4",
"@fluentui/react-icons": "^2.0.334",
"@fontsource-variable/cascadia-code": "^5.3.0",
"@hookform/resolvers": "^5.5.7",
"@react-router/node": "8.3.0",
"d3-shape": "^3.2.0",
Expand Down Expand Up @@ -46,6 +47,7 @@
"@types/prismjs": "^1.26.6",
"@types/react": "^19.2.17",
"@types/react-dom": "^19.2.3",
"@unocss/postcss": "^66.7.5",
"happy-dom": "^20.11.1",
"unocss": "^66.7.5",
"vite": "^8.1.5"
Expand Down
25 changes: 25 additions & 0 deletions apps/web/postcss.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import UnoCSS from '@unocss/postcss';

// UnoCSS generates through PostCSS rather than `unocss/vite`, whose global mode
// emits nothing under React Router: it keys its `vite:css-post` handle by the
// top-level `build.outDir`, while React Router sets `outDir` only per
// environment (`dist/client`, `dist/server`) and opts into
// `builder.sharedConfigBuild`, which is what otherwise re-resolves the config
// per environment. The lookup misses and the layer placeholder ships as the
// entire stylesheet.
// https://github.com/unocss/unocss/issues/4990
// https://github.com/unocss/unocss/blob/e28a47c557fe179935a37a4fbeb650292d0d1d5a/packages-integrations/vite/src/modes/global/build.ts#L128-L182
//
// That plugin's per-module mode does emit, but generates one sheet per module,
// and concatenating them in module-graph order breaks the cascade: a breakpoint
// variant can land ahead of the base utility it has to override, and since a
// media query adds no specificity the base utility wins at every width. Rule
// order is a property of a single `generate()` call, which is what one PostCSS
// pass over the whole content set gives us.
//
// `cwd` resolves both uno.config.ts discovery and the `content.filesystem`
// globs, and defaults to the build process's working directory. Pinning it here
// keeps a build launched from the workspace root scanning the same files as one
// launched from this package.
// https://github.com/unocss/unocss/blob/e28a47c557fe179935a37a4fbeb650292d0d1d5a/packages-integrations/postcss/src/esm.ts#L21-L110
export default { plugins: [UnoCSS({ cwd: import.meta.dirname })] };
54 changes: 32 additions & 22 deletions apps/web/src/components/api-docs/api-docs-content.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ArrowUpRight16Regular } from '@fluentui/react-icons';
import { useState } from 'react';
import { Fragment, useState } from 'react';
import { useTranslation } from 'react-i18next';

import { apiDocsEndpoints, apiDocsExamples, apiDocsGroups, authCurlExample, type ApiDocsExampleId } from './api-docs-data';
Expand All @@ -13,6 +13,7 @@ const {
AccordionHeader,
AccordionItem,
AccordionPanel,
Badge,
Link,
MessageBar,
MessageBarBody,
Expand Down Expand Up @@ -65,23 +66,29 @@ export function ApiDocsContent() {
<Text as="h2" size={500} weight="semibold" className="!m-0">{t('dashboard.apiDocs.endpointsTitle')}</Text>
<Text size={300} className="text-fui-fg2">{t('dashboard.apiDocs.endpointsDescription')}</Text>
</div>
{apiDocsGroups.map(group => <section className="grid gap-2" key={group}>
<Text as="h3" size={300} weight="semibold" className="!m-0">{t(`dashboard.apiDocs.groups.${group}`)}</Text>
<ScrollArea axes="horizontal" className="min-w-0">
<div className="grid min-w-[780px]">
{apiDocsEndpoints.filter(endpoint => endpoint.group === group).map(endpoint => (
<div className="grid grid-cols-[54px_minmax(260px,1fr)_minmax(190px,auto)_74px] items-center gap-3 py-2 px-2 border-b border-fui-subtle last:border-b-0" key={`${endpoint.method} ${endpoint.path}`}>
<MethodBadge method={endpoint.method} />
<code className="font-mono text-xs">{endpoint.path}</code>
<Text size={200}>{t(`dashboard.apiDocs.endpointNames.${endpoint.name}`)}</Text>
<Link href={endpoint.docs} target="_blank">
{t('dashboard.apiDocs.docsLink')} <ArrowUpRight16Regular aria-hidden="true" />
</Link>
</div>
))}
</div>
</ScrollArea>
</section>)}
{apiDocsGroups.map(group => {
const endpoints = apiDocsEndpoints.filter(endpoint => endpoint.group === group);
return <section className="grid gap-2" key={group}>
<Text as="h3" size={300} weight="semibold" className="!m-0">{t(`dashboard.apiDocs.groups.${group}`)}</Text>
<ScrollArea axes="horizontal" className="min-w-0">
<div className="grid grid-cols-[70px_minmax(360px,1fr)_minmax(260px,320px)_74px] min-w-[780px]">
{endpoints.map((endpoint, index) => {
const cellClassName = `min-w-0 px-2 py-2 ${index === endpoints.length - 1 ? '' : 'border-b border-fui-subtle'}`;
return <Fragment key={`${endpoint.method} ${endpoint.path}`}>
<div className={`flex items-center ${cellClassName}`}><MethodBadge method={endpoint.method} /></div>
<code className={`${cellClassName} flex items-center font-mono text-xs`} translate="no">{endpoint.path}</code>
<Text className={`${cellClassName} !flex !items-center`} size={200}>{t(`dashboard.apiDocs.endpointNames.${endpoint.name}`)}</Text>
<div className={`flex items-center ${cellClassName}`}>
<Link href={endpoint.docs} target="_blank">
{t('dashboard.apiDocs.docsLink')} <ArrowUpRight16Regular aria-hidden="true" />
</Link>
</div>
</Fragment>;
})}
</div>
</ScrollArea>
</section>;
})}
</Panel>

<Panel className="grid gap-3 !p-[22px_24px] max-[680px]:!p-[18px]">
Expand Down Expand Up @@ -110,8 +117,11 @@ export function ApiDocsContent() {
}

function MethodBadge({ method }: { method: 'GET' | 'POST' }) {
return <span className="rounded inline-flex font-mono text-[11px] font-bold justify-center leading-none p-[4px_7px] w-[46px]" style={{
color: method === 'GET' ? 'light-dark(#0f6cbd, #75b6f7)' : 'light-dark(#107c41, #7fd99a)',
background: method === 'GET' ? 'light-dark(#e6f2fb, rgba(71,158,245,0.18))' : 'light-dark(#e8f5ee, rgba(84,179,111,0.18))',
}}>{method}</span>;
return <Badge
appearance="tint"
className="!font-bold !font-mono !justify-center !min-w-[48px]"
color={method === 'GET' ? 'brand' : 'success'}
size="small"
translate="no"
>{method}</Badge>;
}
4 changes: 2 additions & 2 deletions apps/web/src/components/requests/request-detail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ const useStyles = makeStyles({
code: {
backgroundColor: 'var(--colorNeutralBackground1)',
color: 'var(--colorNeutralForeground1)',
fontFamily: 'monospace',
fontFamily: 'var(--fontFamilyMonospace)',
fontSize: '12px',
lineHeight: 1.55,
margin: 0,
Expand All @@ -53,7 +53,7 @@ const useStyles = makeStyles({
tabSize: 2,
whiteSpace: 'pre',
},
headers: { borderCollapse: 'collapse', fontFamily: 'monospace', fontSize: '12px', width: '100%' },
headers: { borderCollapse: 'collapse', fontFamily: 'var(--fontFamilyMonospace)', fontSize: '12px', width: '100%' },
headerRow: { borderBottom: '1px solid var(--colorNeutralStroke3)' },
headerName: { color: 'var(--colorNeutralForeground3)', padding: '7px 14px 7px 16px', textAlign: 'left', verticalAlign: 'top', whiteSpace: 'nowrap', width: '190px' },
headerValue: { color: 'var(--colorNeutralForeground1)', padding: '7px 16px 7px 0', verticalAlign: 'top', whiteSpace: 'nowrap' },
Expand Down
10 changes: 7 additions & 3 deletions apps/web/src/components/ui/code-block.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,15 @@ const useStyles = makeStyles({
},
lang: {
color: 'var(--colorNeutralForeground2)',
fontFamily: 'monospace',
fontFamily: 'var(--fontFamilyMonospace)',
fontSize: '12px',
},
// The Prism themes set the code font through `pre[class*="language-"]`,
// which outranks a single-class rule, so the theme's monospace family has to
// be overridden outright.
// https://github.com/PrismJS/prism-themes/blob/v1.9.0/themes/prism-vs.css#L5-L10
pre: {
fontFamily: 'monospace',
fontFamily: 'var(--fontFamilyMonospace) !important',
fontSize: '12px',
lineHeight: '1.55',
margin: 0,
Expand All @@ -47,7 +51,7 @@ const useStyles = makeStyles({
'& .token.table': {
display: 'inline',
},
fontFamily: 'monospace',
fontFamily: 'var(--fontFamilyMonospace) !important',
whiteSpace: 'pre',
},
});
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/upstream-editor/model-detail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const {

const useStyles = makeStyles({
endpointLabel: {
fontFamily: 'monospace !important',
fontFamily: 'var(--fontFamilyMonospace) !important',
},
});

Expand Down
4 changes: 2 additions & 2 deletions apps/web/src/components/upstream-editor/provider-config.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,10 @@ const useStyles = makeStyles({
marginLeft: '-8px',
},
endpointCheckbox: {
fontFamily: 'monospace !important',
fontFamily: 'var(--fontFamilyMonospace) !important',
},
pathOverrideLabel: {
fontFamily: 'monospace !important',
fontFamily: 'var(--fontFamilyMonospace) !important',
fontSize: 'var(--fontSizeBase200) !important',
},
});
Expand Down
3 changes: 2 additions & 1 deletion apps/web/src/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ import { scrollAreaCss } from './components/ui/scroll-area';
import { fluentComponents } from './fluent';
import { flowayDarkTheme, flowayLightTheme } from './theme';
import './i18n';
import 'virtual:uno.css';
import '@fontsource-variable/cascadia-code';
import './uno.css';

const { FluentProvider } = fluentComponents;

Expand Down
13 changes: 10 additions & 3 deletions apps/web/src/theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,17 @@ export const australianBrand: BrandVariants = {
160: '#f7faff',
};

// Cascadia Code is Microsoft's own coding typeface and the one Fluent surfaces
// pair with; `monospace` alone resolves to Courier on macOS, which reads
// nothing like the rest of the dashboard. The bundled variable face is named
// with the Fontsource `Variable` suffix, so locally installed Cascadia builds
// come next, then the platform coding faces.
// https://github.com/microsoft/cascadia-code/blob/2404.23/README.md#cascadia-code
// https://github.com/fontsource/font-files/blob/main/fonts/variable/cascadia-code/README.md#cascadia-code
const monospaceStack = "'Cascadia Code Variable', 'Cascadia Code', 'Cascadia Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace";

const fontOverrides = {
fontFamilyBase: 'sans-serif',
fontFamilyMonospace: 'monospace',
fontFamilyNumeric: 'sans-serif',
fontFamilyMonospace: monospaceStack,
fontSizeBase100: '10px',
fontSizeBase200: '12px',
fontSizeBase300: '14px',
Expand Down
1 change: 1 addition & 0 deletions apps/web/src/uno.css
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@unocss;
1 change: 1 addition & 0 deletions apps/web/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"react-router.config.ts",
"vite.config.ts",
"vitest.config.ts",
"postcss.config.ts",
"uno.config.ts"
]
}
10 changes: 8 additions & 2 deletions apps/web/uno.config.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { defineConfig, presetWind3, transformerDirectives, transformerVariantGroup } from 'unocss';
import { defineConfig, presetWind3 } from 'unocss';

export default defineConfig({
presets: [
presetWind3(),
],
theme: {
fontFamily: {
mono: 'var(--fontFamilyMonospace)',
},
fontSize: {
'fui-base200': 'var(--fontSizeBase200)',
'fui-base300': 'var(--fontSizeBase300)',
Expand Down Expand Up @@ -32,7 +35,10 @@ export default defineConfig({
['font-fui-medium', { 'font-weight': 'var(--fontWeightMedium)' }],
['font-fui-semibold', { 'font-weight': 'var(--fontWeightSemibold)' }],
],
transformers: [transformerDirectives(), transformerVariantGroup()],
// The PostCSS integration reads these globs itself and never sees the module
// graph, so a class only ships if a file here spells it out. It also applies
// no source-level transformers: utilities must appear verbatim in the source,
// not as variant groups.
content: {
filesystem: ['src/**/*.{ts,tsx}'],
},
Expand Down
3 changes: 1 addition & 2 deletions apps/web/vite.config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { reactRouter } from '@react-router/dev/vite';
import UnoCSS from 'unocss/vite';
import { defineConfig, type Plugin } from 'vite';

// Prism ships its language components as scripts that mutate a global `Prism`
Expand Down Expand Up @@ -87,7 +86,7 @@ export default defineConfig({
'zustand',
],
},
plugins: [prismComponentsEsm(), UnoCSS({ mode: 'per-module' }), reactRouter()],
plugins: [prismComponentsEsm(), reactRouter()],
server: {
port: 5174,
proxy: Object.fromEntries(wranglerProxiedPaths.map(p => [p, { target: wranglerOrigin, changeOrigin: true }])),
Expand Down
33 changes: 30 additions & 3 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.