Skip to content

Commit 2c602d0

Browse files
committed
feat: add segment preset
1 parent 611bb47 commit 2c602d0

File tree

9 files changed

+131
-0
lines changed

9 files changed

+131
-0
lines changed
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
.frame {
2+
display: inline-flex;
3+
align-items: center;
4+
justify-content: center;
5+
padding: 2px;
6+
border-radius: 4px;
7+
border: 1px solid var(--composify-palette-outline);
8+
background-color: var(--composify-palette-surface-container-low);
9+
}
10+
11+
.item {
12+
flex: 1;
13+
display: inline-flex;
14+
align-items: center;
15+
justify-content: center;
16+
height: 30px;
17+
padding: 0 8px;
18+
background-color: transparent;
19+
font-size: 12px;
20+
font-weight: 500;
21+
white-space: nowrap;
22+
border-radius: 4px;
23+
border: none;
24+
outline: none;
25+
color: var(--composify-palette-on-surface-variant);
26+
}
27+
28+
.item--active {
29+
background-color: var(--composify-palette-secondary);
30+
color: var(--composify-palette-on-secondary);
31+
}
32+
33+
.item > * {
34+
height: 16px;
35+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { compoundComponents } from '../../utils';
2+
import { SegmentFrame } from './SegmentFrame';
3+
import { SegmentItem } from './SegmentItem';
4+
5+
export const Segment = compoundComponents(SegmentFrame, {
6+
Item: SegmentItem,
7+
});
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import {
2+
Children,
3+
cloneElement,
4+
isValidElement,
5+
type PropsWithChildren,
6+
type ReactElement,
7+
useEffect,
8+
useMemo,
9+
useState,
10+
} from 'react';
11+
import { createVariants } from '../../utils';
12+
import styles from './Segment.module.css';
13+
import type { Props as SegmentItemProps } from './SegmentItem';
14+
15+
type Props<Value> = PropsWithChildren<{
16+
className?: string;
17+
value: Value;
18+
onChange: (value: Value) => void;
19+
}>;
20+
21+
const variants = createVariants(styles);
22+
23+
export const SegmentFrame = <Value,>({ className, children, onChange, ...props }: Props<Value>) => {
24+
const validChildren = useMemo(
25+
() =>
26+
Children.toArray(children).filter((child) => isValidElement(child)) as ReactElement<
27+
SegmentItemProps<Value>
28+
>[],
29+
[children],
30+
);
31+
32+
const options = useMemo(
33+
() => validChildren.map(({ props }) => ({ ...props })) ?? [],
34+
[validChildren],
35+
);
36+
37+
const [value, setValue] = useState(options[0]?.value);
38+
39+
useEffect(() => {
40+
onChange?.(value);
41+
}, [value, onChange]);
42+
43+
return (
44+
<div className={variants('frame', { className })} {...props}>
45+
{validChildren.map((child) =>
46+
cloneElement(child, { active: child.props.value === value, onChange: setValue }),
47+
)}
48+
</div>
49+
);
50+
};
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import type { PropsWithChildren } from 'react';
2+
import { createVariants } from '../../utils';
3+
import styles from './Segment.module.css';
4+
5+
export type Props<Value> = PropsWithChildren<{
6+
className?: string;
7+
active?: boolean;
8+
value: Value;
9+
onChange?: (value: Value) => void;
10+
}>;
11+
12+
const variants = createVariants(styles);
13+
14+
export const SegmentItem = <Value,>({
15+
className,
16+
active,
17+
value,
18+
onChange,
19+
...props
20+
}: Props<Value>) => (
21+
<button
22+
className={variants('item', { className, active })}
23+
onClick={() => onChange?.(value)}
24+
{...props}
25+
/>
26+
);
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { Segment } from './Segment';

packages/react/src/preset/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export { Button } from './Button';
2+
export { Segment } from './Segment';
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/** biome-ignore-all lint/suspicious/noExplicitAny: To accept any component */
2+
import type { ComponentType } from 'react';
3+
4+
export function compoundComponents<
5+
Parent extends ComponentType<any>,
6+
Children extends Record<string, ComponentType<any>>,
7+
>(ParentComponent: Parent, children: Children): Parent & Children {
8+
return Object.assign(ParentComponent, children) as Parent & Children;
9+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { compoundComponents } from './compoundComponents';

packages/react/src/utils/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export { Bridge, GuestEventType, HostEventType } from './Bridge';
2+
export { compoundComponents } from './compoundComponents';
23
export { createVariants } from './createVariants';
34
export { getClassNameFactory } from './getClassNameFactory';

0 commit comments

Comments
 (0)