feat(cdr): Initial UI scaffolding

This is testing out [Approach 3](https://www.notion.so/coderhq/Workspaces-v2-Initial-UI-Scaffolding-3b07d2847eed48839a7e6f0f2bb9bf56#56256f25d2954897a8ee315f0820cedd) in the UI scaffolding RFC.

Fixes https://github.com/coder/coder/issues/11

The folder structure looks like:
- `site`
    - `components` (buttons, empty state, etc)
    - `pages` (large sections of UI -> composition of components)
    - `theme` (files defining our palette)

Several components were able to be brought in essentially unmodified:
- `SplitButton`
- `EmptyState`
- `Footer`
-  All the icons / logos
- Theming (removed several items that aren't necessary, yet, though)

Other components had more coupling, and need more refactoring:
- `NavBar`
- `Confetti`

Current State:

![2022-01-06 17 16 31](https://user-images.githubusercontent.com/88213859/148475521-96e080cc-1d33-4b8e-a434-29e388936e3f.gif)

For a full working app, there's potentially a lot more to bring in:
- User / Account Settings Stuff
- Users Page
- Organizations Page
(and all the supporting dependencies)
This commit is contained in:
Bryan 2022-01-12 14:25:12 -08:00 committed by GitHub
parent 0778f3e738
commit ace89161fb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 2977 additions and 7 deletions

View File

@ -148,6 +148,6 @@ jobs:
with:
node-version: "14"
# Check that node is available
# TODO: Implement actual test run
- run: node --version
- run: yarn install
- run: yarn build

7
.gitignore vendored
View File

@ -11,4 +11,9 @@
node_modules
vendor
.eslintcache
.eslintcache
yarn-error.log
# Front-end ignore
.next/
site/.next/

View File

@ -4,5 +4,12 @@
# https://github.com/prettier/prettier/issues/8506
# https://github.com/prettier/prettier/issues/8679
###############################################################################
node_modules
vendor
vendor
.eslintcache
yarn-error.log
# Front-end ignore
.next/
site/.next/

View File

@ -11,6 +11,7 @@ This repository contains source code for Coder V2. Additional documentation:
- `.github/`: Settings for [Dependabot for updating dependencies](https://docs.github.com/en/code-security/supply-chain-security/customizing-dependency-updates) and [build/deploy pipelines with GitHub Actions](https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions).
- [`semantic.yaml`](./github/semantic.yaml): Configuration for [semantic pull requests](https://github.com/apps/semantic-pull-requests)
- `site`: Front-end UI code.
## Front-End Plan

View File

@ -4,10 +4,25 @@
"repository": "https://github.com/coder/coder",
"private": true,
"scripts": {
"build": "NODE_ENV=production next build site",
"build:dev": "next build site",
"dev": "next dev site",
"format:check": "prettier --check '**/*.{css,html,js,json,jsx,md,ts,tsx,yaml,yml}'",
"format:write": "prettier --write '**/*.{css,htmljs,json,jsx,md,ts,tsx,yaml,yml}'"
"format:write": "prettier --write '**/*.{css,html,js,json,jsx,md,ts,tsx,yaml,yml}'"
},
"devDependencies": {
"prettier": "2.5.1"
"@material-ui/core": "4.9.4",
"@material-ui/icons": "4.5.1",
"@material-ui/lab": "4.0.0-alpha.42",
"@types/node": "14.18.4",
"@types/react": "17.0.38",
"@types/react-dom": "17.0.11",
"@types/superagent": "4.1.14",
"next": "12.0.7",
"prettier": "2.5.1",
"react": "17.0.2",
"react-dom": "17.0.2",
"ts-loader": "9.2.6",
"typescript": "4.5.4"
}
}

View File

@ -0,0 +1,125 @@
import Button, { ButtonProps } from "@material-ui/core/Button"
import ButtonGroup from "@material-ui/core/ButtonGroup"
import ClickAwayListener from "@material-ui/core/ClickAwayListener"
import Grow from "@material-ui/core/Grow"
import MenuItem from "@material-ui/core/MenuItem"
import MenuList from "@material-ui/core/MenuList"
import Paper from "@material-ui/core/Paper"
import Popper from "@material-ui/core/Popper"
import ArrowDropDownIcon from "@material-ui/icons/ArrowDropDown"
import React, { useRef, useState } from "react"
interface SplitButtonOptions<T> {
/**
* label is shown in the SplitButton UI
*/
label: string
/**
* value is any value for this option
*/
value: T
}
export interface SplitButtonProps<T> extends Pick<ButtonProps, "color" | "disabled" | "startIcon"> {
/**
* onClick is called with the selectedOption
*/
onClick: (selectedOption: T) => void
/**
* options is a list of options
*/
options: SplitButtonOptions<T>[]
/**
* textTransform is applied to the primary button text. Defaults to
* uppercase
*/
textTransform?: React.CSSProperties["textTransform"]
}
/**
* SplitButton is a button with a primary option and a dropdown with secondary
* options.
* @remark The primary option is the 0th index (first option) in the array.
* @see https://mui.com/components/button-group/#split-button
*/
export const SplitButton = <T,>({
color,
disabled,
onClick,
options,
startIcon,
textTransform,
}: SplitButtonProps<T>): ReturnType<React.FC> => {
const [isPopperOpen, setIsPopperOpen] = useState<boolean>(false)
const anchorRef = useRef<HTMLDivElement>(null)
const displayedLabel = options[0].label
const handleClick = () => {
onClick(options[0].value)
}
const handleClose = (e: React.MouseEvent<Document, MouseEvent>) => {
if (anchorRef.current && anchorRef.current.contains(e.target as HTMLElement)) {
return
}
setIsPopperOpen(false)
}
const handleSelectOpt = (e: React.MouseEvent<HTMLLIElement, MouseEvent>, opt: number) => {
onClick(options[opt].value)
setIsPopperOpen(false)
}
const handleTogglePopper = () => {
setIsPopperOpen((prevOpen) => !prevOpen)
}
return (
<>
<ButtonGroup aria-label="split button" color={color} ref={anchorRef} variant="contained">
<Button disabled={disabled} onClick={handleClick} startIcon={startIcon} style={{ textTransform }}>
{displayedLabel}
</Button>
<Button
aria-controls={isPopperOpen ? "split-button-menu" : undefined}
aria-expanded={isPopperOpen ? "true" : undefined}
aria-label="select merge strategy"
aria-haspopup="menu"
disabled={disabled}
size="small"
onClick={handleTogglePopper}
>
<ArrowDropDownIcon />
</Button>
</ButtonGroup>
<Popper
anchorEl={anchorRef.current}
disablePortal
open={isPopperOpen}
role={undefined}
style={{ zIndex: 1 }}
transition
>
{({ TransitionProps, placement }) => (
<Grow
{...TransitionProps}
style={{
transformOrigin: placement === "bottom" ? "center top" : "center bottom",
}}
>
<Paper>
<ClickAwayListener onClickAway={handleClose}>
<MenuList id="split-button-menu">
{options.map((opt, idx) => (
<MenuItem key={opt.label} onClick={(e) => handleSelectOpt(e, idx)}>
{opt.label}
</MenuItem>
))}
</MenuList>
</ClickAwayListener>
</Paper>
</Grow>
)}
</Popper>
</>
)
}

View File

@ -0,0 +1 @@
export { SplitButton } from "./SplitButton"

View File

@ -0,0 +1,74 @@
import React from "react"
import { makeStyles } from "@material-ui/core/styles"
import Box from "@material-ui/core/Box"
import Button, { ButtonProps } from "@material-ui/core/Button"
import Typography from "@material-ui/core/Typography"
export interface EmptyStateProps {
/** Text Message to display, placed inside Typography component */
message: React.ReactNode
/** Longer optional description to display below the message */
description?: React.ReactNode
button?: ButtonProps
}
/**
* Component to place on screens or in lists that have no content. Optionally
* provide a button that would allow the user to return from where they were,
* or to add an item that they currently have none of.
*
* EmptyState's props extend the [Material UI Box component](https://material-ui.com/components/box/)
* that you can directly pass props through to to customize the shape and layout of it.
*/
export const EmptyState: React.FC<EmptyStateProps> = (props) => {
const { message, description, button, ...boxProps } = props
const styles = useStyles()
const descClassName = `${styles.description}`
const buttonClassName = `${styles.button} ${button && button.className ? button.className : ""}`
return (
<Box className={styles.root} {...boxProps}>
<Typography variant="h5" color="textSecondary" className={styles.header}>
{message}
</Typography>
{description && (
<Typography variant="body2" color="textSecondary" className={descClassName}>
{description}
</Typography>
)}
{button && <Button variant="contained" color="primary" {...button} className={buttonClassName} />}
</Box>
)
}
const useStyles = makeStyles(
(theme) => ({
root: {
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
textAlign: "center",
minHeight: 120,
padding: theme.spacing(3),
},
header: {
fontWeight: 400,
},
description: {
marginTop: theme.spacing(2),
marginBottom: theme.spacing(1),
},
button: {
marginTop: theme.spacing(2),
},
icon: {
fontSize: theme.typography.h2.fontSize,
color: theme.palette.text.secondary,
marginBottom: theme.spacing(1),
opacity: 0.5,
},
}),
{ name: "EmptyState" },
)

View File

@ -0,0 +1,20 @@
import * as React from "react"
export const Logo = (props: React.SVGProps<SVGSVGElement>): JSX.Element => (
<svg aria-labelledby="title" viewBox="0 0 341 75" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
<title id="title" lang="en">
Coder logo
</title>
<path d="M176.753 32.7584H165.554C165.35 31.3095 164.932 30.0226 164.301 28.8976C163.671 27.7555 162.861 26.7839 161.872 25.9828C160.884 25.1817 159.742 24.568 158.446 24.1419C157.168 23.7158 155.779 23.5027 154.279 23.5027C151.568 23.5027 149.208 24.176 147.196 25.5226C145.185 26.8521 143.625 28.7953 142.517 31.3521C141.409 33.8919 140.855 36.9771 140.855 40.6078C140.855 44.3408 141.409 47.4771 142.517 50.0169C143.642 52.5567 145.21 54.4743 147.222 55.7697C149.233 57.0652 151.56 57.7129 154.202 57.7129C155.685 57.7129 157.057 57.5169 158.318 57.1249C159.597 56.7328 160.73 56.1618 161.719 55.4118C162.708 54.6447 163.526 53.7158 164.173 52.6249C164.838 51.534 165.298 50.2896 165.554 48.8919L176.753 48.943C176.463 51.3464 175.739 53.6646 174.58 55.8976C173.438 58.1135 171.895 60.0993 169.952 61.855C168.026 63.5936 165.725 64.9743 163.048 65.997C160.389 67.0027 157.381 67.5055 154.023 67.5055C149.352 67.5055 145.176 66.4487 141.494 64.3351C137.83 62.2214 134.932 59.1618 132.801 55.1561C130.688 51.1504 129.631 46.301 129.631 40.6078C129.631 34.8976 130.705 30.0396 132.852 26.0339C135 22.0283 137.915 18.9771 141.597 16.8805C145.279 14.7669 149.421 13.7101 154.023 13.7101C157.057 13.7101 159.869 14.1362 162.46 14.9885C165.068 15.8408 167.378 17.0851 169.389 18.7214C171.401 20.3408 173.037 22.3266 174.298 24.6788C175.577 27.0311 176.395 29.7243 176.753 32.7584Z" />
<path d="M202.032 67.5567C198.06 67.5567 194.625 66.7129 191.728 65.0254C188.847 63.3209 186.623 60.9516 185.054 57.9175C183.486 54.8663 182.702 51.3294 182.702 47.3067C182.702 43.2499 183.486 39.7044 185.054 36.6703C186.623 33.6192 188.847 31.2499 191.728 29.5624C194.625 27.8578 198.06 27.0055 202.032 27.0055C206.003 27.0055 209.429 27.8578 212.31 29.5624C215.208 31.2499 217.441 33.6192 219.009 36.6703C220.577 39.7044 221.361 43.2499 221.361 47.3067C221.361 51.3294 220.577 54.8663 219.009 57.9175C217.441 60.9516 215.208 63.3209 212.31 65.0254C209.429 66.7129 206.003 67.5567 202.032 67.5567ZM202.083 59.1192C203.89 59.1192 205.398 58.6078 206.608 57.5851C207.819 56.5453 208.731 55.1305 209.344 53.3408C209.975 51.551 210.29 49.5141 210.29 47.23C210.29 44.9459 209.975 42.9089 209.344 41.1192C208.731 39.3294 207.819 37.9146 206.608 36.8749C205.398 35.8351 203.89 35.3152 202.083 35.3152C200.259 35.3152 198.725 35.8351 197.481 36.8749C196.253 37.9146 195.324 39.3294 194.694 41.1192C194.08 42.9089 193.773 44.9459 193.773 47.23C193.773 49.5141 194.08 51.551 194.694 53.3408C195.324 55.1305 196.253 56.5453 197.481 57.5851C198.725 58.6078 200.259 59.1192 202.083 59.1192Z" />
<path d="M242.715 67.4288C239.732 67.4288 237.031 66.6618 234.61 65.1277C232.207 63.5766 230.298 61.301 228.883 58.301C227.485 55.284 226.786 51.5851 226.786 47.2044C226.786 42.7044 227.511 38.9629 228.96 35.98C230.408 32.98 232.335 30.7385 234.738 29.2555C237.158 27.7555 239.809 27.0055 242.69 27.0055C244.889 27.0055 246.721 27.3805 248.187 28.1305C249.67 28.8635 250.863 29.7839 251.766 30.8919C252.687 31.9828 253.386 33.0567 253.863 34.1135H254.195V14.426H265.062V66.7896H254.323V60.4999H253.863C253.352 61.5908 252.627 62.6732 251.69 63.747C250.769 64.8038 249.568 65.6817 248.085 66.3805C246.619 67.0794 244.829 67.4288 242.715 67.4288ZM246.167 58.7612C247.923 58.7612 249.406 58.2839 250.616 57.3294C251.843 56.3578 252.781 55.0027 253.428 53.2641C254.093 51.5254 254.425 49.4885 254.425 47.1533C254.425 44.818 254.102 42.7896 253.454 41.068C252.806 39.3464 251.869 38.0169 250.641 37.0794C249.414 36.1419 247.923 35.6732 246.167 35.6732C244.377 35.6732 242.869 36.1589 241.641 37.1305C240.414 38.1021 239.485 39.4487 238.854 41.1703C238.224 42.8919 237.908 44.8862 237.908 47.1533C237.908 49.4374 238.224 51.4572 238.854 53.2129C239.502 54.9516 240.431 56.3152 241.641 57.3038C242.869 58.2754 244.377 58.7612 246.167 58.7612Z" />
<path d="M291.715 67.5567C287.675 67.5567 284.198 66.7385 281.283 65.1021C278.385 63.4487 276.152 61.1135 274.584 58.0964C273.016 55.0624 272.232 51.4743 272.232 47.3322C272.232 43.2925 273.016 39.747 274.584 36.6959C276.152 33.6447 278.36 31.2669 281.206 29.5624C284.07 27.8578 287.428 27.0055 291.28 27.0055C293.871 27.0055 296.283 27.4232 298.516 28.2584C300.766 29.0766 302.726 30.3124 304.397 31.9658C306.084 33.6192 307.397 35.6987 308.334 38.2044C309.272 40.693 309.74 43.6078 309.74 46.9487V49.9402H276.578V43.1902H299.488C299.488 41.622 299.147 40.2328 298.465 39.0226C297.783 37.8124 296.837 36.8663 295.627 36.1845C294.434 35.4857 293.044 35.1362 291.459 35.1362C289.806 35.1362 288.34 35.5197 287.061 36.2868C285.8 37.0368 284.811 38.051 284.095 39.3294C283.38 40.5908 283.013 41.997 282.996 43.5482V49.9658C282.996 51.9089 283.354 53.5879 284.07 55.0027C284.803 56.4175 285.834 57.5084 287.164 58.2754C288.493 59.0425 290.07 59.426 291.894 59.426C293.104 59.426 294.212 59.2555 295.218 58.9146C296.223 58.5737 297.084 58.0624 297.8 57.3805C298.516 56.6987 299.061 55.8635 299.436 54.8749L309.51 55.5396C308.999 57.9601 307.951 60.0737 306.365 61.8805C304.797 63.6703 302.769 65.068 300.28 66.0737C297.809 67.0624 294.953 67.5567 291.715 67.5567Z" />
<path d="M316.648 66.7896V27.5169H327.208V34.3692H327.617C328.333 31.9317 329.535 30.0908 331.222 28.8464C332.91 27.5851 334.853 26.9544 337.052 26.9544C337.597 26.9544 338.185 26.9885 338.816 27.0567C339.447 27.1249 340.001 27.2186 340.478 27.3379V37.0027C339.967 36.8493 339.259 36.7129 338.356 36.5936C337.452 36.4743 336.626 36.4146 335.876 36.4146C334.273 36.4146 332.842 36.7641 331.58 37.4629C330.336 38.1447 329.347 39.0993 328.614 40.3266C327.898 41.5538 327.54 42.9686 327.54 44.5709V66.7896H316.648Z" />
<path d="M102.464 32.5472C100.371 32.5472 98.9767 31.3202 98.9767 28.8017V14.3362C98.9767 5.10163 95.1722 0 85.3443 0H80.779V9.75122H82.1741C86.0417 9.75122 87.8805 11.8823 87.8805 15.6924V28.4788C87.8805 34.0324 89.529 36.2927 93.1432 37.4551C89.529 38.553 87.8805 40.8777 87.8805 46.4313C87.8805 49.5957 87.8805 52.7599 87.8805 55.9244C87.8805 58.572 87.8805 61.1551 87.1831 63.8027C86.4856 66.2568 85.3443 68.5815 83.7592 70.5833C82.8715 71.7459 81.857 72.7145 80.7158 73.6187V74.9101H85.2808C95.1088 74.9101 98.9132 69.8085 98.9132 60.5738V46.1084C98.9132 43.5253 100.245 42.3629 102.401 42.3629H105V32.6116H102.464V32.5472Z" />
<path d="M71.3947 14.7257H57.3186C57.0015 14.7257 56.748 14.4674 56.748 14.1445V13.0467C56.748 12.7238 57.0015 12.4655 57.3186 12.4655H71.4581C71.775 12.4655 72.0287 12.7238 72.0287 13.0467V14.1445C72.0287 14.4674 71.7116 14.7257 71.3947 14.7257Z" />
<path d="M73.8044 28.6738H63.5327C63.2156 28.6738 62.9619 28.4154 62.9619 28.0925V26.9948C62.9619 26.672 63.2156 26.4135 63.5327 26.4135H73.8044C74.1215 26.4135 74.375 26.672 74.375 26.9948V28.0925C74.375 28.3509 74.1215 28.6738 73.8044 28.6738Z" />
<path d="M77.862 21.6998H57.3186C57.0015 21.6998 56.748 21.4415 56.748 21.1186V20.0208C56.748 19.6979 57.0015 19.4396 57.3186 19.4396H77.7985C78.1157 19.4396 78.3694 19.6979 78.3694 20.0208V21.1186C78.3694 21.3769 78.1791 21.6998 77.862 21.6998Z" />
<path d="M41.0235 17.888C42.4183 17.888 43.8134 18.0172 45.1448 18.3401V15.6924C45.1448 11.9469 47.047 9.75122 50.8514 9.75122H52.2462V0H47.681C37.853 0 34.0488 5.10163 34.0488 14.3362V19.115C36.2679 18.3401 38.6141 17.888 41.0235 17.888Z" />
<path d="M82.1739 53.0168C81.1594 44.8154 74.9456 37.9701 66.9564 36.4202C64.7373 35.9683 62.518 35.9036 60.3623 36.2911C60.2989 36.2911 60.2989 36.2264 60.2355 36.2264C56.7482 28.8001 49.2664 23.8922 41.1504 23.8922C33.0344 23.8922 25.616 28.671 22.0652 36.0974C22.0018 36.0974 22.0018 36.162 21.9384 36.162C19.6558 35.9036 17.3732 36.0327 15.0906 36.614C7.22825 38.5512 1.26813 45.2674 0.190222 53.4041C0.0633989 54.2436 0 55.0831 0 55.8581C0 58.3119 1.64855 60.5722 4.05796 60.8951C7.03803 61.3473 9.63766 59.0223 9.57426 56.0519C9.57426 55.5997 9.57426 55.0831 9.63766 54.6311C10.1449 50.4981 13.2518 47.011 17.3098 46.0422C18.5779 45.7193 19.846 45.6549 21.0507 45.8487C24.9185 46.3653 28.7228 44.3632 30.3713 40.8761C31.5761 38.293 33.4783 36.0327 36.0145 34.8057C38.8042 33.4495 41.9746 33.256 44.8914 34.2893C47.9347 35.387 50.2172 37.7117 51.6123 40.6177C53.0706 43.4593 53.768 45.4611 56.875 45.8487C58.143 46.0422 61.6937 45.9777 63.0254 45.9131C65.625 45.9131 68.2246 46.8172 70.0634 48.69C71.268 49.9814 72.1557 51.596 72.5362 53.4041C73.1068 56.3101 72.4094 59.2161 70.6974 61.4117C69.4926 62.9616 67.8441 64.124 66.0053 64.6406C65.1176 64.899 64.2299 64.9635 63.3422 64.9635C62.8351 64.9635 62.1377 64.9635 61.3134 64.9635C58.7772 64.9635 53.3877 64.9635 49.3296 64.9635C46.5399 64.9635 44.3206 62.7034 44.3206 59.8618V50.3043V40.9405C44.3206 40.1657 43.6866 39.52 42.9257 39.52H40.9601C37.0922 39.5844 33.9855 43.9759 33.9855 48.6253C33.9855 53.275 33.9855 65.6094 33.9855 65.6094C33.9855 70.6464 37.9799 74.7148 42.9257 74.7148C42.9257 74.7148 64.9276 74.6501 65.2445 74.6501C70.3169 74.1335 75.009 71.4859 78.1792 67.4175C81.3496 63.4782 82.8079 58.3119 82.1739 53.0168Z" />
</svg>
)

View File

@ -0,0 +1,12 @@
import SvgIcon from "@material-ui/core/SvgIcon"
import React from "react"
export const WorkspacesIcon: typeof SvgIcon = (props) => (
<SvgIcon {...props} viewBox="0 0 16 16">
<path d="M6 14H2V2H12V5.5L14 7V1C14 0.734784 13.8946 0.48043 13.7071 0.292893C13.5196 0.105357 13.2652 0 13 0L1 0C0.734784 0 0.48043 0.105357 0.292893 0.292893C0.105357 0.48043 0 0.734784 0 1L0 15C0 15.2652 0.105357 15.5196 0.292893 15.7071C0.48043 15.8946 0.734784 16 1 16H6V14Z" />
<path d="M12 8L8 11V16H11V13H13.035V16H16V11L12 8Z" />
<path d="M10 4H4V5H10V4Z" />
<path d="M10 7H4V8H10V7Z" />
<path d="M7 10H4V11H7V10Z" />
</SvgIcon>
)

View File

@ -0,0 +1,2 @@
export { Logo } from "./Logo"
export { WorkspacesIcon } from "./WorkspacesIcon"

View File

@ -0,0 +1,51 @@
import ListItemIcon from "@material-ui/core/ListItemIcon"
import MenuItem from "@material-ui/core/MenuItem"
import { SvgIcon, Typography } from "@material-ui/core"
import { makeStyles } from "@material-ui/core/styles"
import React from "react"
export interface NavMenuEntryProps {
icon: typeof SvgIcon
path: string
label?: string
selected: boolean
className?: string
onClick?: () => void
}
export const NavMenuEntry: React.FC<NavMenuEntryProps> = ({
className,
icon,
path,
label = path,
selected,
onClick,
}) => {
const styles = useStyles()
const Icon = icon
return (
<MenuItem selected={selected} className={className} onClick={onClick}>
<div className={styles.root}>
{icon && (
<ListItemIcon>
<Icon className={styles.icon} />
</ListItemIcon>
)}
<Typography>{label}</Typography>
</div>
</MenuItem>
)
}
const useStyles = makeStyles((theme) => ({
root: {
padding: "2em",
},
icon: {
color: theme.palette.text.primary,
"& path": {
fill: theme.palette.text.primary,
},
},
}))

View File

@ -0,0 +1,69 @@
import React from "react"
import { makeStyles } from "@material-ui/core/styles"
import { Button, List, ListSubheader } from "@material-ui/core"
import Link from "next/link"
import { Logo } from "../Icons"
export const Navbar: React.FC = () => {
const styles = useStyles()
return (
<div className={styles.root}>
<div className={styles.fixed}>
<Link href="/">
<Button className={styles.logo} variant="text">
<Logo fill="white" opacity={1} />
</Button>
</Link>
</div>
<div className={styles.fullWidth}>
<div className={styles.title}>Hello, World - Coder v2</div>
</div>
<div className={styles.fixed}>
<List>
<ListSubheader>Manage</ListSubheader>
</List>
</div>
</div>
)
}
const useStyles = makeStyles((theme) => ({
root: {
position: "relative",
display: "flex",
flexDirection: "row",
justifyContent: "center",
alignItems: "center",
height: "56px",
background: theme.palette.navbar.main,
marginTop: 0,
transition: "margin 150ms ease",
"@media (display-mode: standalone)": {
borderTop: `1px solid ${theme.palette.divider}`,
},
borderBottom: `1px solid #383838`,
},
fixed: {
flex: "0",
},
fullWidth: {
flex: "1",
},
logo: {
flex: "0",
height: "56px",
paddingLeft: theme.spacing(4),
paddingRight: theme.spacing(2),
borderRadius: 0,
"& svg": {
display: "block",
width: 125,
},
},
title: {
flex: "1",
textAlign: "center",
},
}))

View File

@ -0,0 +1,38 @@
import { makeStyles } from "@material-ui/core/styles"
import Typography from "@material-ui/core/Typography"
import React from "react"
export const Footer: React.FC = ({ children }) => {
const styles = useFooterStyles()
return (
<div className={styles.root}>
{children}
<div className={styles.copyRight}>
<Typography color="textSecondary" variant="caption">
{`Copyright \u00a9 ${new Date().getFullYear()} Coder Technologies, Inc. All rights reserved.`}
</Typography>
</div>
<div className={styles.version}>
<Typography color="textSecondary" variant="caption">
v2 0.0.0-prototype
</Typography>
</div>
</div>
)
}
const useFooterStyles = makeStyles((theme) => ({
root: {
textAlign: "center",
marginBottom: theme.spacing(5),
},
copyRight: {
backgroundColor: theme.palette.background.default,
margin: theme.spacing(0.25),
},
version: {
backgroundColor: theme.palette.background.default,
margin: theme.spacing(0.25),
},
}))

View File

@ -0,0 +1,50 @@
import React from "react"
import { makeStyles } from "@material-ui/core/styles"
import { Footer } from "./Footer"
import { Navbar } from "../Navbar"
export const Page: React.FC<{ children: React.ReactNode }> = ({ children }) => {
// TODO: More interesting styling here!
const styles = useStyles()
const header = (
<div className={styles.header}>
<Navbar />
</div>
)
const footer = (
<div className={styles.footer}>
<Footer />
</div>
)
const body = <div className={styles.body}> {children}</div>
return (
<div className={styles.root}>
{header}
{body}
{footer}
</div>
)
}
const useStyles = makeStyles((theme) => ({
root: {
display: "flex",
flexDirection: "column",
},
header: {
flex: 0,
},
body: {
height: "100%",
flex: 1,
},
footer: {
flex: 0,
},
}))

View File

@ -0,0 +1,3 @@
export * from "./Button"
export * from "./EmptyState"
export * from "./Page"

5
site/next-env.d.ts vendored Normal file
View File

@ -0,0 +1,5 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.

26
site/next.config.js Normal file
View File

@ -0,0 +1,26 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-var-requires */
const path = require("path")
module.exports = {
env: {},
experimental: {
// Allows us to import TS files from outside product/coder/site.
externalDir: true,
},
webpack: (config, { dev, isServer, webpack }) => {
// Inject CODERD_HOST environment variable for clients
if (!isServer) {
config.plugins.push(
new webpack.DefinePlugin({
"process.env.CODERD_HOST": JSON.stringify(process.env.CODERD_HOST),
}),
)
}
return config
},
}

24
site/pages/_app.tsx Normal file
View File

@ -0,0 +1,24 @@
import React from "react"
import CssBaseline from "@material-ui/core/CssBaseline"
import ThemeProvider from "@material-ui/styles/ThemeProvider"
import { dark } from "../theme"
import { AppProps } from "next/app"
/**
* <App /> is the root rendering logic of the application - setting up our router
* and any contexts / global state management.
* @returns
*/
const MyApp: React.FC<AppProps> = ({ Component, pageProps }) => {
return (
<ThemeProvider theme={dark}>
<CssBaseline />
<Component {...pageProps} />
</ThemeProvider>
)
}
export default MyApp

61
site/pages/index.tsx Normal file
View File

@ -0,0 +1,61 @@
import React, { useState } from "react"
import { Dialog, DialogActions, Button, DialogTitle, DialogContent, makeStyles, Box, Paper } from "@material-ui/core"
import { AddToQueue as AddWorkspaceIcon } from "@material-ui/icons"
import { EmptyState, Page, SplitButton } from "../components"
const WorkspacesPage: React.FC = () => {
const styles = useStyles()
const createWorkspace = () => {
alert("create")
}
const button = {
children: "New Workspace",
onClick: createWorkspace,
}
return (
<Page>
<div className={styles.header}>
<SplitButton<string>
color="primary"
onClick={createWorkspace}
options={[
{
label: "New workspace",
value: "custom",
},
{
label: "New workspace from template",
value: "template",
},
]}
startIcon={<AddWorkspaceIcon />}
textTransform="none"
/>
</div>
<Paper style={{ maxWidth: "1380px", margin: "1em auto" }}>
<Box pt={4} pb={4}>
<EmptyState message="No workspaces available." button={button} />
</Box>
</Paper>
</Page>
)
}
const useStyles = makeStyles((theme) => ({
header: {
display: "flex",
flexDirection: "row-reverse",
justifyContent: "space-between",
margin: "1em auto",
maxWidth: "1380px",
padding: theme.spacing(2, 6.25, 0),
width: "100%",
},
}))
export default WorkspacesPage

8
site/theme/constants.ts Normal file
View File

@ -0,0 +1,8 @@
export const spacing = 8
export const borderRadius = 4
export const buttonBorderWidth = 2
export const MONOSPACE_FONT_FAMILY =
"'Fira Code', 'Lucida Console', 'Lucida Sans Typewriter', 'Liberation Mono', 'Monaco', 'Courier New', Courier, monospace"
export const BODY_FONT_FAMILY = `"Inter", sans-serif`
export const lightButtonShadow = "0 2px 2px rgba(0, 23, 121, 0.08)"
export const emptyBoxShadow = "none"

1
site/theme/index.ts Normal file
View File

@ -0,0 +1 @@
export { light, dark } from "./theme"

118
site/theme/palettes.ts Normal file
View File

@ -0,0 +1,118 @@
import { Palette } from "@material-ui/core/styles/createPalette"
/**
* Augment MUI Palette with Coder-specific design system
*/
declare module "@material-ui/core/styles/createPalette" {
interface Palette {
navbar: {
main: string
}
}
interface PaletteOptions {
navbar: {
main: string
}
}
}
/**
* CustomPalette implements a minimal subset of MUI Palette interface for our
* light and dark themes.
*/
export type CustomPalette = Pick<
Palette,
"action" | "background" | "divider" | "error" | "info" | "navbar" | "primary" | "secondary" | "text" | "type"
>
/**
* Light theme color palette, the default
*
* This maps to our design system at:
* https://www.figma.com/file/VkXU4873QOsSprMQV02GgR/Design-System?node-id=3%3A2
*/
export const lightPalette: CustomPalette = {
type: "light",
background: {
default: "#F3F3F3",
paper: "#FFF",
},
primary: {
main: "#519A54",
light: "#A2E0A5",
dark: "#3A783D",
contrastText: "#FFF",
},
info: {
main: "#000",
light: "#000",
dark: "#000",
contrastText: "#FFF",
},
navbar: {
main: "#242424",
},
secondary: {
main: "#F7CD6F",
light: "#FFE7A0",
dark: "#BF9331",
contrastText: "#FFF",
},
error: {
main: "#DD4764",
light: "#A14E5E",
dark: "#912F42",
contrastText: "#FFF",
},
text: {
primary: "#000",
secondary: "#747474",
disabled: "#749367",
hint: "#749367",
},
action: {
active: "#242424",
hover: "rgba(0, 0, 0, 0.1)",
hoverOpacity: 0.08,
selected: "#D0EFD2",
disabled: "#DDE2EC",
disabledBackground: "#F3F3F3",
},
divider: "#DDE2EC",
}
/**
* Dark theme color palette
*
* This maps to our design system at:
* https://www.figma.com/file/VkXU4873QOsSprMQV02GgR/Design-System?node-id=219%3A40
*/
export const darkPalette: CustomPalette = {
type: "dark",
primary: lightPalette.primary,
secondary: lightPalette.secondary,
info: lightPalette.info,
error: lightPalette.error,
navbar: {
main: "rgb(8, 9, 10)",
},
background: {
default: "rgb(24, 26, 27)",
paper: "rgb(31, 33, 35)",
},
text: {
primary: "rgba(255, 255, 255, 0.95)",
secondary: "#BDBDBD",
disabled: "#BDBDBD",
hint: "#BDBDBD",
},
action: {
active: "#FFF",
hover: "rgba(255, 255, 255, 0.1)",
hoverOpacity: 0.1,
selected: "rgba(255, 255, 255, 0.2)",
disabled: "rgba(255, 255, 255, 0.1)",
disabledBackground: "rgba(255, 255, 255, 0.12)",
},
divider: "rgba(255, 255, 255, 0.12)",
}

20
site/theme/theme.tsx Normal file
View File

@ -0,0 +1,20 @@
import { createMuiTheme } from "@material-ui/core/styles"
import { borderRadius } from "./constants"
import { CustomPalette, darkPalette, lightPalette } from "./palettes"
import { typography } from "./typography"
const makeTheme = (palette: CustomPalette) => {
// Grab defaults to re-use in overrides
const { breakpoints } = createMuiTheme()
return createMuiTheme({
palette,
typography,
shape: {
borderRadius,
},
})
}
export const light = makeTheme(lightPalette)
export const dark = makeTheme(darkPalette)

72
site/theme/typography.ts Normal file
View File

@ -0,0 +1,72 @@
import { TypographyOptions } from "@material-ui/core/styles/createTypography"
import { BODY_FONT_FAMILY } from "./constants"
export const typography: TypographyOptions = {
fontFamily: BODY_FONT_FAMILY,
fontSize: 16,
fontWeightLight: 300,
fontWeightRegular: 400,
fontWeightMedium: 500,
h1: {
fontSize: 72,
fontWeight: 400,
letterSpacing: -2,
},
h2: {
fontSize: 64,
letterSpacing: -2,
fontWeight: 400,
},
h3: {
fontSize: 32,
letterSpacing: -0.3,
fontWeight: 400,
},
h4: {
fontSize: 24,
letterSpacing: -0.3,
fontWeight: 400,
},
h5: {
fontSize: 20,
letterSpacing: -0.3,
fontWeight: 400,
},
h6: {
fontSize: 16,
fontWeight: 600,
},
body1: {
fontSize: 16,
lineHeight: "24px",
},
body2: {
fontSize: 14,
lineHeight: "20px",
},
subtitle1: {
fontSize: 18,
lineHeight: "28px",
},
subtitle2: {
fontSize: 16,
lineHeight: "24px",
},
caption: {
fontSize: 12,
lineHeight: "16px",
},
overline: {
fontSize: 12,
fontWeight: 500,
lineHeight: "16px",
letterSpacing: 1.5,
textTransform: "uppercase",
},
button: {
fontSize: 13,
fontWeight: 600,
textTransform: "uppercase",
letterSpacing: 1.5,
},
}

23
site/tsconfig.json Normal file
View File

@ -0,0 +1,23 @@
{
"compilerOptions": {
"outDir": "./dist/",
"noImplicitAny": true,
"module": "commonjs",
"target": "es5",
"jsx": "preserve",
"allowJs": true,
"downlevelIteration": true,
"esModuleInterop": true,
"resolveJsonModule": true,
"isolatedModules": true,
"lib": ["dom", "dom.iterable", "esnext"],
"skipLibCheck": true,
"strict": false,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"incremental": true,
"moduleResolution": "node"
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]
}

2139
yarn.lock

File diff suppressed because it is too large Load Diff