Add new Select theme in the toolbar

Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
This commit is contained in:
Charles de Dreuille
2025-01-11 11:10:19 +00:00
parent f0f89c1baa
commit 305bab61ee
9 changed files with 231 additions and 101 deletions
+4 -4
View File
@@ -1,5 +1,5 @@
import Link from 'next/link';
import { TabsVersion, TabsTheme } from './tabs';
import { ThemeSelector } from './theme';
import { ThemeNameSelector } from './theme-name';
import styles from './styles.module.css';
import { Nav } from './nav';
@@ -10,8 +10,8 @@ export const Toolbar = () => {
<Nav />
</div>
<div className={styles.actions}>
<TabsVersion />
<TabsTheme />
<ThemeNameSelector />
<ThemeSelector />
</div>
</div>
);
@@ -13,39 +13,26 @@
display: flex;
position: relative;
z-index: 0;
gap: 1.5rem;
gap: var(--canon-spacing-lg);
}
.tab {
all: unset;
height: 60px;
display: flex;
align-items: center;
justify-content: center;
border: 0;
margin: 0;
outline: 0;
background: none;
appearance: none;
color: var(--canon-text-secondary) !important;
font-family: inherit;
font-size: 0.75rem;
line-height: 1.25rem;
font-weight: 500;
user-select: none;
flex: 1;
color: var(--canon-text-secondary);
font-size: var(--canon-font-size-body);
font-weight: var(--canon-font-weight-bold);
cursor: pointer;
padding: 0;
transition: color 0.2s ease-in-out;
&[data-selected] {
color: var(--canon-text-primary) !important;
& p {
color: var(--canon-text-primary) !important;
}
&:hover {
color: var(--canon-text-primary);
}
@media (hover: hover) {
&:hover {
&[data-selected] {
color: var(--canon-text-primary);
& p {
color: var(--canon-text-primary);
}
}
+2 -7
View File
@@ -1,7 +1,6 @@
'use client';
import { Tabs } from '@base-ui-components/react/tabs';
import { Text } from '../../../../packages/canon';
import { usePathname } from 'next/navigation';
import { useRouter } from 'next/navigation';
import styles from './nav.module.css';
@@ -32,9 +31,7 @@ export const Nav = () => {
router.push('/');
}}
>
<Text variant="caption" weight="bold">
Documentation
</Text>
Documentation
</Tabs.Tab>
<Tabs.Tab
className={styles.tab}
@@ -43,9 +40,7 @@ export const Nav = () => {
router.push('/playground');
}}
>
<Text variant="caption" weight="bold">
Playground
</Text>
Playground
</Tabs.Tab>
<Tabs.Indicator className={styles.indicator} />
</Tabs.List>
@@ -3,7 +3,7 @@
top: 0;
left: 0;
right: 0;
z-index: 1000;
z-index: 10;
background-color: var(--canon-surface-1);
border-bottom: 1px solid var(--canon-border-base);
height: 60px;
@@ -17,5 +17,5 @@
.actions {
display: flex;
align-items: center;
gap: var(--canon-spacing-md);
gap: var(--canon-spacing-sm);
}
@@ -1,53 +0,0 @@
'use client';
import { Tabs } from '@base-ui-components/react/tabs';
import { Icon, Text } from '../../../../packages/canon';
import { usePlayground } from '@/utils/playground-context';
import styles from './tabs.module.css';
export const TabsVersion = () => {
const { selectedThemeName, setSelectedThemeName } = usePlayground();
return (
<Tabs.Root
className={styles.tabs}
onValueChange={setSelectedThemeName}
value={selectedThemeName}
>
<Tabs.List className={styles.list}>
<Tabs.Tab className={styles.tab} value="legacy">
<Text variant="caption" weight="bold">
Theme 1
</Text>
</Tabs.Tab>
<Tabs.Tab className={styles.tab} value="default">
<Text variant="caption" weight="bold">
Theme 2
</Text>
</Tabs.Tab>
<Tabs.Indicator className={styles.indicator} />
</Tabs.List>
</Tabs.Root>
);
};
export const TabsTheme = () => {
const { selectedTheme, setSelectedTheme } = usePlayground();
return (
<Tabs.Root
className={styles.tabsTheme}
onValueChange={setSelectedTheme}
value={selectedTheme}
>
<Tabs.List className={styles.list}>
<Tabs.Tab className={styles.tab} value="light">
<Icon name="sun" />
</Tabs.Tab>
<Tabs.Tab className={styles.tab} value="dark">
<Icon name="moon" />
</Tabs.Tab>
<Tabs.Indicator className={styles.indicator} />
</Tabs.List>
</Tabs.Root>
);
};
@@ -0,0 +1,129 @@
.Select {
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.75rem;
height: 2.5rem;
padding-left: 0.875rem;
padding-right: 0.75rem;
margin: 0;
outline: 0;
border: 1px solid var(--color-gray-200);
border-radius: 0.375rem;
font-family: inherit;
font-size: 1rem;
line-height: 1.5rem;
color: var(--color-gray-900);
cursor: pointer;
user-select: none;
background-color: var(--canon-surface-2);
transition: background-color 0.2s ease-in-out;
&:focus-visible {
outline: 2px solid var(--color-blue);
outline-offset: -1px;
}
}
.SelectIcon {
display: flex;
}
.Popup {
box-sizing: border-box;
padding-block: 0.25rem;
border-radius: 0.375rem;
background-color: canvas;
color: var(--color-gray-900);
border: 1px solid var(--canon-border-base);
transform-origin: var(--transform-origin);
transition: transform 150ms, opacity 150ms;
&[data-starting-style],
&[data-ending-style] {
opacity: 0;
transform: scale(0.9);
}
&[data-side='none'] {
transition: none;
transform: none;
opacity: 1;
}
@media (prefers-color-scheme: light) {
outline: 1px solid var(--color-gray-200);
box-shadow: 0px 10px 15px -3px var(--color-gray-200),
0px 4px 6px -4px var(--color-gray-200);
}
@media (prefers-color-scheme: dark) {
outline: 1px solid var(--color-gray-300);
outline-offset: -1px;
}
}
.Item {
box-sizing: border-box;
outline: 0;
font-size: 0.875rem;
line-height: 1rem;
padding-block: 0.5rem;
padding-left: 0.625rem;
padding-right: 1rem;
font-size: var(--canon-font-size-body);
font-weight: var(--canon-font-weight-bold);
display: grid;
gap: 0.5rem;
align-items: center;
grid-template-columns: 0.75rem 1fr;
cursor: default;
user-select: none;
[data-side='none'] & {
font-size: 1rem;
padding-right: 3rem;
min-width: calc(var(--anchor-width) + 1rem);
}
&[data-highlighted] {
z-index: 0;
position: relative;
color: var(--color-gray-50);
}
&[data-highlighted]::before {
content: '';
z-index: -1;
position: absolute;
inset-block: 0;
inset-inline: 0.25rem;
border-radius: 0.25rem;
background-color: var(--color-gray-900);
}
}
.ItemIndicator {
grid-column-start: 1;
}
.ItemIndicatorIcon {
display: block;
width: 0.75rem;
height: 0.75rem;
}
.ItemText {
grid-column-start: 2;
}
.SelectValue {
font-size: var(--canon-font-size-body);
font-weight: var(--canon-font-weight-bold);
}
.Positioner {
z-index: 20;
}
@@ -0,0 +1,48 @@
'use client';
import { Select } from '@base-ui-components/react/select';
import styles from './theme-name.module.css';
import { Icon } from '@backstage/canon';
import { usePlayground } from '@/utils/playground-context';
const themes = [
{ name: 'Backstage Legacy', value: 'legacy' },
{ name: 'Backstage Default', value: 'default' },
];
export const ThemeNameSelector = () => {
const { selectedThemeName, setSelectedThemeName } = usePlayground();
return (
<Select.Root
value={selectedThemeName || 'default'}
onValueChange={setSelectedThemeName}
>
<Select.Trigger className={styles.Select}>
<Select.Value
className={styles.SelectValue}
placeholder="Select a theme"
/>
<Select.Icon className={styles.SelectIcon}>
<Icon name="chevronDown" />
</Select.Icon>
</Select.Trigger>
<Select.Portal>
<Select.Positioner className={styles.Positioner} sideOffset={8}>
<Select.Popup className={styles.Popup}>
{themes.map(({ name, value }) => (
<Select.Item className={styles.Item} value={value}>
<Select.ItemIndicator className={styles.ItemIndicator}>
<Icon name="check" />
</Select.ItemIndicator>
<Select.ItemText className={styles.ItemText}>
{name}
</Select.ItemText>
</Select.Item>
))}
</Select.Popup>
</Select.Positioner>
</Select.Portal>
</Select.Root>
);
};
@@ -6,7 +6,7 @@
}
.tabsTheme {
width: 142px;
width: 120px;
border-radius: 0.375rem;
background-color: var(--canon-surface-2);
transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out;
@@ -16,7 +16,7 @@
display: flex;
position: relative;
z-index: 0;
padding-inline: 0.25rem;
padding-inline: 0.375rem;
gap: 0.25rem;
}
@@ -29,21 +29,17 @@
outline: 0;
background: none;
appearance: none;
color: var(--canon-text-secondary) !important;
font-family: inherit;
font-size: 0.75rem;
line-height: 1.25rem;
font-weight: 500;
color: var(--canon-text-secondary);
user-select: none;
height: 2.25rem;
height: 40px;
flex: 1;
cursor: pointer;
&[data-selected] {
color: var(--canon-text-primary) !important;
color: var(--canon-text-primary);
& p {
color: var(--canon-text-primary) !important;
color: var(--canon-text-primary);
}
}
@@ -0,0 +1,28 @@
'use client';
import { Tabs } from '@base-ui-components/react/tabs';
import { Icon } from '@backstage/canon';
import { usePlayground } from '@/utils/playground-context';
import styles from './theme.module.css';
export const ThemeSelector = () => {
const { selectedTheme, setSelectedTheme } = usePlayground();
return (
<Tabs.Root
className={styles.tabsTheme}
onValueChange={setSelectedTheme}
value={selectedTheme}
>
<Tabs.List className={styles.list}>
<Tabs.Tab className={styles.tab} value="light">
<Icon name="sun" />
</Tabs.Tab>
<Tabs.Tab className={styles.tab} value="dark">
<Icon name="moon" />
</Tabs.Tab>
<Tabs.Indicator className={styles.indicator} />
</Tabs.List>
</Tabs.Root>
);
};