diff --git a/src/base.css b/src/base.css
index 04d56db4..af1fede3 100644
--- a/src/base.css
+++ b/src/base.css
@@ -305,7 +305,7 @@
--border-neutral-hover: var(--color-neutral-500);
--border-neutral-default: var(--color-neutral-400);
--border-neutral-disabled: var(--color-neutral-400-40);
- --border-neutral-softest: var(--color-neutral-300);
+ --border-neutral-softest: var(--color-neutral-200);
--border-neutral-inset: var(--color-base-white);
--border-neutral-alpha: hsla(
0,
@@ -698,7 +698,7 @@
--border-neutral-hover: var(--color-neutral-500);
--border-neutral-default: var(--color-neutral-600);
--border-neutral-disabled: var(--color-neutral-600-40);
- --border-neutral-softest: var(--color-neutral-700);
+ --border-neutral-softest: var(--color-neutral-800-56);
--border-neutral-inset: var(--color-base-black);
--border-neutral-alpha: hsla(
255,
diff --git a/src/components/Input/index.tsx b/src/components/Input/index.tsx
index 42a437af..eb144def 100644
--- a/src/components/Input/index.tsx
+++ b/src/components/Input/index.tsx
@@ -58,7 +58,7 @@ export function Input({
{...commonProps}
{...props}
className={cn(
- 'bg-surface-primary-default placeholder:text-placeholder text-default h-full w-full rounded-md text-sm shadow-none outline-none placeholder:transition-colors placeholder:duration-500 disabled:cursor-not-allowed disabled:opacity-50',
+ 'bg-surface-primary-default placeholder:text-placeholder text-default h-full w-full text-sm shadow-none outline-none disabled:cursor-not-allowed disabled:opacity-50',
isFocused && 'placeholder:text-default'
)}
/>
@@ -72,7 +72,7 @@ export function Input({
cols={30}
rows={10}
className={cn(
- 'bg-surface-primary-default placeholder:text-placeholder text-default my-2 h-full max-h-60 min-h-16 w-full rounded-md px-3 py-3 text-sm shadow-none outline-none placeholder:transition-colors placeholder:duration-500 disabled:cursor-not-allowed disabled:opacity-50',
+ 'bg-surface-primary-default placeholder:text-placeholder text-default my-2 h-full max-h-60 min-h-16 w-full px-3 py-3 text-sm shadow-none outline-none disabled:cursor-not-allowed disabled:opacity-50',
isFocused && 'placeholder:text-default'
)}
/>
@@ -82,7 +82,7 @@ export function Input({
return (
+
+const meta: Meta
= {
+ component: SegmentedButton,
+ tags: ['autodocs'],
+}
+
+export default meta
+
+const StoryRenderer = (args: SegmentedButtonProps) => {
+ const getInitialActiveId = () => {
+ let foundActiveId: string | null = null
+ React.Children.forEach(args.children, (child) => {
+ if (
+ foundActiveId === null &&
+ React.isValidElement(child) &&
+ child.props.active &&
+ child.props.id
+ ) {
+ foundActiveId = child.props.id as string
+ }
+ })
+
+ if (foundActiveId) return foundActiveId
+
+ let firstId: string | null = null
+ React.Children.forEach(args.children, (child) => {
+ if (firstId === null && React.isValidElement(child) && child.props.id) {
+ firstId = child.props.id as string
+ }
+ })
+
+ return firstId
+ }
+
+ const [activeId, setActiveId] = useState(() =>
+ getInitialActiveId()
+ )
+ return (
+
+ {React.Children.map(args.children, (child) => {
+ if (!React.isValidElement(child)) {
+ return child
+ }
+
+ const props: SegmentedButtonItemProps = {
+ id: child.props.id ?? '',
+ active: child.props.id === activeId,
+ children: child.props.children,
+ onClick: () => setActiveId(child.props.id ?? ''),
+ }
+ return React.cloneElement(child, props)
+ })}
+
+ )
+}
+
+export const Default: Story = {
+ args: {
+ children: [
+ Chat,
+
+ Split
+ ,
+ Code,
+ ],
+ },
+ render: (args) => ,
+}
+
+export const TwoItems: Story = {
+ args: {
+ children: [
+ Chat,
+
+ Split
+ ,
+ ],
+ },
+ render: (args) => ,
+}
+
+export const FourItems: Story = {
+ args: {
+ children: [
+ Chat,
+
+ Split
+ ,
+ Code,
+ Beast Mode,
+ ],
+ },
+ render: (args) => ,
+}
diff --git a/src/components/SegmentedButton/index.tsx b/src/components/SegmentedButton/index.tsx
new file mode 100644
index 00000000..1661387d
--- /dev/null
+++ b/src/components/SegmentedButton/index.tsx
@@ -0,0 +1,108 @@
+import { cn } from '@/lib/utils'
+import { motion, MotionProps } from 'framer-motion'
+import React from 'react'
+import styles from './index.module.css'
+
+export interface SegmentedButtonProps {
+ children: React.ReactNode
+ className?: string
+}
+
+const SegmentedButtonBase = ({ children, className }: SegmentedButtonProps) => {
+ const [hoveredId, setHoveredId] = React.useState(null)
+
+ const enhancedChildren = React.Children.map(children, (child) => {
+ if (!React.isValidElement(child)) return child
+ const childId: string | undefined = child.props.id
+
+ const handleMouseEnter: React.MouseEventHandler = (
+ e
+ ) => {
+ setHoveredId(childId ?? null)
+ child.props.onMouseEnter?.(e)
+ }
+
+ const handleMouseLeave: React.MouseEventHandler = (
+ e
+ ) => {
+ setHoveredId((current) => (current === childId ? null : current))
+ child.props.onMouseLeave?.(e)
+ }
+
+ const highlighted = hoveredId ? childId === hoveredId : child.props.active
+
+ return React.cloneElement(
+ child as React.ReactElement,
+ {
+ highlighted,
+ onMouseEnter: handleMouseEnter,
+ onMouseLeave: handleMouseLeave,
+ }
+ )
+ })
+
+ return (
+ setHoveredId(null)}
+ >
+ {enhancedChildren}
+
+ )
+}
+
+SegmentedButtonBase.displayName = 'SegmentedButton'
+
+export interface SegmentedButtonItemProps extends MotionProps {
+ children: React.ReactNode
+ className?: string
+ active?: boolean
+ highlighted?: boolean
+ id: string
+ onClick?: () => void
+ onMouseEnter?: React.MouseEventHandler
+ onMouseLeave?: React.MouseEventHandler
+}
+
+const SegmentedButtonItem = ({
+ children,
+ className,
+ highlighted,
+ ...props
+}: SegmentedButtonItemProps) => {
+ return (
+
+ {highlighted && (
+
+ )}
+
+ {children}
+
+
+ )
+}
+
+SegmentedButtonItem.displayName = 'SegmentedButtonItem'
+
+export const SegmentedButton = Object.assign(SegmentedButtonBase, {
+ Item: SegmentedButtonItem,
+})
diff --git a/src/index.ts b/src/index.ts
index ca8b5489..bd7cefda 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -107,7 +107,7 @@ export { Facepile, type FacepileProps } from '@/components/Facepile'
export { Link, type LinkProps } from '@/components/Link'
export { Dialog } from '@/components/Dialog'
export { Switch, type SwitchProps } from '@/components/Switch'
-
+export { SegmentedButton } from '@/components/SegmentedButton'
// AppLayout
export {
AppLayout,