Skip to content

Commit

Permalink
tabs
Browse files Browse the repository at this point in the history
  • Loading branch information
lerte committed Nov 16, 2024
1 parent 0c5503c commit 14b1903
Show file tree
Hide file tree
Showing 5 changed files with 100 additions and 20 deletions.
14 changes: 14 additions & 0 deletions apps/docs/content/components/tabs.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,17 @@ description: Tabs organize content across different screens and views
## Usage

<usage></usage>

## Props

| Props | Type | Description | Default |
| -------------------- | ----------------------- | --------------------------- | ------------ |
| `orientation` | `horizontal` `vertical` | The orientation of the tabs | `horizontal` |
| `keyboardActivation` | `automatic` `manual` | keyboard activation | `automatic` |
| `defaultSelectedKey` | `string` \| `number` | default selected key | `undefined` |

## Events

| Events | Description |
| ------------------- | --------------------------- |
| `onSelectionChange` | fired when selected changed |
20 changes: 15 additions & 5 deletions apps/docs/src/usages/tabs.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
import { TabItem, Tabs } from 'actify'

import Divider from './divider'

export default () => {
return (
<Tabs>
<TabItem title="Actify">actify</TabItem>
<TabItem title="Ngroker">ngroker</TabItem>
<TabItem title="Taildoor">taildoor</TabItem>
</Tabs>
<>
<Tabs>
<TabItem title="Actify">actify</TabItem>
<TabItem title="Ngroker">ngroker</TabItem>
<TabItem title="Taildoor">taildoor</TabItem>
</Tabs>
<Divider />
<Tabs orientation="vertical">
<TabItem title="Actify">actify</TabItem>
<TabItem title="Ngroker">ngroker</TabItem>
<TabItem title="Taildoor">taildoor</TabItem>
</Tabs>
</>
)
}
8 changes: 7 additions & 1 deletion packages/actify/src/components/Tabs/TabPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { AriaTabPanelProps, useTabPanel } from 'react-aria'

import React from 'react'
import { TabListState } from 'react-stately'
import styles from './tabs.module.css'

interface TabPanelProps<T> extends AriaTabPanelProps {
state: TabListState<T>
Expand All @@ -11,7 +12,12 @@ const TabPanel = <T extends object>({ state, ...props }: TabPanelProps<T>) => {
const { tabPanelProps } = useTabPanel(props, state, ref)

return (
<div role="tabpanel" {...tabPanelProps} ref={ref}>
<div
ref={ref}
role="tabpanel"
{...tabPanelProps}
className={styles['tabpanel']}
>
{state.selectedItem?.props.children}
</div>
)
Expand Down
52 changes: 41 additions & 11 deletions packages/actify/src/components/Tabs/Tabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,50 +9,80 @@ import {
import React, { useEffect, useState } from 'react'
import { TabListProps, useTabListState } from 'react-stately'

import { StyleProps } from '../../utils'
import { Tab } from './Tab'
import { TabPanel } from './TabPanel'
import clsx from 'clsx'
import styles from './tabs.module.css'

interface TabsProps<T> extends AriaTabListProps<T>, TabListProps<T> {
style?: React.CSSProperties
className?: string
}
interface TabsProps<T>
extends AriaTabListProps<T>,
TabListProps<T>,
StyleProps {}

const Tabs = <T extends object>(props: TabsProps<T>) => {
const state = useTabListState(props)
const ref = React.useRef<HTMLDivElement>(null)
const { tabListProps } = useTabList(props, state, ref)

const [activeTabStyle, setActiveTabStyle] = useState({
const { orientation = 'horizontal' } = props

const [activeTabStyle, setActiveTabStyle] = useState<{
width?: number
height?: number
transform: string
}>({
width: 0,
height: 0,
transform: 'translateX(0)'
})

useEffect(() => {
const activeTab = ref?.current?.querySelector(
'[role="tab"][aria-selected="true"]'
) as HTMLDivElement

setActiveTabStyle({
width: activeTab?.offsetWidth,
transform: `translateX(${activeTab?.offsetLeft}px)`
...(orientation === 'vertical'
? {
height: activeTab?.offsetHeight,
transform: `translateY(${activeTab?.offsetTop}px)`
}
: {
width: activeTab?.offsetWidth,
transform: `translateX(${activeTab?.offsetLeft}px)`
})
})
}, [state.selectedKey])
}, [state.selectedKey, orientation])

const { focusProps, isFocusVisible } = useFocusRing({
within: true
})

return (
<nav style={props.style} className={clsx(styles['tabs'], props.className)}>
<nav
style={props.style}
className={clsx(
styles['tabs'],
orientation === 'vertical' && styles['tabs-vertical'],
props.className
)}
>
<div className={styles['tab']}>
<div
style={{ ...activeTabStyle }}
className={clsx(styles['tab-selection'], isFocusVisible && 'focused')}
className={clsx(
styles['tab-selection'],
styles[`tab-selection-${orientation}`],
isFocusVisible && 'focused'
)}
/>
<div
ref={ref}
className={styles['tab-list']}
className={clsx(
styles['tab-list'],
orientation === 'vertical' && styles['tab-list-vertical']
)}
{...mergeProps(tabListProps, focusProps)}
>
{[...state.collection].map((item) => (
Expand Down
26 changes: 23 additions & 3 deletions packages/actify/src/components/Tabs/tabs.module.css
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
.tabs {
padding: 8px;
--_active-indicator-width: var(
--md-secondary-tab-active-indicator-width,
2px
);
--_active-indicator-height: var(
--md-secondary-tab-active-indicator-height,
2px
Expand All @@ -9,25 +13,41 @@
var(--md-sys-color-primary, #6750a4)
);
}
.tabs-vertical {
display: flex;
}
.tab {
position: relative;
}
.tabpanel {
flex: 1;
}
.tab-selection {
position: absolute;
left: 0;
bottom: 2px;
height: var(--_active-indicator-height);
will-change: transform, width;
transition:
transform 150ms,
width 100ms;
background: rgb(var(--_active-indicator-color));
}
.tab-selection-horizontal {
left: 0;
bottom: 2px;
height: var(--_active-indicator-height);
}
.tab-selection-vertical {
top: 0;
right: 2px;
width: var(--_active-indicator-width);
}
.tab-list {
display: flex;
gap: 8px;
align-items: center;
}
.tab-list-vertical {
flex-direction: column;
}
.tab-item {
padding: 10px 12px;
position: relative;
Expand Down

0 comments on commit 14b1903

Please sign in to comment.