Add more functionalities to our playground

Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
This commit is contained in:
Charles de Dreuille
2024-12-31 17:33:11 +01:00
parent 00995ef08f
commit 39b3c2e123
14 changed files with 219 additions and 36 deletions
+50
View File
@@ -0,0 +1,50 @@
import { ButtonProps, Text } from '@backstage/canon';
import { Stack, Inline, Button } from '@backstage/canon';
const variants: {
variant: ButtonProps['variant'];
size: ButtonProps['size'];
}[] = [
{ variant: 'primary', size: 'small' },
{ variant: 'primary', size: 'medium' },
{ variant: 'secondary', size: 'small' },
{ variant: 'secondary', size: 'medium' },
{ variant: 'tertiary', size: 'small' },
{ variant: 'tertiary', size: 'medium' },
];
const capitalizeFirstLetter = (string: string): string => {
return string.charAt(0).toUpperCase() + string.slice(1);
};
export const ButtonPlayground = () => {
return (
<Stack>
{variants.map(({ variant, size }) => (
<Stack key={`${variant}-${size}`}>
<Text>
{capitalizeFirstLetter(variant as string)} -{' '}
{capitalizeFirstLetter(size as string)}
</Text>
<Inline alignY="center">
<Button iconStart="cloud" variant={variant} size={size}>
Button
</Button>
<Button iconEnd="chevronRight" variant={variant} size={size}>
Button
</Button>
<Button
iconStart="cloud"
iconEnd="chevronRight"
style={{ width: '200px' }}
variant={variant}
size={size}
>
Button
</Button>
</Inline>
</Stack>
))}
</Stack>
);
};
@@ -0,0 +1,18 @@
import { Text } from '@backstage/canon';
import { Stack, Inline, Checkbox } from '@backstage/canon';
export const CheckboxPlayground = () => {
return (
<Stack>
<Stack>
<Text>All variants</Text>
<Inline alignY="center">
<Checkbox />
<Checkbox checked />
<Checkbox label="Checkbox" />
<Checkbox label="Checkbox" checked />
</Inline>
</Stack>
</Stack>
);
};
+48 -25
View File
@@ -1,15 +1,32 @@
'use client';
import styles from './styles.module.css';
import { Text } from '@backstage/canon';
import { Inline, Stack, Text } from '@backstage/canon';
import { screenSizes } from '@/utils/data';
import { Frame } from '@/components/Frame';
import { Stack, Inline, Button } from '@backstage/canon';
import { usePlayground } from '@/utils/playground-context';
import { ButtonPlayground } from './button';
import { CheckboxPlayground } from './checkbox';
import styles from './styles.module.css';
import { ReactNode } from 'react';
export default function PlaygroundPage() {
const { selectedScreenSizes, selectedComponents } = usePlayground();
const filteredScreenSizes = screenSizes.filter(item =>
selectedScreenSizes.includes(item.slug),
);
if (filteredScreenSizes.length === 0) {
return (
<div className={styles.containerEmpty}>
<Content />
</div>
);
}
return (
<div className={styles.container}>
{screenSizes.map(screenSize => (
{filteredScreenSizes.map(screenSize => (
<div
className={styles.breakpointContainer}
style={{ width: screenSize.width }}
@@ -20,27 +37,7 @@ export default function PlaygroundPage() {
</Text>
<div className={styles.breakpointContent}>
<Frame>
<Stack>
<Stack>
<Text>Button - Primary - Small</Text>
<Inline alignY="center">
<Button iconStart="cloud">Button</Button>
<Button iconEnd="chevronRight">Button</Button>
<Button iconStart="cloud" iconEnd="chevronRight">
Button
</Button>
<Button iconStart="cloud" iconEnd="chevronRight">
Button
</Button>
<Button iconStart="cloud" iconEnd="chevronRight">
Button
</Button>
<Button iconStart="cloud" iconEnd="chevronRight">
Button
</Button>
</Inline>
</Stack>
</Stack>
<Content />
</Frame>
</div>
</div>
@@ -48,3 +45,29 @@ export default function PlaygroundPage() {
</div>
);
}
const Content = () => {
const { selectedComponents } = usePlayground();
return (
<Stack gap="xl">
{selectedComponents.find(c => c === 'button') && (
<Line content={<ButtonPlayground />} title="Button" />
)}
{selectedComponents.find(c => c === 'checkbox') && (
<Line content={<CheckboxPlayground />} title="Checkbox" />
)}
</Stack>
);
};
const Line = ({ content, title }: { content: ReactNode; title: string }) => {
return (
<Inline gap={{ xs: 'xs', md: 'xl' }}>
<div style={{ width: '100px' }}>
<Text>{title}</Text>
</div>
{content}
</Inline>
);
};
@@ -6,6 +6,10 @@
overflow-x: scroll;
}
.containerEmpty {
padding: 24px;
}
.breakpointContainer {
flex-shrink: 0;
margin-top: 40px;
+4 -1
View File
@@ -3,11 +3,14 @@
import { ReactNode } from 'react';
import { CanonProvider } from '@backstage/canon';
import { ThemeProvider } from 'next-themes';
import { PlaygroundProvider } from '@/utils/playground-context';
export const Providers = ({ children }: { children: ReactNode }) => {
return (
<CanonProvider>
<ThemeProvider defaultTheme="light">{children}</ThemeProvider>
<ThemeProvider defaultTheme="light">
<PlaygroundProvider>{children}</PlaygroundProvider>
</ThemeProvider>
</CanonProvider>
);
};
@@ -13,7 +13,7 @@
overflow: hidden;
}
.sidebar svg path {
.logo path {
fill: var(--canon-text-primary);
}
@@ -12,6 +12,7 @@ export const Sidebar = () => {
height="27"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={styles.logo}
>
<path d="M77.414 9.71h3.036v2.992l-.264-.022c.279-.88.748-1.65 1.408-2.31.675-.66 1.533-.99 2.574-.99 1.086 0 1.9.352 2.442 1.056.558.689.836 1.598.836 2.728v7.502H84.41v-6.6c0-.66-.117-1.159-.352-1.496-.234-.338-.623-.506-1.166-.506-.601 0-1.122.271-1.562.814a4.321 4.321 0 0 0-.88 1.848v5.94h-3.036V9.71ZM69.868 21.04c-1.13 0-2.142-.257-3.036-.77a5.547 5.547 0 0 1-2.068-2.09c-.484-.895-.726-1.892-.726-2.992 0-1.115.242-2.112.726-2.992a5.503 5.503 0 0 1 2.068-2.112c.88-.514 1.892-.77 3.036-.77 1.144 0 2.156.256 3.036.77a5.347 5.347 0 0 1 2.046 2.112c.498.88.748 1.87.748 2.97 0 1.114-.25 2.12-.748 3.014a5.343 5.343 0 0 1-2.068 2.09c-.88.513-1.885.77-3.014.77Zm0-2.618c.557 0 1.048-.132 1.474-.396a2.79 2.79 0 0 0 .99-1.144c.234-.499.352-1.064.352-1.694 0-.63-.118-1.188-.352-1.672a2.764 2.764 0 0 0-.99-1.166c-.426-.279-.917-.418-1.474-.418-.558 0-1.05.14-1.474.418a2.764 2.764 0 0 0-.99 1.166c-.22.484-.33 1.041-.33 1.672 0 .63.11 1.195.33 1.694.234.484.564.865.99 1.144.425.264.916.396 1.474.396ZM52.385 9.71h3.036v2.992l-.264-.022c.279-.88.748-1.65 1.408-2.31.675-.66 1.533-.99 2.574-.99 1.085 0 1.9.352 2.442 1.056.557.689.836 1.598.836 2.728v7.502h-3.036v-6.6c0-.66-.117-1.159-.352-1.496-.235-.338-.623-.506-1.166-.506-.601 0-1.122.271-1.562.814a4.321 4.321 0 0 0-.88 1.848v5.94h-3.036V9.71ZM43.904 20.952c-1.026 0-1.87-.308-2.53-.924-.66-.616-.99-1.416-.99-2.398 0-1.115.418-2.01 1.254-2.684.85-.69 2.105-1.034 3.762-1.034h2.75l-.924.352v-.462c0-.572-.176-1.02-.528-1.342-.352-.338-.931-.506-1.738-.506-.69 0-1.386.154-2.09.462-.704.308-1.29.69-1.76 1.144v-2.882c.47-.352 1.1-.66 1.892-.924a7.942 7.942 0 0 1 2.486-.396c1.599 0 2.78.41 3.542 1.232.778.806 1.166 1.914 1.166 3.322v5.742c0 .176.008.352.022.528.03.161.066.322.11.484h-3.014v-3.3l.044 1.386a4.044 4.044 0 0 1-1.32 1.606c-.572.396-1.283.594-2.134.594Zm.858-2.156c.558 0 1.049-.206 1.474-.616.44-.426.77-.954.99-1.584v-.792h-1.562c-.762 0-1.334.146-1.716.44-.381.278-.572.674-.572 1.188 0 .41.125.74.374.99.264.25.602.374 1.012.374ZM34.66 21.04c-1.525 0-2.874-.345-4.047-1.034-1.174-.704-2.09-1.68-2.75-2.926-.646-1.247-.968-2.662-.968-4.246 0-1.584.322-2.992.968-4.224.66-1.232 1.576-2.193 2.75-2.882 1.173-.704 2.522-1.056 4.048-1.056.968 0 1.818.11 2.552.33.748.22 1.356.506 1.826.858v3.498c-.396-.484-.96-.895-1.694-1.232-.719-.338-1.51-.506-2.376-.506-.939 0-1.775.22-2.508.66-.719.44-1.284 1.056-1.694 1.848-.396.792-.594 1.694-.594 2.706 0 1.026.198 1.943.594 2.75.41.792.975 1.408 1.694 1.848.733.44 1.57.66 2.508.66.88 0 1.68-.162 2.398-.484.718-.323 1.276-.726 1.672-1.21v3.41a5.686 5.686 0 0 1-1.87.902c-.704.22-1.54.33-2.508.33ZM11.275 6.271c.276.526.019 1.152-.464 1.498a5.846 5.846 0 0 0 1.004 10.082c.454.205.789.632.789 1.13v6.206a.837.837 0 0 1-.838.837C5.33 26.024.112 20.804.112 14.366V.862C.112.399.487.024.95.024c4.483 0 8.376 2.533 10.325 6.247Z" />
<path d="M14.037 16.729a4.237 4.237 0 0 0 4.234-4.24 4.237 4.237 0 0 0-4.234-4.242 4.237 4.237 0 0 0-4.234 4.241 4.237 4.237 0 0 0 4.234 4.24Z" />
@@ -5,11 +5,38 @@ import { Box, Checkbox, Text } from '@backstage/canon';
import { motion } from 'framer-motion';
import styles from './Sidebar.module.css';
import { usePathname } from 'next/navigation';
import { screenSizes } from '@/utils/data';
import { screenSizes, ScreenSize } from '@/utils/data';
import { usePlayground } from '@/utils/playground-context';
export const Playground = () => {
const pathname = usePathname();
const isPlayground = pathname.includes('/playground');
const {
selectedScreenSizes,
setSelectedScreenSizes,
selectedComponents,
setSelectedComponents,
} = usePlayground();
const handleComponentCheckboxChange = (slug: string) => {
if (selectedComponents.find(item => item === slug)) {
const res = selectedComponents.filter(item => item !== slug);
setSelectedComponents(res);
} else {
setSelectedComponents([...selectedComponents, slug]);
}
};
const handleCheckboxChange = (slug: string) => {
if (selectedScreenSizes.find(item => item === slug)) {
const res = selectedScreenSizes.filter(item => item !== slug);
setSelectedScreenSizes(res);
} else {
setSelectedScreenSizes([...selectedScreenSizes, slug]);
}
};
console.log(selectedComponents);
return (
<motion.div
@@ -35,7 +62,10 @@ export const Playground = () => {
{components.map(({ slug, title }) => (
<div className={styles.line} key={slug}>
<Text variant="body">{title}</Text>
<Checkbox />
<Checkbox
checked={selectedComponents.includes(slug)}
onChange={() => handleComponentCheckboxChange(slug)}
/>
</div>
))}
<Box marginTop="md" marginBottom="xs">
@@ -46,7 +76,10 @@ export const Playground = () => {
{screenSizes.map(({ slug, title }) => (
<div className={styles.line} key={slug}>
<Text variant="body">{title}</Text>
<Checkbox />
<Checkbox
checked={selectedScreenSizes.includes(slug)}
onChange={() => handleCheckboxChange(slug)}
/>
</div>
))}
</motion.div>
+7 -1
View File
@@ -71,7 +71,13 @@ export const components = [
},
];
export const screenSizes = [
export type ScreenSize = {
title: string;
slug: string;
width: number;
};
export const screenSizes: ScreenSize[] = [
{ title: 'Mobile', slug: 'mobile', width: 390 },
{ title: 'Tablet', slug: 'tablet', width: 768 },
{ title: 'Desktop', slug: 'desktop', width: 1280 },
@@ -0,0 +1,41 @@
import React, { createContext, useContext, ReactNode, useState } from 'react';
import { components } from './data';
// Create a context with an empty array as the default value
const PlaygroundContext = createContext<{
selectedScreenSizes: string[];
setSelectedScreenSizes: (screenSizes: string[]) => void;
selectedComponents: string[];
setSelectedComponents: (components: string[]) => void;
}>({
selectedScreenSizes: [],
setSelectedScreenSizes: () => {},
selectedComponents: [],
setSelectedComponents: () => {},
});
// Create a provider component
export const PlaygroundProvider = ({ children }: { children: ReactNode }) => {
const [selectedScreenSizes, setSelectedScreenSizes] = useState<string[]>([]);
const [selectedComponents, setSelectedComponents] = useState<string[]>(
components.map(component => component.slug),
);
return (
<PlaygroundContext.Provider
value={{
selectedScreenSizes,
setSelectedScreenSizes,
selectedComponents,
setSelectedComponents,
}}
>
{children}
</PlaygroundContext.Provider>
);
};
// Create a custom hook to use the screen sizes
export const usePlayground = () => {
return useContext(PlaygroundContext);
};
@@ -29,6 +29,8 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
iconStart,
iconEnd,
children,
style,
...rest
} = props;
const { getResponsiveValue } = useCanon();
@@ -39,7 +41,7 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
return (
<button
{...props}
{...rest}
ref={ref}
disabled={disabled}
className={[
@@ -47,6 +49,7 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
`cn-button-${responsiveSize}`,
`cn-button-${responsiveVariant}`,
].join(' ')}
style={style}
>
<span
className={[
@@ -35,18 +35,18 @@
&:hover {
background-color: transparent;
box-shadow: inset 0 0 0 1px var(--canon-outline-focus);
box-shadow: inset 0 0 0 1px var(--canon-border-focus);
color: var(--canon-text-primary);
}
}
.cn-button-secondary {
background-color: transparent;
box-shadow: inset 0 0 0 1px var(--canon-outline);
box-shadow: inset 0 0 0 1px var(--canon-border-base);
color: var(--canon-text-primary);
&:hover {
box-shadow: inset 0 0 0 1px var(--canon-outline-hover);
box-shadow: inset 0 0 0 1px var(--canon-border-hover);
}
}
@@ -31,4 +31,5 @@ export interface ButtonProps {
disabled?: boolean;
iconStart?: IconNames;
iconEnd?: IconNames;
style?: React.CSSProperties;
}
@@ -18,7 +18,7 @@
.table-header {
tr {
/* border-bottom: 1px solid var(--canon-outline); */
/* border-bottom: 1px solid var(--canon-border); */
}
}