1
0
mirror of https://github.com/matrix-org/matrix-authentication-service.git synced 2025-07-31 09:24:31 +03:00

frontend: start using CSS modules for components with design tokens

This commit is contained in:
Quentin Gliech
2023-08-03 18:28:43 +02:00
parent 7c2e691175
commit c8ba2a1fa3
17 changed files with 229 additions and 102 deletions

View File

@ -0,0 +1,21 @@
/* Copyright 2023 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.block {
background-color: var(--cpd-color-bg-subtle-secondary);
color: var(--cpd-color-text-primary);
padding: var(--cpd-space-4x);
border-radius: var(--cpd-space-2x);
}

View File

@ -12,15 +12,18 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import styles from "./Block.module.css";
type Props = { type Props = {
children: React.ReactNode;
highlight?: boolean; highlight?: boolean;
className?: string;
}; };
const Block: React.FC<Props> = ({ children, highlight, className }) => { const Block: React.FC<React.PropsWithChildren<Props>> = ({
children,
highlight,
}) => {
return ( return (
<div className={className} data-active={highlight}> <div className={styles.block} data-active={highlight}>
{children} {children}
</div> </div>
); );

View File

@ -0,0 +1,21 @@
/* Copyright 2023 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.block-list {
display: grid;
grid-template-columns: 1fr;
gap: var(--cpd-space-4x);
align-content: flex-start;
}

View File

@ -12,14 +12,10 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
type Props = { import styles from "./BlockList.module.css";
children: React.ReactNode;
};
const BlockList: React.FC<Props> = ({ children }) => { const BlockList: React.FC<React.PropsWithChildren<{}>> = ({ children }) => {
return ( return <div className={styles.blockList}>{children}</div>;
<div className="grid grid-cols-1 gap-4 group content-start">{children}</div>
);
}; };
export default BlockList; export default BlockList;

View File

@ -0,0 +1,24 @@
/* Copyright 2023 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.session-icon {
color: var(--cpd-color-icon-secondary);
background: var(--cpd-color-bg-subtle-secondary);
height: var(--cpd-space-10x);
width: var(--cpd-space-10x);
padding: var(--cpd-space-2x);
border-radius: var(--cpd-space-1x);
margin-right: var(--cpd-space-2x);
}

View File

@ -22,7 +22,7 @@ import { useTransition } from "react";
import { currentBrowserSessionIdAtom, currentUserIdAtom } from "../atoms"; import { currentBrowserSessionIdAtom, currentUserIdAtom } from "../atoms";
import { FragmentType, graphql, useFragment } from "../gql"; import { FragmentType, graphql, useFragment } from "../gql";
import Block from "./Block"; import styles from "./BrowserSession.module.css";
import DateTime from "./DateTime"; import DateTime from "./DateTime";
const FRAGMENT = graphql(/* GraphQL */ ` const FRAGMENT = graphql(/* GraphQL */ `
@ -92,8 +92,8 @@ const BrowserSession: React.FC<Props> = ({ session, isCurrent }) => {
}; };
return ( return (
<Block className="flex items-center"> <div className="flex items-center">
<IconWebBrowser className="mr-4 session-icon" /> <IconWebBrowser className={styles.sessionIcon} />
<div className="flex-1"> <div className="flex-1">
<Body size="md" weight="medium"> <Body size="md" weight="medium">
{isCurrent ? ( {isCurrent ? (
@ -118,7 +118,7 @@ const BrowserSession: React.FC<Props> = ({ session, isCurrent }) => {
> >
Sign out Sign out
</Button> </Button>
</Block> </div>
); );
}; };

View File

@ -82,7 +82,7 @@ const CompatSession: React.FC<{
}; };
return ( return (
<Block className="p-4 bg-grey-25 dark:bg-grey-450 rounded-lg"> <Block>
<Body> <Body>
Started: <DateTime datetime={data.createdAt} /> Started: <DateTime datetime={data.createdAt} />
</Body> </Body>

View File

@ -0,0 +1,31 @@
/* Copyright 2023 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.container {
width: 378px;
margin: var(--cpd-space-10x) auto var(--cpd-space-6x) auto;
}
.footer {
text-align: center;
}
.separator {
border: 0;
height: 1px;
background: var(--cpd-color-border-interactive-secondary);
margin: var(--cpd-space-4x) 0;
}

View File

@ -12,24 +12,27 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import styles from "./Layout.module.css";
import NavBar from "./NavBar"; import NavBar from "./NavBar";
import NavItem from "./NavItem"; import NavItem, { ExternalLink } from "./NavItem";
const Separator: React.FC = () => <hr className={styles.separator} />;
const Layout: React.FC<{ children?: React.ReactNode }> = ({ children }) => { const Layout: React.FC<{ children?: React.ReactNode }> = ({ children }) => {
return ( return (
<> <div className={styles.container}>
<NavBar className="nav-bar container"> <NavBar>
<NavItem route={{ type: "home" }}>Sessions</NavItem> <NavItem route={{ type: "home" }}>Sessions</NavItem>
<NavItem route={{ type: "account" }}>Emails</NavItem> <NavItem route={{ type: "account" }}>Emails</NavItem>
</NavBar> </NavBar>
<hr className="my-2" /> <Separator />
<main className="container">{children}</main> <main>{children}</main>
<hr className="my-2" /> <Separator />
<footer className="text-center"> <footer className={styles.footer}>
<a href="https://matrix.org" target="_blank" rel="noreferrer noopener"> <a href="https://matrix.org" target="_blank" rel="noreferrer noopener">
<img <img
className="inline my-2" className="inline my-2"
@ -40,19 +43,19 @@ const Layout: React.FC<{ children?: React.ReactNode }> = ({ children }) => {
/> />
</a> </a>
<NavBar className="nav-bar container"> <NavBar>
<NavItem href="https://matrix.org/legal/copyright-notice"> <ExternalLink href="https://matrix.org/legal/copyright-notice">
Info Info
</NavItem> </ExternalLink>
<NavItem href="https://matrix.org/legal/privacy-notice"> <ExternalLink href="https://matrix.org/legal/privacy-notice">
Privacy Privacy
</NavItem> </ExternalLink>
<NavItem href="https://matrix.org/legal/terms-and-conditions"> <ExternalLink href="https://matrix.org/legal/terms-and-conditions">
Terms & Conditions Terms & Conditions
</NavItem> </ExternalLink>
</NavBar> </NavBar>
</footer> </footer>
</> </div>
); );
}; };

View File

@ -0,0 +1,21 @@
/* Copyright 2023 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.nav-bar-items {
display: flex;
flex-direction: row;
justify-content: center;
gap: var(--cpd-space-4x);
}

View File

@ -12,12 +12,11 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
const NavBar: React.FC<{ import styles from "./NavBar.module.css";
className: string;
children: React.ReactNode; const NavBar: React.FC<React.PropsWithChildren<{}>> = ({ children }) => (
}> = ({ className, children }) => ( <nav>
<nav className={className}> <ul className={styles.navBarItems}>{children}</ul>
<ul className="flex flex-row gap-4 justify-center">{children}</ul>
</nav> </nav>
); );

View File

@ -0,0 +1,30 @@
/* Copyright 2023 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.nav-item {
padding: var(--cpd-space-1x) var(--cpd-space-2x);
color: var(--cpd-color-text-secondary);
line-height: var(--cpd-space-6x);
border-radius: var(--cpd-radius-pill-effect);
}
.nav-item:hover {
color: var(--cpd-color-text-primary);
background-color: var(--cpd-color-bg-subtle-secondary);
}
.nav-item[aria-current="page"] {
color: var(--cpd-color-text-primary);
}

View File

@ -12,41 +12,37 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import { Link as CpdLink } from "@vector-im/compound-web";
import { useAtomValue } from "jotai"; import { useAtomValue } from "jotai";
import { Link, Route, routeAtom } from "../Router"; import { Link, Route, routeAtom } from "../Router";
type NavItemProps = { import styles from "./NavItem.module.css";
children: React.ReactNode;
} & ({ route: Route; href?: never } | { route?: never; href: string });
function isRoute(route: Route | undefined): route is Route { const NavItem: React.FC<React.PropsWithChildren<{ route: Route }>> = ({
return !!route?.type; route,
} children,
}) => {
function isHref(href: string | undefined): href is string {
return typeof href === "string";
}
const NavItem: React.FC<NavItemProps> = ({ route, href, children }) => {
const currentRoute = useAtomValue(routeAtom); const currentRoute = useAtomValue(routeAtom);
return ( return (
<li className="m-1 mr-0"> <li>
{isRoute(route) && ( <Link
<Link className={styles.navItem}
route={route} route={route}
className={currentRoute.type === route.type ? "active" : ""} aria-current={currentRoute.type === route.type ? "page" : undefined}
> >
{children} {children}
</Link> </Link>
)}
{isHref(href) && (
<a href={href} target="_blank" rel="noopenner noreferrer">
{children}
</a>
)}
</li> </li>
); );
}; };
export const ExternalLink: React.FC<
React.PropsWithChildren<{ href: string }>
> = ({ href, children }) => (
<li>
<CpdLink href={href}>{children}</CpdLink>
</li>
);
export default NavItem; export default NavItem;

View File

@ -99,13 +99,7 @@ const OAuth2Session: React.FC<Props> = ({ session }) => {
}; };
return ( return (
<Block <Block>
className={`p-4 bg-grey-25 dark:bg-grey-450 rounded-lg ${
data.finishedAt
? "opacity-50 group-hover:opacity-100 transition-opacity"
: ""
}`}
>
<Typography variant="body" bold> <Typography variant="body" bold>
<Link <Link
route={{ type: "client", id: data.client.id }} route={{ type: "client", id: data.client.id }}

View File

@ -25,37 +25,10 @@
@tailwind utilities; @tailwind utilities;
body { body {
/* XXX: I'm unsure why this is not part of the tokens */
--cpd-radius-pill-effect: 9999px;
font: var(--cpd-font-body-md-regular); font: var(--cpd-font-body-md-regular);
background: var(--cpd-color-bg-canvas-default); background: var(--cpd-color-bg-canvas-default);
color: var(--cpd-color-text-primary);
width: 378px; }
margin: var(--cpd-space-10x) auto var(--cpd-space-6x) auto;
}
.nav-bar a {
color: var(--cpd-color-text-action-primary);
}
.nav-bar .active {
font-weight: var(--cpd-font-weight-semibold);
}
.nav-bar li:not(:first-child) {
list-style-type: disc;
color: var(--cpd-color-text-secondary);
}
hr {
border: 0;
height: 1px;
background: var(--cpd-color-gray-400);
}
.session-icon {
color: var(--cpd-color-icon-secondary);
background: var(--cpd-color-bg-subtle-secondary);
height: var(--cpd-space-10x);
width: var(--cpd-space-10x);
padding: var(--cpd-space-2x);
border-radius: var(--cpd-space-1x);
}

View File

@ -42,6 +42,11 @@ module.exports = {
"black-950": "#21262C", "black-950": "#21262C",
ice: "#F4F9FD", ice: "#F4F9FD",
}, },
fontWeight: {
semibold: "var(--cpd-font-weight-semibold)",
medium: "var(--cpd-font-weight-medium)",
regular: "var(--cpd-font-weight-regular)",
},
}, },
variants: { variants: {
extend: {}, extend: {},

View File

@ -23,6 +23,13 @@ import { defineConfig } from "vitest/config";
export default defineConfig((env) => ({ export default defineConfig((env) => ({
base: "./", base: "./",
css: {
modules: {
localsConvention: "camelCaseOnly",
},
},
build: { build: {
manifest: true, manifest: true,
assetsDir: "", assetsDir: "",
@ -37,6 +44,7 @@ export default defineConfig((env) => ({
], ],
}, },
}, },
plugins: [ plugins: [
codegen(), codegen(),
@ -104,6 +112,7 @@ export default defineConfig((env) => ({
ext: ".zz", ext: ".zz",
}), }),
], ],
server: { server: {
base: "/account/", base: "/account/",
proxy: { proxy: {
@ -112,6 +121,7 @@ export default defineConfig((env) => ({
"http://127.0.0.1:8080", "http://127.0.0.1:8080",
}, },
}, },
test: { test: {
coverage: { coverage: {
provider: "v8", provider: "v8",