Improving our get started page in BUI
Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
This commit is contained in:
@@ -1,173 +1,9 @@
|
||||
import { ComponentCards, ComponentCard } from '@/components/ComponentCards';
|
||||
import { LayoutComponents } from '@/components/LayoutComponents';
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
import { ComponentGrid } from '@/components/ComponentGrid';
|
||||
|
||||
# Components
|
||||
|
||||
## Layout Components
|
||||
Below is the full list of components available in the library. Each component is designed to be flexible, accessible,
|
||||
and easy to integrate into your plugins. We are actively working on adding more components, so check back regularly
|
||||
for updates.
|
||||
|
||||
We built a couple of layout components to help you build responsive elements
|
||||
that will be consistent with the rest of your Backstage instance. These
|
||||
components are opinionated and use TypeScript to ensure that the props you
|
||||
provide are the ones coming from the theme.
|
||||
|
||||
<CodeBlock
|
||||
title="Layout components"
|
||||
code={`<Flex direction="column" gap="4">
|
||||
<Box>Hello World</Box>
|
||||
<Inline gap="sm">
|
||||
<Box>Project 1</Box>
|
||||
<Box>Project 2</Box>
|
||||
</Inline>
|
||||
</Flex>
|
||||
`}
|
||||
/>
|
||||
|
||||
<LayoutComponents />
|
||||
|
||||
## Components
|
||||
|
||||
### Actions
|
||||
|
||||
<ComponentCards>
|
||||
<ComponentCard
|
||||
title="Button"
|
||||
description="A button component to help you trigger actions."
|
||||
href="/components/button"
|
||||
/>
|
||||
<ComponentCard
|
||||
title="ButtonLink"
|
||||
description="A button component to help you trigger actions."
|
||||
href="/components/button-link"
|
||||
/>
|
||||
<ComponentCard
|
||||
title="ButtonIcon"
|
||||
description="A button for actions with an icon."
|
||||
href="/components/button-icon"
|
||||
/>
|
||||
<ComponentCard
|
||||
title="Link"
|
||||
description="A link component to help you navigate to other pages."
|
||||
href="/components/link"
|
||||
/>
|
||||
</ComponentCards>
|
||||
|
||||
### Content display
|
||||
|
||||
<ComponentCards>
|
||||
<ComponentCard
|
||||
title="Card"
|
||||
description="A component to separate your content on the page."
|
||||
href="/components/card"
|
||||
/>
|
||||
<ComponentCard
|
||||
title="Collapsible"
|
||||
description="A collapsible component for expandable content."
|
||||
href="/components/collapsible"
|
||||
/>
|
||||
</ComponentCards>
|
||||
|
||||
### Selection and inputs
|
||||
|
||||
<ComponentCards>
|
||||
<ComponentCard
|
||||
title="Checkbox"
|
||||
description="A checkbox component to help you select items."
|
||||
href="/components/checkbox"
|
||||
/>
|
||||
<ComponentCard
|
||||
title="TextField"
|
||||
description="A text field component to help you input text."
|
||||
href="/components/text-field"
|
||||
/>
|
||||
<ComponentCard
|
||||
title="SearchField"
|
||||
description="A search field component to help you search for items."
|
||||
href="/components/search-field"
|
||||
/>
|
||||
<ComponentCard
|
||||
title="PasswordField"
|
||||
description="A password field component to help you input passwords."
|
||||
href="/components/password-field"
|
||||
/>
|
||||
<ComponentCard
|
||||
title="Select"
|
||||
description="A select component to help you select items."
|
||||
href="/components/select"
|
||||
/>
|
||||
<ComponentCard
|
||||
title="Switch"
|
||||
description="A switch component to help you toggle items."
|
||||
href="/components/switch"
|
||||
/>
|
||||
<ComponentCard
|
||||
title="RadioGroup"
|
||||
description="A radio group component to help you select items."
|
||||
href="/components/radio-group"
|
||||
/>
|
||||
</ComponentCards>
|
||||
|
||||
### Navigation
|
||||
|
||||
<ComponentCards>
|
||||
<ComponentCard
|
||||
title="Header"
|
||||
description="A header component to help you display a header."
|
||||
href="/components/header"
|
||||
/>
|
||||
<ComponentCard
|
||||
title="HeaderPage"
|
||||
description="A header to complement the Header component."
|
||||
href="/components/header-page"
|
||||
/>
|
||||
<ComponentCard
|
||||
title="Menu"
|
||||
description="A menu component to help you display a menu."
|
||||
href="/components/menu"
|
||||
/>
|
||||
<ComponentCard
|
||||
title="Tabs"
|
||||
description="A tabs component to help you display a tabs."
|
||||
href="/components/tabs"
|
||||
/>
|
||||
</ComponentCards>
|
||||
|
||||
### Images and icons
|
||||
|
||||
<ComponentCards>
|
||||
<ComponentCard
|
||||
title="Avatar"
|
||||
description="A avatar component to help you display user avatars."
|
||||
href="/components/avatar"
|
||||
/>
|
||||
<ComponentCard
|
||||
title="Icon"
|
||||
description="A icon component to help you display icons."
|
||||
href="/components/icon"
|
||||
/>
|
||||
</ComponentCards>
|
||||
|
||||
### Feedback indicators
|
||||
|
||||
<ComponentCards>
|
||||
<ComponentCard
|
||||
title="Skeleton"
|
||||
description="A skeleton component to help you display loading states."
|
||||
href="/components/skeleton"
|
||||
/>
|
||||
<ComponentCard
|
||||
title="Tooltip"
|
||||
description="A tooltip component to help you display a tooltip."
|
||||
href="/components/tooltip"
|
||||
/>
|
||||
</ComponentCards>
|
||||
|
||||
### Typography
|
||||
|
||||
<ComponentCards>
|
||||
<ComponentCard
|
||||
title="Text"
|
||||
description="A text component to help you style your text."
|
||||
href="/components/text"
|
||||
/>
|
||||
</ComponentCards>
|
||||
<ComponentGrid />
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
import { Banner } from '@/components/Banner';
|
||||
import { snippet } from './snippets';
|
||||
|
||||
# Installation
|
||||
|
||||
## Import BUI's global styles
|
||||
|
||||
Backstage UI works by importing a global CSS file at the root of your application. This file includes all the default styles for the components.
|
||||
First, you'll need to install the package using a package manager. For example, if you're using Yarn:
|
||||
|
||||
<CodeBlock
|
||||
lang="shell"
|
||||
title="Run this command in your `packages/app` directory"
|
||||
code={`yarn add @backstage/ui`}
|
||||
/>
|
||||
|
||||
<CodeBlock
|
||||
lang="tsx"
|
||||
title="Add this line to `packages/app/src/index.tsx`"
|
||||
code={`import '@backstage/cli/asset-types';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import App from './App';
|
||||
import '@backstage/ui/css/styles.css'; // [!code ++]
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')!).render(<App />);`}
|
||||
/>
|
||||
|
||||
<Banner
|
||||
text="Import these styles only once at your application root. Plugin developers should skip this step to avoid conflicts."
|
||||
variant="warning"
|
||||
/>
|
||||
|
||||
## Use BUI components
|
||||
|
||||
As a plugin maintainer, you can use BUI components in your plugin. As mentioned above, you should not import the styles
|
||||
again in your plugin as this will be handled at the root of your application. To get started, just add the library to
|
||||
your plugin and import the components you need.
|
||||
|
||||
<CodeBlock
|
||||
lang="shell"
|
||||
title="Run this command in your `packages/[your-plugin]` directory"
|
||||
code={`yarn add @backstage/ui`}
|
||||
/>
|
||||
|
||||
<CodeBlock lang="tsx" title="Let's get started 🚀" code={snippet} />
|
||||
@@ -0,0 +1,6 @@
|
||||
export const snippet = `import { Flex, Button, Text } from '@backstage/ui';
|
||||
|
||||
<Flex>
|
||||
<Text>Hello World</Text>
|
||||
<Button>Click me</Button>
|
||||
</Flex>;`;
|
||||
+54
-35
@@ -1,59 +1,78 @@
|
||||
import { ComponentGrid } from '@/components/ComponentGrid';
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
import { Banner } from '@/components/Banner';
|
||||
import { snippet } from './snippets';
|
||||
import { ColorFamily } from '@/components/ColorFamily';
|
||||
import {
|
||||
surfacesSnippet,
|
||||
adaptiveSnippet,
|
||||
customCardSnippet,
|
||||
customTokensSnippet,
|
||||
colorPickerSnippet,
|
||||
} from './snippets';
|
||||
|
||||
# Welcome to Backstage UI
|
||||
# Get Started with BUI
|
||||
|
||||
Backstage UI is a design system created specifically for Backstage, built with React, TypeScript, and vanilla CSS.
|
||||
This open-source library is hosted in the Backstage monorepo. While it can be used in other projects, Backstage UI
|
||||
is designed to deliver a consistent, accessible, and extensible experience tailored to Backstage users.
|
||||
|
||||
## Import BUI's global styles
|
||||
Backstage UI is installed by default on every instance of Backstage, so you can start using it right away.
|
||||
If your setup doesn't include it yet, follow the [installation guide](/get-started/installation) to get started.
|
||||
|
||||
Backstage UI works by importing a global CSS file at the root of your application. This file includes all the default styles for the components.
|
||||
First, you'll need to install the package using a package manager. For example, if you're using Yarn:
|
||||
## Layout containers
|
||||
|
||||
<CodeBlock
|
||||
lang="shell"
|
||||
title="Run this command in your `packages/app` directory"
|
||||
code={`yarn add @backstage/ui`}
|
||||
/>
|
||||
[`Box`](/components/box), [`Flex`](/components/flex), [`Grid`](/components/grid), and [`Card`](/components/card) are the foundation of every layout in Backstage UI.
|
||||
Each one offers a set of utility props that map directly to our design tokens, so you can build consistent
|
||||
layouts without writing any CSS. When nested, they also act as surfaces and automatically increment the
|
||||
background depth so visual hierarchy is handled for you.
|
||||
|
||||
<CodeBlock
|
||||
lang="tsx"
|
||||
title="Add this line to `packages/app/src/index.tsx`"
|
||||
code={`import '@backstage/cli/asset-types';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import App from './App';
|
||||
import '@backstage/ui/css/styles.css'; // [!code ++]
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')!).render(<App />);`}
|
||||
title="Nested surfaces with automatic styling"
|
||||
code={surfacesSnippet}
|
||||
/>
|
||||
|
||||
<Banner
|
||||
text="Import these styles only once at your application root. Plugin developers should skip this step to avoid conflicts."
|
||||
variant="warning"
|
||||
/>
|
||||
## Adaptive components
|
||||
|
||||
## Use BUI components
|
||||
|
||||
As a plugin maintainer, you can use BUI components in your plugin. As mentioned above, you should not import the styles
|
||||
again in your plugin as this will be handled at the root of your application. To get started, just add the library to
|
||||
your plugin and import the components you need.
|
||||
Components like [`Card`](/components/card), [`Button`](/components/button), [`Text`](/components/text), and others are **adaptive components**. They
|
||||
automatically adjust their colors, borders, and backgrounds to match the surface they live on. Drop a
|
||||
[`Button`](/components/button) inside a [`Card`](/components/card) inside a [`Box`](/components/box) and each component styles itself appropriately
|
||||
without any extra configuration.
|
||||
|
||||
<CodeBlock
|
||||
lang="shell"
|
||||
title="Run this command in your `packages/[your-plugin]` directory"
|
||||
code={`yarn add @backstage/ui`}
|
||||
lang="tsx"
|
||||
title="Nested surfaces with automatic styling"
|
||||
code={adaptiveSnippet}
|
||||
/>
|
||||
|
||||
<CodeBlock lang="tsx" title="Let's get started 🚀" code={snippet} />
|
||||
## The neutral scale background colors
|
||||
|
||||
## Support
|
||||
<ColorFamily />
|
||||
|
||||
Now that you have the basics down, you can start building your plugin using the new design system.
|
||||
Please familiarise yourself first with our theming principles. This will help you understand the core concepts of the design system.
|
||||
If you have any questions, please reach out to us on [Discord](https://discord.gg/MUpMjP2).
|
||||
## Creating custom components
|
||||
|
||||
As much as possible we would like you to use components directly without creating custom components. If you need to create a custom component, you should use the components provided by Backstage UI.
|
||||
|
||||
<CodeBlock
|
||||
lang="tsx"
|
||||
title="Creating a custom card using BUI components"
|
||||
code={customCardSnippet}
|
||||
/>
|
||||
|
||||
If you need to build custom components outside of BUI, you can use our [design tokens](/tokens) as CSS variables to ensure your styles stay consistent with the rest of the system.
|
||||
|
||||
<CodeBlock
|
||||
lang="tsx"
|
||||
title="Creating a custom component using BUI tokens"
|
||||
code={customTokensSnippet}
|
||||
/>
|
||||
|
||||
When building custom interactive components, we strongly recommend using [React Aria](https://react-spectrum.adobe.com/react-aria/) as your foundation. React Aria provides all the necessary accessibility features out of the box — keyboard navigation, focus management, ARIA attributes, and screen reader support — so you can focus on styling and logic without worrying about compliance.
|
||||
|
||||
<CodeBlock
|
||||
lang="tsx"
|
||||
title="Building a color picker using React Aria and BUI tokens"
|
||||
code={colorPickerSnippet}
|
||||
/>
|
||||
|
||||
## Philosophy
|
||||
|
||||
|
||||
@@ -1,6 +1,39 @@
|
||||
export const snippet = `import { Flex, Button, Text } from '@backstage/ui';
|
||||
export const surfacesSnippet = `<Flex direction="column" gap="4">
|
||||
<Box bg="neutral-1">
|
||||
<Button variant="secondary">Hello World</Button>
|
||||
</Box>
|
||||
<Box bg="neutral-1">
|
||||
<Button variant="secondary">Hello World</Button>
|
||||
</Box>
|
||||
</Flex>`;
|
||||
|
||||
<Flex>
|
||||
export const adaptiveSnippet = `<Box bg="neutral-1">
|
||||
<Card> {/* automatically set background to neutral-2 */}
|
||||
<Button variant="secondary">Button with background set to neutral-3</Button>
|
||||
</Card>
|
||||
</Box>`;
|
||||
|
||||
export const customCardSnippet = `<Box bg="autoIncrement">
|
||||
<Text>Hello World</Text>
|
||||
<Button>Click me</Button>
|
||||
</Flex>;`;
|
||||
</Box>`;
|
||||
|
||||
export const customTokensSnippet = `<div style={{ backgroundColor: 'var(--bui-bg-solid)' }}>
|
||||
<div style={{ color: 'var(--bui-fg-solid)' }}>Hello World</div>
|
||||
</div>`;
|
||||
|
||||
export const colorPickerSnippet = `import { ColorPicker, ColorArea, ColorSlider, ColorField } from 'react-aria-components';
|
||||
|
||||
function MyColorPicker() {
|
||||
return (
|
||||
<ColorPicker defaultValue="#184">
|
||||
<ColorArea
|
||||
colorSpace="hsb"
|
||||
xChannel="saturation"
|
||||
yChannel="brightness"
|
||||
style={{ width: 192, height: 192, borderRadius: 'var(--bui-radius-md)' }}
|
||||
/>
|
||||
<ColorSlider colorSpace="hsb" channel="hue" />
|
||||
<ColorField label="Hex" />
|
||||
</ColorPicker>
|
||||
);
|
||||
}`;
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
.wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2rem;
|
||||
margin-top: 1rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.wrapper {
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
.visual {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.text {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.base {
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.baseLabel {
|
||||
display: block;
|
||||
font-size: 13px;
|
||||
color: var(--bui-fg-primary);
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.level {
|
||||
border-radius: 10px;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.levelHeader {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.level > .level > .levelHeader:last-child,
|
||||
.level:not(:has(> .level)) > .levelHeader {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.levelLabel {
|
||||
font-size: 13px;
|
||||
color: var(--bui-fg-primary);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.chips {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.chip {
|
||||
padding: 6px 10px;
|
||||
border-radius: 6px;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
color: var(--bui-fg-primary);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.description {
|
||||
color: var(--primary);
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
font-size: 1rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.description:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.link {
|
||||
color: var(--link);
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
'use client';
|
||||
|
||||
import styles from './ColorFamily.module.css';
|
||||
|
||||
const levels = [1, 2, 3, 4] as const;
|
||||
|
||||
const StateChip = ({
|
||||
level,
|
||||
state,
|
||||
label,
|
||||
}: {
|
||||
level: number;
|
||||
state: string;
|
||||
label: string;
|
||||
}) => (
|
||||
<div
|
||||
className={styles.chip}
|
||||
style={{
|
||||
backgroundColor: `var(--bui-bg-neutral-${level}${state})`,
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
</div>
|
||||
);
|
||||
|
||||
const NeutralLevel = ({
|
||||
level,
|
||||
children,
|
||||
height,
|
||||
}: {
|
||||
level: number;
|
||||
children?: React.ReactNode;
|
||||
height?: number;
|
||||
}) => (
|
||||
<div
|
||||
className={styles.level}
|
||||
style={{
|
||||
backgroundColor: `var(--bui-bg-neutral-${level})`,
|
||||
...(height ? { minHeight: height } : {}),
|
||||
}}
|
||||
>
|
||||
<div className={styles.levelHeader}>
|
||||
<span className={styles.levelLabel}>Neutral {level}</span>
|
||||
<div className={styles.chips}>
|
||||
<StateChip level={level} state="-hover" label="Hover" />
|
||||
<StateChip level={level} state="-pressed" label="Pressed" />
|
||||
<StateChip level={level} state="-disabled" label="Disabled" />
|
||||
</div>
|
||||
</div>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
||||
export const ColorFamily = () => {
|
||||
return (
|
||||
<div className={styles.wrapper}>
|
||||
<div className={styles.visual}>
|
||||
<div
|
||||
className={styles.base}
|
||||
style={{ backgroundColor: 'var(--bui-bg-neutral-0)' }}
|
||||
>
|
||||
<span className={styles.baseLabel}>Neutral 0</span>
|
||||
<NeutralLevel level={1}>
|
||||
<NeutralLevel level={2}>
|
||||
<NeutralLevel level={3}>
|
||||
<NeutralLevel level={4} height={120} />
|
||||
</NeutralLevel>
|
||||
</NeutralLevel>
|
||||
</NeutralLevel>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.text}>
|
||||
<p className={styles.description}>
|
||||
BUI uses a layered neutral scale from 0 to 4. Each level nests inside
|
||||
the previous one, automatically incrementing the background depth.
|
||||
This creates clear visual hierarchy without manually picking colors.
|
||||
</p>
|
||||
<p className={styles.description}>
|
||||
Each level can be <strong>interactive</strong> or{' '}
|
||||
<strong>non-interactive</strong>. A Card, for example, can be flat
|
||||
(just a surface) or fully clickable with hover and pressed states. The
|
||||
three interaction states (hover, pressed, disabled) are built into
|
||||
every neutral level.
|
||||
</p>
|
||||
<p className={styles.description}>
|
||||
Explore the full list of color tokens on the{' '}
|
||||
<a href="/tokens" className={styles.link}>
|
||||
tokens page
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export { ColorFamily } from './ColorFamily';
|
||||
@@ -1,54 +0,0 @@
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(1, 1fr);
|
||||
gap: 1rem;
|
||||
margin-top: 1rem;
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.grid {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1280px) {
|
||||
.grid {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
.card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
background-color: var(--bg);
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--border);
|
||||
padding: 16px;
|
||||
gap: 4px;
|
||||
min-height: 120px;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--bg-hover);
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.description {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
color: var(--secondary);
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
import Link from 'next/link';
|
||||
import styles from './ComponentCards.module.css';
|
||||
|
||||
export const ComponentCards = ({ children }: { children: React.ReactNode }) => {
|
||||
return <div className={styles.grid}>{children}</div>;
|
||||
};
|
||||
|
||||
export const ComponentCard = ({
|
||||
title,
|
||||
description,
|
||||
href,
|
||||
}: {
|
||||
title: string;
|
||||
description: string;
|
||||
href: string;
|
||||
}) => {
|
||||
return (
|
||||
<Link href={href} className={styles.card}>
|
||||
<h3 className={styles.title}>{title}</h3>
|
||||
<p className={styles.description}>{description}</p>
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
export * from './ComponentCards';
|
||||
@@ -0,0 +1,37 @@
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
margin-top: 1rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.grid {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
.item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 10px 0;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
color: var(--primary);
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
color: var(--link);
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.dot {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
background-color: var(--link);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
'use client';
|
||||
|
||||
import Link from 'next/link';
|
||||
import { components } from '@/utils/data';
|
||||
import styles from './ComponentGrid.module.css';
|
||||
|
||||
export const ComponentGrid = () => {
|
||||
return (
|
||||
<div className={styles.grid}>
|
||||
{components.map(item => (
|
||||
<Link
|
||||
key={item.slug}
|
||||
href={`/components/${item.slug}`}
|
||||
className={styles.item}
|
||||
>
|
||||
{item.title}
|
||||
{item.status === 'new' && <span className={styles.dot} />}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export * from './ComponentGrid';
|
||||
@@ -1,48 +0,0 @@
|
||||
.layoutComponents {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
gap: 1rem;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 2rem;
|
||||
|
||||
& svg rect {
|
||||
transition: fill 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
& .box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: calc(50% - 0.5rem);
|
||||
margin-bottom: 1rem;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
& .content {
|
||||
flex: none;
|
||||
background-color: var(--bg);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 4px;
|
||||
width: 100%;
|
||||
height: 180px;
|
||||
transition: all 0.2s ease-in-out;
|
||||
margin-bottom: 0.75rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-4px);
|
||||
}
|
||||
}
|
||||
|
||||
& .title {
|
||||
font-size: 16px;
|
||||
transition: color 0.2s ease-in-out;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
& .description {
|
||||
font-size: 16px;
|
||||
color: var(--secondary);
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
import { BoxSvg } from './svgs/box';
|
||||
import { FlexSvg } from './svgs/flex';
|
||||
import { GridSvg } from './svgs/grid';
|
||||
import { ContainerSvg } from './svgs/container';
|
||||
import styles from './LayoutComponents.module.css';
|
||||
import Link from 'next/link';
|
||||
|
||||
export const LayoutComponents = () => {
|
||||
return (
|
||||
<div className={styles.layoutComponents}>
|
||||
<div className={styles.box}>
|
||||
<Link className={styles.content} href="/components/box">
|
||||
<BoxSvg />
|
||||
</Link>
|
||||
<div className={styles.title}>Box</div>
|
||||
<div className={styles.description}>
|
||||
The most basic layout component
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.box}>
|
||||
<Link className={styles.content} href="/components/flex">
|
||||
<FlexSvg />
|
||||
</Link>
|
||||
<div className={styles.title}>Flex</div>
|
||||
<div className={styles.description}>
|
||||
Arrange your components vertically
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.box}>
|
||||
<Link className={styles.content} href="/components/grid">
|
||||
<GridSvg />
|
||||
</Link>
|
||||
<div className={styles.title}>Grid</div>
|
||||
<div className={styles.description}>
|
||||
Arrange your components in a grid
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.box}>
|
||||
<Link className={styles.content} href="/components/container">
|
||||
<ContainerSvg />
|
||||
</Link>
|
||||
<div className={styles.title}>Container</div>
|
||||
<div className={styles.description}>
|
||||
A container for your components
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
export { LayoutComponents } from './LayoutComponents';
|
||||
@@ -49,16 +49,13 @@
|
||||
}
|
||||
|
||||
.sectionTitle {
|
||||
font-size: 0.6875rem;
|
||||
font-weight: 500;
|
||||
padding: 0 12px 4px;
|
||||
color: var(--secondary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
color: var(--primary);
|
||||
margin-top: 40px;
|
||||
text-transform: uppercase;
|
||||
|
||||
&:first-child {
|
||||
margin-top: 12px;
|
||||
}
|
||||
padding: 8px 12px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.line {
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import Link from 'next/link';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { Fragment } from 'react';
|
||||
import clsx from 'clsx';
|
||||
import {
|
||||
RiCollageLine,
|
||||
@@ -12,26 +11,13 @@ import {
|
||||
RiServiceLine,
|
||||
RiStackLine,
|
||||
} from '@remixicon/react';
|
||||
import { components, layoutComponents } from '@/utils/data';
|
||||
import { components } from '@/utils/data';
|
||||
import styles from './Navigation.module.css';
|
||||
|
||||
interface NavigationProps {
|
||||
onLinkClick?: () => void;
|
||||
}
|
||||
|
||||
const data = [
|
||||
{
|
||||
title: 'Layout Components',
|
||||
content: layoutComponents,
|
||||
url: '/components',
|
||||
},
|
||||
{
|
||||
title: 'Components',
|
||||
content: components,
|
||||
url: '/components',
|
||||
},
|
||||
];
|
||||
|
||||
export const Navigation = ({ onLinkClick }: NavigationProps) => {
|
||||
const pathname = usePathname();
|
||||
|
||||
@@ -55,16 +41,6 @@ export const Navigation = ({ onLinkClick }: NavigationProps) => {
|
||||
Tokens
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
href="/components"
|
||||
data-active={pathname.startsWith('/components')}
|
||||
onClick={onLinkClick}
|
||||
>
|
||||
<RiCollageLine size={20} />
|
||||
Components
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<div data-disabled={true}>
|
||||
<RiStackLine size={20} />
|
||||
@@ -89,35 +65,31 @@ export const Navigation = ({ onLinkClick }: NavigationProps) => {
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
{data.map(section => {
|
||||
<div className={styles.sectionTitle}>
|
||||
<RiCollageLine size={20} />
|
||||
<span>Components</span>
|
||||
</div>
|
||||
{components.map(item => {
|
||||
const isActive = pathname === `/components/${item.slug}`;
|
||||
|
||||
return (
|
||||
<Fragment key={section.title}>
|
||||
<div className={styles.sectionTitle}>{section.title}</div>
|
||||
|
||||
{section.content.map(item => {
|
||||
const isActive = pathname === `${section.url}/${item.slug}`;
|
||||
|
||||
return (
|
||||
<Link
|
||||
href={`${section.url}/${item.slug}`}
|
||||
key={item.slug}
|
||||
className={clsx(styles.line, {
|
||||
[styles.active]: isActive,
|
||||
})}
|
||||
onClick={onLinkClick}
|
||||
>
|
||||
<div className={styles.lineTitle}>{item.title}</div>
|
||||
<div className={styles.lineStatus}>
|
||||
{item.status === 'alpha' && 'Alpha'}
|
||||
{item.status === 'beta' && 'Beta'}
|
||||
{item.status === 'inProgress' && 'In Progress'}
|
||||
{item.status === 'stable' && 'Stable'}
|
||||
{item.status === 'deprecated' && 'Deprecated'}
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
<Link
|
||||
href={`/components/${item.slug}`}
|
||||
key={item.slug}
|
||||
className={clsx(styles.line, {
|
||||
[styles.active]: isActive,
|
||||
})}
|
||||
</Fragment>
|
||||
onClick={onLinkClick}
|
||||
>
|
||||
<div className={styles.lineTitle}>{item.title}</div>
|
||||
<div className={styles.lineStatus}>
|
||||
{item.status === 'alpha' && 'Alpha'}
|
||||
{item.status === 'beta' && 'Beta'}
|
||||
{item.status === 'inProgress' && 'In Progress'}
|
||||
{item.status === 'stable' && 'Stable'}
|
||||
{item.status === 'deprecated' && 'Deprecated'}
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
|
||||
@@ -21,7 +21,7 @@ import styles from './Toolbar.module.css';
|
||||
import { usePlayground } from '@/utils/playground-context';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import Link from 'next/link';
|
||||
import { components, layoutComponents } from '@/utils/data';
|
||||
import { components } from '@/utils/data';
|
||||
import { Logo } from '@/components/Sidebar/Logo';
|
||||
|
||||
interface ToolbarProps {
|
||||
@@ -46,12 +46,7 @@ export const Toolbar = ({ version }: ToolbarProps) => {
|
||||
|
||||
// Determine breadcrumb content based on current path
|
||||
const getBreadcrumb = () => {
|
||||
const allComponents = [...components, ...layoutComponents];
|
||||
|
||||
// Root page
|
||||
if (pathname === '/') {
|
||||
return { section: null, title: 'Getting Started' };
|
||||
}
|
||||
const allComponents = components;
|
||||
|
||||
// Components index page
|
||||
if (pathname === '/components') {
|
||||
|
||||
+16
-20
@@ -4,25 +4,6 @@ interface Page {
|
||||
status?: 'alpha' | 'beta' | 'stable' | 'deprecated' | 'inProgress' | 'new';
|
||||
}
|
||||
|
||||
export const layoutComponents: Page[] = [
|
||||
{
|
||||
title: 'Box',
|
||||
slug: 'box',
|
||||
},
|
||||
{
|
||||
title: 'Container',
|
||||
slug: 'container',
|
||||
},
|
||||
{
|
||||
title: 'Grid',
|
||||
slug: 'grid',
|
||||
},
|
||||
{
|
||||
title: 'Flex',
|
||||
slug: 'flex',
|
||||
},
|
||||
];
|
||||
|
||||
export const components: Page[] = [
|
||||
{
|
||||
title: 'Accordion',
|
||||
@@ -36,6 +17,10 @@ export const components: Page[] = [
|
||||
title: 'Avatar',
|
||||
slug: 'avatar',
|
||||
},
|
||||
{
|
||||
title: 'Box',
|
||||
slug: 'box',
|
||||
},
|
||||
{
|
||||
title: 'Button',
|
||||
slug: 'button',
|
||||
@@ -56,10 +41,22 @@ export const components: Page[] = [
|
||||
title: 'Checkbox',
|
||||
slug: 'checkbox',
|
||||
},
|
||||
{
|
||||
title: 'Container',
|
||||
slug: 'container',
|
||||
},
|
||||
{
|
||||
title: 'Dialog',
|
||||
slug: 'dialog',
|
||||
},
|
||||
{
|
||||
title: 'Flex',
|
||||
slug: 'flex',
|
||||
},
|
||||
{
|
||||
title: 'Grid',
|
||||
slug: 'grid',
|
||||
},
|
||||
{
|
||||
title: 'Header',
|
||||
slug: 'header',
|
||||
@@ -83,7 +80,6 @@ export const components: Page[] = [
|
||||
{
|
||||
title: 'Popover',
|
||||
slug: 'popover',
|
||||
status: 'new',
|
||||
},
|
||||
{
|
||||
title: 'RadioGroup',
|
||||
|
||||
@@ -1,17 +1,10 @@
|
||||
import { components, layoutComponents } from './data';
|
||||
import { components } from './data';
|
||||
|
||||
export function getPageName(slug: string): string | null {
|
||||
// Search in components array
|
||||
const component = components.find(c => c.slug === slug);
|
||||
if (component) {
|
||||
return component.title;
|
||||
}
|
||||
|
||||
// Search in layoutComponents array
|
||||
const layoutComponent = layoutComponents.find(c => c.slug === slug);
|
||||
if (layoutComponent) {
|
||||
return layoutComponent.title;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user