Compare commits

...

4 Commits

Author SHA1 Message Date
Kyle Doherty
c044743160 add css dir 2025-01-13 11:25:21 -05:00
Kyle Doherty
b151159305 wip documentation for fyc 2025-01-13 11:19:43 -05:00
Kyle Doherty
b6f01de156 code sample wip 2025-01-13 11:19:43 -05:00
Kyle Doherty
43e671e7d9 proof of concept of nextjs + mdx design system docs 2025-01-13 11:19:43 -05:00
23 changed files with 7857 additions and 0 deletions

View File

@@ -0,0 +1,3 @@
{
"extends": ["next/core-web-vitals", "next/typescript"]
}

View File

@@ -0,0 +1,36 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env*.local
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts

View File

@@ -0,0 +1,36 @@
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
## Getting Started
First, run the development server:
```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
## Learn More
To learn more about Next.js, take a look at the following resources:
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
## Deploy on Vercel
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.

View File

@@ -0,0 +1,21 @@
import { ColorSwatch } from "@mantine/core";
# Colors
Our color system is focused on contrast.
---
## Named colors
### Ocean
<ColorSwatch color="blue.1" />
<ColorSwatch color="blue.1" />
<ColorSwatch color="blue.1" />
<ColorSwatch color="blue.1" />
<ColorSwatch color="blue.1" />
<ColorSwatch color="blue.1" />
<ColorSwatch color="blue.1" />
<ColorSwatch color="blue.1" />
<ColorSwatch color="blue.1" />

View File

@@ -0,0 +1,6 @@
"use client";
import ColorDocs from "./docs.mdx";
export default function Page() {
return <ColorDocs />;
}

View File

@@ -0,0 +1,32 @@
import { Button } from "metabase/ui";
# Buttons
Folks gotta click.
<a
href="https://www.figma.com/design/Ey1rOyIxRHpmRvE9XrGyop/Buttons-%2F-Button?m=auto&t=cKYkujdMHHNcACQs-6"
target="_blank"
>
<Button>Figma File</Button>
</a>
---
## Usage
### Default
```tsx
<Button>Click me</Button>
```
### Filled
```tsx
<Button variant="filled">Click me</Button>
```
```tsx
<Button variant="outline">Click me</Button>
```

View File

@@ -0,0 +1,28 @@
"use client";
import ButtonDocs from "./docs.mdx";
import { LiveEditor, LivePreview, LiveProvider } from "react-live";
import { Box, Button, Card } from "metabase/ui";
function CodeTest({ children }) {
const scope = { Box, Button };
return (
<LiveProvider code={children} scope={scope} disabled>
<Box p="md">
<LivePreview />
</Box>
<Card p="0">
<LiveEditor />
</Card>
</LiveProvider>
);
}
const components = {
code: CodeTest,
};
export default function Page() {
// @ts-ignore
return <ButtonDocs components={components} />;
}

View File

@@ -0,0 +1,9 @@
# Modal
# Basic Example
```jsx
<Modal>
</Modal>
```

View File

@@ -0,0 +1,5 @@
import Docs from "./docs.mdx";
export default function Page() {
return <Docs />;
}

View File

@@ -0,0 +1,11 @@
import { Radio } from "metabase/ui";
# Radio
<Radio label="Option" value="Option"></Radio>
<Radio label="Option" value="Option"></Radio>
<Radio label="Option" value="Option"></Radio>
```tsx
<Radio label="Option" value="Option"></Radio>
```

View File

@@ -0,0 +1,7 @@
"use client";
import RadioDocs from "./docs.mdx";
export default function Page() {
return <RadioDocs />;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View File

@@ -0,0 +1,29 @@
/* Make sure our mb color vars are present */
/* Must be done here as opposed to layout.tsx because nextjs complains about impure css */
@import url("../../../css/core/colors.module.css");
body {
font-family: var(--font-lato), sans-serif;
background-color: var(--mb-base-color-gray-5);
}
a {
color: var(--mb-color-brand);
}
hr {
border: 0;
border-top: 1px solid var(--mb-color-border);
}
.mantine-Navbar-root {
border-right: 1px solid var(--mb-color-border);
}
.mantine-Header-root {
border-bottom: 1px solid var(--mb-color-border);
}
pre {
font-family: inherit;
}

View File

@@ -0,0 +1,86 @@
"use client";
import localFont from "next/font/local";
import "./globals.css";
import { AppShell, Navbar, Header, MantineProvider } from "@mantine/core";
import Link from "next/link";
import { Box, Title } from "metabase/ui";
const Lato = localFont({
src: "../../../../../../resources/frontend_client/app/fonts/Lato/Lato-Regular.ttf",
variable: "--font-lato",
weight: "100 400 700 900",
});
import { getThemeOverrides } from "../../theme";
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
const theme = getThemeOverrides();
return (
<html lang="en">
<body className={`${Lato.variable}`}>
<MantineProvider theme={theme}>
<AppShell
padding="md"
navbar={
<Navbar width={{ base: 300 }} p="md">
<Link href="/">Home</Link>
<Box my="md">
<Title order={4}>System</Title>
<Box my="md">
<Box>
<Link href="/colors">Colors</Link>
</Box>
<Box>
<Link href="/typograghy">Typography</Link>
</Box>
</Box>
</Box>
<Box my="md">
<Title order={4}>Core Components</Title>
<Box>
<Link href="/components/button">Button</Link>
</Box>
<Box>
<Link href="/components/radio">Radio</Link>
</Box>
</Box>
<Box>
<Title order={4}>Overlays</Title>
<Box>
<Link href="/components/modal">Modal</Link>
</Box>
</Box>
<Box my="md">
<Title order={4}>Patterns</Title>
<Box>
<Link href="/patterns/for-your-consideration">
For your consideration
</Link>
</Box>
</Box>
</Navbar>
}
header={
<Header height={60} p="xs">
<Link href="/">Metabase</Link>
</Header>
}
>
<Box ml="auto" mr="auto" maw={1200} w={800}>
{/* @ts-ignore */}
{children}
</Box>
</AppShell>
</MantineProvider>
</body>
</html>
);
}

View File

@@ -0,0 +1,168 @@
.page {
--gray-rgb: 0, 0, 0;
--gray-alpha-200: rgba(var(--gray-rgb), 0.08);
--gray-alpha-100: rgba(var(--gray-rgb), 0.05);
--button-primary-hover: #383838;
--button-secondary-hover: #f2f2f2;
display: grid;
grid-template-rows: 20px 1fr 20px;
align-items: center;
justify-items: center;
min-height: 100svh;
padding: 80px;
gap: 64px;
font-family: var(--font-geist-sans);
}
@media (prefers-color-scheme: dark) {
.page {
--gray-rgb: 255, 255, 255;
--gray-alpha-200: rgba(var(--gray-rgb), 0.145);
--gray-alpha-100: rgba(var(--gray-rgb), 0.06);
--button-primary-hover: #ccc;
--button-secondary-hover: #1a1a1a;
}
}
.main {
display: flex;
flex-direction: column;
gap: 32px;
grid-row-start: 2;
}
.main ol {
font-family: var(--font-geist-mono);
padding-left: 0;
margin: 0;
font-size: 14px;
line-height: 24px;
letter-spacing: -0.01em;
list-style-position: inside;
}
.main li:not(:last-of-type) {
margin-bottom: 8px;
}
.main code {
font-family: inherit;
background: var(--gray-alpha-100);
padding: 2px 4px;
border-radius: 4px;
font-weight: 600;
}
.ctas {
display: flex;
gap: 16px;
}
.ctas a {
appearance: none;
border-radius: 128px;
height: 48px;
padding: 0 20px;
border: none;
border: 1px solid transparent;
transition:
background 0.2s,
color 0.2s,
border-color 0.2s;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
line-height: 20px;
font-weight: 500;
}
a.primary {
background: var(--foreground);
color: var(--background);
gap: 8px;
}
a.secondary {
border-color: var(--gray-alpha-200);
min-width: 180px;
}
.footer {
grid-row-start: 3;
display: flex;
gap: 24px;
}
.footer a {
display: flex;
align-items: center;
gap: 8px;
}
.footer img {
flex-shrink: 0;
}
/* Enable hover only on non-touch devices */
@media (hover: hover) and (pointer: fine) {
a.primary:hover {
background: var(--button-primary-hover);
border-color: transparent;
}
a.secondary:hover {
background: var(--button-secondary-hover);
border-color: transparent;
}
.footer a:hover {
text-decoration: underline;
text-underline-offset: 4px;
}
}
@media (max-width: 600px) {
.page {
padding: 32px;
padding-bottom: 80px;
}
.main {
align-items: center;
}
.main ol {
text-align: center;
}
.ctas {
flex-direction: column;
}
.ctas a {
font-size: 14px;
height: 40px;
padding: 0 16px;
}
a.secondary {
min-width: auto;
}
.footer {
flex-wrap: wrap;
align-items: center;
justify-content: center;
}
}
@media (prefers-color-scheme: dark) {
.logo {
filter: invert();
}
}

View File

@@ -0,0 +1,24 @@
"use client";
import { Box, Button, Text, Title } from "metabase/ui";
export default function Home() {
return (
<Box mt={400}>
<Title size={40}>Metabase's Design System</Title>
<Text maw={600} mt="md">
This is the documentation for our design system. It's meant to help
designers and developers who work on Metabase understand how and why to
apply patterns and make certain design choices in the product.
</Text>
<Box mt="lg">
<a
href="https://metaboat.slack.com/archives/C057WD5L0JG"
target="_blank"
>
<Button mr="md">#design-system on Slack</Button>
</a>
<Button>Github</Button>
</Box>
</Box>
);
}

View File

@@ -0,0 +1,111 @@
import { Indicator, Badge, Code } from "@mantine/core"
import { Button, Icon, Menu } from "metabase/ui"
import { useState } from "react"
# For your consideration
When
## Example
```jsx
function MyNewFeature () {
// this will be replaced with the K,V store
const [seen, setSeen] = React.useState(false)
const [isNew, setIsNew] = React.useState(true)
const [isReccomended, setIsReccomended] = React.useState(true)
// pretend feature modals
const [showTransmogrifyModal, setShowTransmogrifyModal] = React.useState(false)
const [showCalvinballModal, setShowCalvinballModal] = React.useState(false)
return (
<>
<Box>
<Menu>
<Menu.Target>
{ /* we use disabled to react to seen state */}
<Indicator inline disabled={seen}>
{ /* clearing */}
<Button onClick={() => setSeen(true)}>Menu</Button>
</Indicator>
</Menu.Target>
<Menu.Dropdown>
<Menu.Item>Edit</Menu.Item>
<Menu.Item
onClick={() => {
setShowTransmogrifyModal(true)
setIsNew(false)
}}
rightSection={
isNew && <Badge variant="light">
{/* callout reason - new */}
New
</Badge>
}>Transmogrify</Menu.Item>
<Menu.Item onClick={() => {
setShowCalvinballModal(true)
}}
rightSection={
isReccomended && <Badge variant="light">
{/* callout reason - recommended */}
Recommended
</Badge>
}>Calvinball</Menu.Item>
<Menu.Item>Trash</Menu.Item>
</Menu.Dropdown>
</Menu>
</Box>
{ /* everything here is just for demo purposes */ }
<Box mt="3">
<table>
<tr>
<td>Seen:</td>
<td>{seen.toString()}</td>
</tr>
<tr>
<td>New:</td>
<td>{isNew.toString()}</td>
</tr>
<tr>
<td>Reccomended:</td>
<td>{isReccomended.toString()}</td>
</tr>
</table>
</Box>
<Modal title="Transmogrify" opened={showTransmogrifyModal} onClose={() => setShowTransmogrifyModal(false)}>
<Text>Hobbes appears worried, but you can go ahead if you're sure.</Text>
</Modal>
<Modal title="Calvinball" opened={showCalvinballModal} onClose={() => setShowCalvinballModal(false)}>
<img href="https://en.wikipedia.org/wiki/Calvin_and_Hobbes#/media/File:Calvin_and_Hobbes.png" />
<Text>There are no rules, and the outcome will be determined in real time, but you knew that already.</Text>
<Box>
<Button onClick={() => {
setShowCalvinballModal(false)
}}>Opposite Pole</Button>
<Button variant="filled" onClick={() => {
setIsReccomended(false)
setShowCalvinballModal(false)
}}>Wickets</Button>
</Box>
</Modal>
</>
)
}
```
## Terms
- **Feature trigger** - The element the user interacts with that would start the first part of a new or recommended feature flow.
- **Eligible** - A boolean for whether the feature trigger should be called out.
- **Clearing** - An action the user performs that removes a callout or indicator.
- **Indicator** - A UI element that draws the user’s attention to a parent element containing feature triggers and callouts. For our purposes this will be a little blue dot that can be affixed to other elements. The indicator never contains text or other content, it just indicates.
- See below for one case where an indicator might be used in place of a callout.
- **Callout reason** - A visual element, with text that is affixed to a feature trigger to describe why the element is being called out for the user’s attention.
- **New** - A feature or flow that a user wouldn’t have seen before. Note this can mean that if someone version skips, many things may be “new”
- **Recommended** - This feature meets a condition to where we believe its use would make Metabase better. E.x. collection cleanup.
Text is used because we want it to be clear to the user why their focus is being mined.

View File

@@ -0,0 +1,39 @@
"use client";
import Docs from "./docs.mdx";
import { LiveEditor, LivePreview, LiveProvider, LiveError } from "react-live";
import { Box, Button, Card, Icon, Menu, Modal, Text } from "metabase/ui";
import { Badge, Code, Indicator } from "@mantine/core";
function CodeTest({ children }) {
const scope = {
Badge,
Box,
Code,
Button,
Menu,
Modal,
Indicator,
Icon,
Text,
};
return (
<LiveProvider code={children} scope={scope} disabled>
<Box p="md">
<LivePreview />
</Box>
<Card p="0">
<LiveEditor />
<LiveError />
</Card>
</LiveProvider>
);
}
const components = {
code: CodeTest,
};
export default function Page() {
// @ts-ignore
return <Docs components={components} />;
}

View File

@@ -0,0 +1,7 @@
import type { MDXComponents } from "mdx/types";
export function useMDXComponents(components: MDXComponents): MDXComponents {
return {
...components,
};
}

View File

@@ -0,0 +1,15 @@
import withMDX from "@next/mdx";
/** @type {import('next').NextConfig} */
const nextConfig = {
pageExtensions: ["js", "jsx", "ts", "tsx", "md", "mdx"],
};
const withMdx = withMDX({
extension: /\.mdx?$/,
processor: {
providerImportSource: "@mdx-js/react",
},
});
export default withMdx(nextConfig);

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,28 @@
{
"name": "docs",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev -p 4000",
"build": "next build",
"start": "next start -p 4000",
"lint": "next lint"
},
"dependencies": {
"@mdx-js/loader": "^3.0.1",
"@next/mdx": "^14.2.15",
"@types/mdx": "^2.0.13",
"next": "14.2.15",
"react": "^18",
"react-dom": "^18",
"react-live": "^4.1.8"
},
"devDependencies": {
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"eslint": "^8",
"eslint-config-next": "14.2.15",
"typescript": "^5"
}
}

View File

@@ -0,0 +1,34 @@
{
"compilerOptions": {
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./*"],
"metabase/ui": ["../"],
"metabase/lib/*": ["../../lib/*"],
"metabase/hooks/*": ["../../hooks/*"],
"metabase-types/*": ["../../../metabase-types/*"],
"embedding-sdk/*": [
"../../../../../enterprise/frontend/src/embedding-sdk/*"
],
"metabase/css/*": ["../../css/*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}