Skip to content

Commit

Permalink
feat(tabs): started tabs compound component
Browse files Browse the repository at this point in the history
  • Loading branch information
Powerplex authored and soykje committed May 5, 2023
1 parent 7a03798 commit 28c811d
Show file tree
Hide file tree
Showing 15 changed files with 330 additions and 0 deletions.
53 changes: 53 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions packages/components/tabs/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
src
**/*.stories.*
17 changes: 17 additions & 0 deletions packages/components/tabs/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "@spark-ui/tabs",
"version": "1.0.0",
"description": "A set of layered sections of content—known as tab panels—that are displayed one at a time.",
"publishConfig": {
"access": "public"
},
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"scripts": {
"build": "vite build"
},
"dependencies": {
"@radix-ui/react-tabs": "1"
}
}
33 changes: 33 additions & 0 deletions packages/components/tabs/src/Tabs.doc.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { ArgsTable, Meta, Story, Canvas } from '@storybook/addon-docs'

import { Tabs } from '.'

import * as stories from './Tabs.stories'

<Meta of={stories} />

# Tabs

A set of layered sections of content—known as tab panels—that are displayed one at a time.

## Install

```
npm install @spark-ui/tabs
```

## Import

```
import { Tabs } from "@spark-ui/tabs"
```

## Props

<ArgsTable of={Tabs} />

## Variants

<Canvas>
<Story of={stories.Default} />
</Canvas>
61 changes: 61 additions & 0 deletions packages/components/tabs/src/Tabs.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { Meta, StoryFn } from '@storybook/react'

import { Tabs } from '.'

const meta: Meta<typeof Tabs> = {
title: 'Components/Tabs',
component: Tabs,
subcomponents: {
'Tabs.List': Tabs.List,
'Tabs.Trigger': Tabs.Trigger,
'Tabs.Content': Tabs.Content,
},
}

export default meta

interface Tab {
title: string
value: string
}

const tabs: Tab[] = [
{
title: 'Inbox',
value: 'tab1',
},
{
title: 'Today',
value: 'tab2',
},

{
title: 'Upcoming',
value: 'tab3',
},
]

export const Default: StoryFn = _args => (
<Tabs defaultValue="tab1">
<Tabs.List>
{tabs.map(({ title, value }) => (
<Tabs.Trigger key={value} value={value}>
<span>{title}</span>
</Tabs.Trigger>
))}
</Tabs.List>
{tabs.map(({ value }) => (
<Tabs.Content key={value} value={value}>
<span>
{
{
tab1: 'Your inbox is empty',
tab2: 'Make some coffee',
tab3: 'Order more coffee',
}[value]
}
</span>
</Tabs.Content>
))}
</Tabs>
)
Empty file.
27 changes: 27 additions & 0 deletions packages/components/tabs/src/Tabs.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { describe, expect, it, vi } from 'vitest'

import { Tabs } from './Tabs'

describe('Tabs', () => {
it('should render', () => {
render(<Tabs>Hello World!</Tabs>)

expect(screen.getByText('Hello World!')).toBeInTheDocument()
})

it('should trigger click event', async () => {
const user = userEvent.setup()
const clickEvent = vi.fn()

// Given
render(<div onClick={clickEvent}>Hello World!</div>)

// When
await user.click(screen.getByText('Hello World!'))

// Then
expect(clickEvent).toHaveBeenCalledTimes(1)
})
})
7 changes: 7 additions & 0 deletions packages/components/tabs/src/Tabs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { type ComponentPropsWithoutRef, forwardRef, type PropsWithChildren } from 'react'

export type TabsProps = ComponentPropsWithoutRef<'div'>

export const Tabs = forwardRef<HTMLDivElement, PropsWithChildren<TabsProps>>((props, ref) => {
return <div ref={ref} {...props} />
})
18 changes: 18 additions & 0 deletions packages/components/tabs/src/TabsContent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import * as RadixTabs from '@radix-ui/react-tabs'
import { cva } from 'class-variance-authority'

export const styles = cva(['p-md', 'shadow-md', 'rounded-b-sm'])

export function TabsContent({ children, asChild = false, ...rest }: RadixTabs.TabsContentProps) {
const defaultRadixValues = {
asChild,
}

return (
<RadixTabs.Content className={styles()} {...defaultRadixValues} {...rest}>
{children}
</RadixTabs.Content>
)
}

TabsContent.displayName = TabsContent.name
24 changes: 24 additions & 0 deletions packages/components/tabs/src/TabsList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import * as RadixTabs from '@radix-ui/react-tabs'
import { cva } from 'class-variance-authority'

export const styles = cva(['flex', 'min-w-full', 'rounded-t-sm', 'shadow-md'])

export function TabsList({
children,
asChild = false,
loop = true,
...rest
}: RadixTabs.TabsListProps) {
const defaultRadixValues = {
asChild,
loop,
}

return (
<RadixTabs.List className={styles()} {...defaultRadixValues} {...rest}>
{children}
</RadixTabs.List>
)
}

TabsList.displayName = TabsList.name
27 changes: 27 additions & 0 deletions packages/components/tabs/src/TabsRoot.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import * as RadixTabs from '@radix-ui/react-tabs'
import { cva } from 'class-variance-authority'
import { PropsWithChildren } from 'react'

export const styles = cva(['flex', 'flex-col', 'px-sm', 'py-sm'])

export function TabsRoot({
children,
asChild = false,
orientation = 'horizontal',
activationMode = 'automatic',
...rest
}: PropsWithChildren<RadixTabs.TabsProps>) {
const defaultRadixValues = {
asChild,
orientation,
activationMode,
}

return (
<RadixTabs.Root className={styles()} {...defaultRadixValues} {...rest}>
{children}
</RadixTabs.Root>
)
}

TabsRoot.displayName = TabsRoot.name
41 changes: 41 additions & 0 deletions packages/components/tabs/src/TabsTrigger.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import * as RadixTabs from '@radix-ui/react-tabs'
import { cva } from 'class-variance-authority'

export const styles = cva([
'flex-1 p-sm',
// first item
'first:rounded-tl-sm first:border-l-transparent',
// last item
'last:rounded-tr-sm last:border-l-transparent last:border-r-transparent',
// borders
'border-sm',
'border-l-none',
'border-t-transparent',
'border-b-sm border-b first:border-r-sm last:border-l-sm',
'border-b-primary-variant',
// radix states
'spark-state-active:border-b-sm',
'spark-state-active:border-b-primary',
'focus-visible:spark-state-active:border-b-transparent',
'spark-state-active:bg-primary spark-state-active:text-on-primary',
'focus:z-raised focus:outline-none focus-visible:ring-2 focus-visible:ring-outline-high',
])

interface Props extends RadixTabs.TabsTriggerProps {
disabled?: boolean
}

export function TabsTrigger({ children, asChild = false, disabled = false, ...rest }: Props) {
const defaultRadixValues = {
asChild,
disabled,
}

return (
<RadixTabs.Trigger className={styles()} {...defaultRadixValues} {...rest}>
{children}
</RadixTabs.Trigger>
)
}

TabsTrigger.displayName = TabsTrigger.name
10 changes: 10 additions & 0 deletions packages/components/tabs/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { TabsContent as Content } from './TabsContent'
import { TabsList as List } from './TabsList'
import { TabsRoot as Root } from './TabsRoot'
import { TabsTrigger as Trigger } from './TabsTrigger'

export const Tabs = Object.assign(Root, {
List,
Trigger,
Content,
})
4 changes: 4 additions & 0 deletions packages/components/tabs/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"extends": "../../../tsconfig.json",
"include": ["src/**/*", "../../../global.d.ts"]
}
6 changes: 6 additions & 0 deletions packages/components/tabs/vite.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import path from 'path'
import { getComponentConfiguration } from '../../../config/index'

const { name } = require(path.resolve(__dirname, 'package.json'))

export default getComponentConfiguration(process.cwd(), name)

0 comments on commit 28c811d

Please sign in to comment.