Add new Tabs component
Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/canon': patch
|
||||
---
|
||||
|
||||
Add new Tabs component to Canon
|
||||
@@ -349,6 +349,83 @@
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.canon-TabsRoot {
|
||||
border: 1px solid var(--color-gray-200);
|
||||
border-radius: .375rem;
|
||||
}
|
||||
|
||||
.canon-TabsList {
|
||||
z-index: 0;
|
||||
display: flex;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.canon-TabsTab {
|
||||
appearance: none;
|
||||
color: var(--canon-fg-secondary);
|
||||
user-select: none;
|
||||
padding-inline: var(--canon-space-3);
|
||||
height: 2rem;
|
||||
font-family: inherit;
|
||||
font-size: .875rem;
|
||||
font-weight: 500;
|
||||
line-height: 1.25rem;
|
||||
font-size: var(--canon-font-size-2);
|
||||
cursor: pointer;
|
||||
background: none;
|
||||
border: 0;
|
||||
outline: 0;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin: 0;
|
||||
padding-block: 0;
|
||||
transition: color .2s ease-in-out;
|
||||
display: flex;
|
||||
|
||||
&[data-selected] {
|
||||
color: var(--canon-fg-primary);
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
&:hover {
|
||||
color: var(--canon-fg-primary);
|
||||
}
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
position: relative;
|
||||
|
||||
&:before {
|
||||
content: "";
|
||||
outline: 1px solid var(--canon-ring);
|
||||
outline-offset: -1px;
|
||||
border-radius: .25rem;
|
||||
position: absolute;
|
||||
inset: .25rem 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.canon-TabsIndicator {
|
||||
z-index: -1;
|
||||
translate: var(--active-tab-left) -50%;
|
||||
width: var(--active-tab-width);
|
||||
background-color: var(--canon-bg-solid);
|
||||
height: 1px;
|
||||
transition-property: translate, width;
|
||||
transition-duration: .2s;
|
||||
transition-timing-function: ease-in-out;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.canon-TabsPanel {
|
||||
&[hidden] {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.canon-Text {
|
||||
font-family: var(--canon-font-regular);
|
||||
margin: 0;
|
||||
|
||||
@@ -9573,6 +9573,83 @@
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.canon-TabsRoot {
|
||||
border: 1px solid var(--color-gray-200);
|
||||
border-radius: .375rem;
|
||||
}
|
||||
|
||||
.canon-TabsList {
|
||||
z-index: 0;
|
||||
display: flex;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.canon-TabsTab {
|
||||
appearance: none;
|
||||
color: var(--canon-fg-secondary);
|
||||
user-select: none;
|
||||
padding-inline: var(--canon-space-3);
|
||||
height: 2rem;
|
||||
font-family: inherit;
|
||||
font-size: .875rem;
|
||||
font-weight: 500;
|
||||
line-height: 1.25rem;
|
||||
font-size: var(--canon-font-size-2);
|
||||
cursor: pointer;
|
||||
background: none;
|
||||
border: 0;
|
||||
outline: 0;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin: 0;
|
||||
padding-block: 0;
|
||||
transition: color .2s ease-in-out;
|
||||
display: flex;
|
||||
|
||||
&[data-selected] {
|
||||
color: var(--canon-fg-primary);
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
&:hover {
|
||||
color: var(--canon-fg-primary);
|
||||
}
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
position: relative;
|
||||
|
||||
&:before {
|
||||
content: "";
|
||||
outline: 1px solid var(--canon-ring);
|
||||
outline-offset: -1px;
|
||||
border-radius: .25rem;
|
||||
position: absolute;
|
||||
inset: .25rem 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.canon-TabsIndicator {
|
||||
z-index: -1;
|
||||
translate: var(--active-tab-left) -50%;
|
||||
width: var(--active-tab-width);
|
||||
background-color: var(--canon-bg-solid);
|
||||
height: 1px;
|
||||
transition-property: translate, width;
|
||||
transition-duration: .2s;
|
||||
transition-timing-function: ease-in-out;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.canon-TabsPanel {
|
||||
&[hidden] {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.canon-Text {
|
||||
font-family: var(--canon-font-regular);
|
||||
margin: 0;
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
.canon-TabsRoot {
|
||||
border: 1px solid var(--color-gray-200);
|
||||
border-radius: .375rem;
|
||||
}
|
||||
|
||||
.canon-TabsList {
|
||||
z-index: 0;
|
||||
display: flex;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.canon-TabsTab {
|
||||
appearance: none;
|
||||
color: var(--canon-fg-secondary);
|
||||
user-select: none;
|
||||
padding-inline: var(--canon-space-3);
|
||||
height: 2rem;
|
||||
font-family: inherit;
|
||||
font-size: .875rem;
|
||||
font-weight: 500;
|
||||
line-height: 1.25rem;
|
||||
font-size: var(--canon-font-size-2);
|
||||
cursor: pointer;
|
||||
background: none;
|
||||
border: 0;
|
||||
outline: 0;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin: 0;
|
||||
padding-block: 0;
|
||||
transition: color .2s ease-in-out;
|
||||
display: flex;
|
||||
|
||||
&[data-selected] {
|
||||
color: var(--canon-fg-primary);
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
&:hover {
|
||||
color: var(--canon-fg-primary);
|
||||
}
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
position: relative;
|
||||
|
||||
&:before {
|
||||
content: "";
|
||||
outline: 1px solid var(--canon-ring);
|
||||
outline-offset: -1px;
|
||||
border-radius: .25rem;
|
||||
position: absolute;
|
||||
inset: .25rem 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.canon-TabsIndicator {
|
||||
z-index: -1;
|
||||
translate: var(--active-tab-left) -50%;
|
||||
width: var(--active-tab-width);
|
||||
background-color: var(--canon-bg-solid);
|
||||
height: 1px;
|
||||
transition-property: translate, width;
|
||||
transition-duration: .2s;
|
||||
transition-timing-function: ease-in-out;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.canon-TabsPanel {
|
||||
&[hidden] {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ import { RefAttributes } from 'react';
|
||||
import type { RemixiconComponentType } from '@remixicon/react';
|
||||
import { ScrollArea as ScrollArea_2 } from '@base-ui-components/react/scroll-area';
|
||||
import { Table as Table_2 } from '@tanstack/react-table';
|
||||
import { Tabs as Tabs_2 } from '@base-ui-components/react/tabs';
|
||||
import { TdHTMLAttributes } from 'react';
|
||||
import { ThHTMLAttributes } from 'react';
|
||||
import { Tooltip as Tooltip_2 } from '@base-ui-components/react/tooltip';
|
||||
@@ -1231,6 +1232,26 @@ export interface TableCellTextProps
|
||||
title: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export const Tabs: {
|
||||
Root: ForwardRefExoticComponent<
|
||||
Omit<Tabs_2.Root.Props & RefAttributes<HTMLDivElement>, 'ref'> &
|
||||
RefAttributes<HTMLDivElement>
|
||||
>;
|
||||
List: ForwardRefExoticComponent<
|
||||
Omit<Tabs_2.List.Props & RefAttributes<HTMLDivElement>, 'ref'> &
|
||||
RefAttributes<HTMLDivElement>
|
||||
>;
|
||||
Tab: ForwardRefExoticComponent<
|
||||
Omit<Tabs_2.Tab.Props & RefAttributes<Element>, 'ref'> &
|
||||
RefAttributes<Element>
|
||||
>;
|
||||
Panel: ForwardRefExoticComponent<
|
||||
Omit<Tabs_2.Panel.Props & RefAttributes<HTMLDivElement>, 'ref'> &
|
||||
RefAttributes<HTMLDivElement>
|
||||
>;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
const Text_2: ForwardRefExoticComponent<
|
||||
TextProps & RefAttributes<HTMLParagraphElement>
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { Tabs } from './Tabs';
|
||||
import { Text } from '../../index';
|
||||
|
||||
const meta = {
|
||||
title: 'Components/Tabs',
|
||||
component: Tabs.Root,
|
||||
} satisfies Meta<typeof Tabs.Root>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
children: (
|
||||
<Tabs.Root>
|
||||
<Tabs.List>
|
||||
<Tabs.Tab>Tab 1</Tabs.Tab>
|
||||
<Tabs.Tab>Tab 2</Tabs.Tab>
|
||||
<Tabs.Tab>Tab 3 With long title</Tabs.Tab>
|
||||
</Tabs.List>
|
||||
<Tabs.Panel style={{ padding: 'var(--canon-space-3' }}>
|
||||
<Text>Content for Tab 1</Text>
|
||||
</Tabs.Panel>
|
||||
<Tabs.Panel style={{ padding: 'var(--canon-space-3' }}>
|
||||
<Text>Content for Tab 2</Text>
|
||||
</Tabs.Panel>
|
||||
<Tabs.Panel style={{ padding: 'var(--canon-space-3' }}>
|
||||
<Text>Content for Tab 3</Text>
|
||||
</Tabs.Panel>
|
||||
</Tabs.Root>
|
||||
),
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,76 @@
|
||||
.canon-TabsRoot {
|
||||
border: 1px solid var(--color-gray-200);
|
||||
border-radius: 0.375rem;
|
||||
}
|
||||
|
||||
.canon-TabsList {
|
||||
display: flex;
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.canon-TabsTab {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 0;
|
||||
margin: 0;
|
||||
outline: 0;
|
||||
background: none;
|
||||
appearance: none;
|
||||
color: var(--canon-fg-secondary);
|
||||
font-family: inherit;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.25rem;
|
||||
font-weight: 500;
|
||||
user-select: none;
|
||||
padding-inline: var(--canon-space-3);
|
||||
padding-block: 0;
|
||||
height: 2rem;
|
||||
font-size: var(--canon-font-size-2);
|
||||
transition: color 200ms ease-in-out;
|
||||
cursor: pointer;
|
||||
|
||||
&[data-selected] {
|
||||
color: var(--canon-fg-primary);
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
&:hover {
|
||||
color: var(--canon-fg-primary);
|
||||
}
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0.25rem 0;
|
||||
border-radius: 0.25rem;
|
||||
outline: 1px solid var(--canon-ring);
|
||||
outline-offset: -1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.canon-TabsIndicator {
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
translate: var(--active-tab-left) -50%;
|
||||
width: var(--active-tab-width);
|
||||
height: 1px;
|
||||
background-color: var(--canon-bg-solid);
|
||||
transition-property: translate, width;
|
||||
transition-duration: 200ms;
|
||||
transition-timing-function: ease-in-out;
|
||||
}
|
||||
|
||||
.canon-TabsPanel {
|
||||
&[hidden] {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { forwardRef } from 'react';
|
||||
import { Tabs as TabsPrimitive } from '@base-ui-components/react/tabs';
|
||||
import clsx from 'clsx';
|
||||
|
||||
const TabsRoot = forwardRef<
|
||||
React.ElementRef<typeof TabsPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Root>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<TabsPrimitive.Root
|
||||
ref={ref}
|
||||
className={clsx('canon-TabsRoot', className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
TabsRoot.displayName = TabsPrimitive.Root.displayName;
|
||||
|
||||
const TabsList = forwardRef<
|
||||
React.ElementRef<typeof TabsPrimitive.List>,
|
||||
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<TabsPrimitive.List
|
||||
ref={ref}
|
||||
className={clsx('canon-TabsList', className)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<TabsPrimitive.Indicator className="canon-TabsIndicator" />
|
||||
</TabsPrimitive.List>
|
||||
));
|
||||
TabsList.displayName = TabsPrimitive.List.displayName;
|
||||
|
||||
const TabsTab = forwardRef<
|
||||
React.ElementRef<typeof TabsPrimitive.Tab>,
|
||||
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Tab>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<TabsPrimitive.Tab
|
||||
ref={ref}
|
||||
className={clsx('canon-TabsTab', className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
TabsTab.displayName = TabsPrimitive.Tab.displayName;
|
||||
|
||||
const TabsPanel = forwardRef<
|
||||
React.ElementRef<typeof TabsPrimitive.Panel>,
|
||||
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Panel>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<TabsPrimitive.Panel
|
||||
ref={ref}
|
||||
className={clsx('canon-TabsPanel', className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
TabsPanel.displayName = TabsPrimitive.Panel.displayName;
|
||||
|
||||
/** @public */
|
||||
export const Tabs = {
|
||||
Root: TabsRoot,
|
||||
List: TabsList,
|
||||
Tab: TabsTab,
|
||||
Panel: TabsPanel,
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
export { Tabs } from './Tabs';
|
||||
@@ -30,6 +30,7 @@
|
||||
@import '../components/Table/TableCellText/TableCellText.styles.css';
|
||||
@import '../components/Table/TableCellLink/TableCellLink.styles.css';
|
||||
@import '../components/Table/TableCellProfile/TableCellProfile.styles.css';
|
||||
@import '../components/Tabs/Tabs.styles.css';
|
||||
@import '../components/Text/styles.css';
|
||||
@import '../components/Heading/styles.css';
|
||||
@import '../components/IconButton/styles.css';
|
||||
|
||||
@@ -40,6 +40,7 @@ export * from './components/Icon';
|
||||
export * from './components/IconButton';
|
||||
export * from './components/Checkbox';
|
||||
export * from './components/Table';
|
||||
export * from './components/Tabs';
|
||||
export * from './components/TextField';
|
||||
export * from './components/Tooltip';
|
||||
export * from './components/Menu';
|
||||
|
||||
Reference in New Issue
Block a user