Merge branch 'develop' of https://github.com/shrimpia/misskey-tools into develop
This commit is contained in:
commit
ba60b42ac7
@ -1,12 +1,12 @@
|
||||
import React from 'react';
|
||||
import { useState } from 'react';
|
||||
import { useLayoutEffect } from 'react';
|
||||
import React, { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
|
||||
import { styled } from '@/libs/stitches';
|
||||
|
||||
export type TabProp = {
|
||||
children: React.ReactElement<TabItem> | React.ReactElement<TabItem>[]
|
||||
value?: string;
|
||||
direction?: 'vertical' | 'horizontal';
|
||||
onChange?: (newValue: string) => void;
|
||||
};
|
||||
|
||||
@ -31,29 +31,115 @@ const Ul = styled('ul', {
|
||||
display: 'flex',
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
|
||||
defaultVariants: {
|
||||
direction: 'horizontal',
|
||||
},
|
||||
|
||||
variants: {
|
||||
direction: {
|
||||
horizontal: {
|
||||
flexDirection: 'row',
|
||||
},
|
||||
vertical: {
|
||||
flexDirection: 'column',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const Li = styled('li', {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
padding: '$m',
|
||||
padding: '$s $l',
|
||||
margin: 0,
|
||||
borderBottom: '2px solid $muted',
|
||||
color: '$muted',
|
||||
|
||||
defaultVariants: {
|
||||
direction: 'horizontal',
|
||||
},
|
||||
|
||||
variants: {
|
||||
active: {
|
||||
true: {
|
||||
color: '$primary',
|
||||
},
|
||||
},
|
||||
|
||||
direction: {
|
||||
horizontal: {
|
||||
borderBottom: '2px solid $divider',
|
||||
},
|
||||
vertical: {
|
||||
borderLeft: '2px solid $divider',
|
||||
justifyContent: 'flex-start',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const Indicator = styled('div', {
|
||||
position: 'absolute',
|
||||
background: '$primary',
|
||||
transition: 'all 0.2s $timingFunction$default',
|
||||
|
||||
defaultVariants: {
|
||||
direction: 'horizontal',
|
||||
},
|
||||
|
||||
variants: {
|
||||
direction: {
|
||||
horizontal: {
|
||||
bottom: 0,
|
||||
height: 2,
|
||||
},
|
||||
vertical: {
|
||||
left: 0,
|
||||
width: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const Tab: React.FC<TabProp> = (p) => {
|
||||
const c = Array.isArray(p.children) ? p.children.map(c => c.props) : [p.children.props];
|
||||
const [value, setValue] = useState(c[0].value);
|
||||
const items = useMemo(() => Array.isArray(p.children) ? p.children.map(c => c.props) : [p.children.props], [p.children]);
|
||||
|
||||
const [value, setValue] = useState<string | null>(null);
|
||||
|
||||
const itemRefs = useRef<(HTMLLIElement | null)[]>([]);
|
||||
|
||||
const position = useMemo(() => {
|
||||
let sum = 0;
|
||||
const max = items.findIndex(item => item.value === value);
|
||||
for (let i = 0; i < max; i++) {
|
||||
const ref = itemRefs.current[i];
|
||||
if (ref == null) return 0;
|
||||
sum += p.direction === 'vertical' ? ref.clientHeight : ref.clientWidth;
|
||||
}
|
||||
console.log(`left: ${sum}`);
|
||||
return sum;
|
||||
}, [items, p.direction, value]);
|
||||
|
||||
const size = useMemo(() => {
|
||||
const i = items.findIndex(item => item.value === value);
|
||||
const ref = itemRefs.current[i];
|
||||
if (ref == null) return 0;
|
||||
return p.direction === 'vertical' ? ref.clientHeight : ref.clientWidth;
|
||||
}, [items, p.direction, value]);
|
||||
|
||||
const indicatorStyle = useMemo(() => (p.direction === 'vertical' ? {
|
||||
top: position, height: size,
|
||||
}: {
|
||||
left: position, width: size,
|
||||
}), [p.direction, position, size]);
|
||||
|
||||
useEffect(() => {
|
||||
itemRefs.current = itemRefs.current.slice(0, items.length);
|
||||
if (value === null) {
|
||||
setValue(items[0].value);
|
||||
}
|
||||
}, [items, value]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (!p.value) return;
|
||||
@ -62,9 +148,9 @@ export const Tab: React.FC<TabProp> = (p) => {
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Ul>
|
||||
{c.map(item => (
|
||||
<Li key={item.value} role="button" active={item.value === value} onClick={() => {
|
||||
<Ul direction={p.direction}>
|
||||
{items.map((item, i) => (
|
||||
<Li direction={p.direction} key={item.value} ref={el => itemRefs.current[i] = el} role="button" active={item.value === value} onClick={() => {
|
||||
if (item.value === value) return;
|
||||
console.log(item.value);
|
||||
setValue(item.value);
|
||||
@ -74,6 +160,7 @@ export const Tab: React.FC<TabProp> = (p) => {
|
||||
</Li>
|
||||
))}
|
||||
</Ul>
|
||||
<Indicator aria-hidden direction={p.direction} css={indicatorStyle} />
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
|
||||
import type { Meta } from '@storybook/react';
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { Tab, TabItem } from '@/components/primitives/Tab';
|
||||
|
||||
@ -9,14 +9,30 @@ const meta = {
|
||||
parameters: {
|
||||
layouts: 'centered',
|
||||
},
|
||||
args: {
|
||||
children: ([
|
||||
<TabItem key="home" value="home">ホーム</TabItem>,
|
||||
<TabItem key="notification" value="notification">通知</TabItem>,
|
||||
<TabItem key="explore" value="explore">みつける</TabItem>,
|
||||
]),
|
||||
},
|
||||
} satisfies Meta<typeof Tab>;
|
||||
|
||||
export default meta;
|
||||
|
||||
export const Default = () => (
|
||||
<Tab>
|
||||
<TabItem value="home">ホーム</TabItem>
|
||||
<TabItem value="notification">通知</TabItem>
|
||||
<TabItem value="explore">みつける</TabItem>
|
||||
</Tab>
|
||||
);
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {};
|
||||
|
||||
export const Horizontal: Story = {
|
||||
args: {
|
||||
direction: 'horizontal',
|
||||
},
|
||||
};
|
||||
|
||||
export const Vertical: Story = {
|
||||
args: {
|
||||
direction: 'vertical',
|
||||
},
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user