Compare commits

...

4 Commits

Author SHA1 Message Date
Raunak Bhagat
22903c9343 feat: move SidebarTab to Opal, add disabled styling for sidebar variants
- Add [data-disabled] CSS rules for sidebar-heavy and sidebar-light
  variants (text-02 foreground, transparent background, no opacity)
- Move SidebarTab from refresh-components to @opal/components with
  disabled prop, docstrings, stories, and README
- Old file re-exports from Opal for backwards compatibility
2026-04-02 10:25:23 -07:00
Raunak Bhagat
17b0d19faf docs: update Disabled section in AGENTS.md 2026-04-02 09:51:22 -07:00
Raunak Bhagat
3b6955468e docs: update Disabled and Interactive.Simple docstrings
Disabled is now a pure CSS wrapper with no React context. Remove
"use client" directive, update CLAUDE.md description, and simplify
Interactive.Simple's docstring.
2026-04-02 09:51:22 -07:00
Raunak Bhagat
9c091ecd45 refactor: remove Disabled context, add disabled prop to all Interactive primitives
- Add `disabled` prop to Interactive.Stateful and Interactive.Simple
- Remove useDisabled() from Interactive.Container (derives from aria-disabled)
- Add `disabled` prop to OpenButton and SelectButton
- Migrate callsites: SharedAppInputBar, AppInputBar, InputBar, LLMPopover
- Strip context, useDisabled, and DisabledContextValue from Disabled component
- Disabled is now a pure CSS wrapper with no React context
2026-04-02 09:51:22 -07:00
18 changed files with 441 additions and 265 deletions

View File

@@ -193,9 +193,9 @@ hover, active, and disabled states.
### Disabled (`core/disabled/`)
Propagates disabled state via React context. `Interactive.Stateless` and `Interactive.Stateful`
consume this automatically, so wrapping a subtree in `<Disabled disabled={true}>` disables all
interactive descendants.
A pure CSS wrapper that applies disabled visuals (`opacity-50`, `cursor-not-allowed`,
`pointer-events: none`) to a single child element via Radix `Slot`. Has no React context —
Interactive primitives and buttons manage their own disabled state via a `disabled` prop.
### Hoverable (`core/animations/`)

View File

@@ -1,6 +1,5 @@
import {
Interactive,
useDisabled,
type InteractiveStatefulProps,
type InteractiveStatefulInteraction,
} from "@opal/core";
@@ -74,6 +73,9 @@ type OpenButtonProps = Omit<InteractiveStatefulProps, "variant"> & {
/** Override the default rounding derived from `size`. */
roundingVariant?: InteractiveContainerRoundingVariant;
/** Applies disabled styling and suppresses clicks. */
disabled?: boolean;
};
// ---------------------------------------------------------------------------
@@ -92,10 +94,9 @@ function OpenButton({
roundingVariant: roundingVariantOverride,
interaction,
variant = "select-heavy",
disabled,
...statefulProps
}: OpenButtonProps) {
const { isDisabled } = useDisabled();
// Derive open state: explicit prop → Radix data-state (injected via Slot chain)
const dataState = (statefulProps as Record<string, unknown>)["data-state"] as
| string
@@ -119,6 +120,7 @@ function OpenButton({
<Interactive.Stateful
variant={variant}
interaction={resolvedInteraction}
disabled={disabled}
{...statefulProps}
>
<Interactive.Container
@@ -168,7 +170,7 @@ function OpenButton({
);
const resolvedTooltip =
tooltip ?? (foldable && isDisabled && children ? children : undefined);
tooltip ?? (foldable && disabled && children ? children : undefined);
if (!resolvedTooltip) return button;

View File

@@ -1,11 +1,7 @@
"use client";
import "@opal/components/buttons/select-button/styles.css";
import {
Interactive,
useDisabled,
type InteractiveStatefulProps,
} from "@opal/core";
import { Interactive, type InteractiveStatefulProps } from "@opal/core";
import type {
ContainerSizeVariants,
ExtremaSizeVariants,
@@ -64,6 +60,9 @@ type SelectButtonProps = InteractiveStatefulProps &
/** Which side the tooltip appears on. */
tooltipSide?: TooltipSide;
/** Applies disabled styling and suppresses clicks. */
disabled?: boolean;
};
// ---------------------------------------------------------------------------
@@ -80,9 +79,9 @@ function SelectButton({
width,
tooltip,
tooltipSide = "top",
disabled,
...statefulProps
}: SelectButtonProps) {
const { isDisabled } = useDisabled();
const isLarge = size === "lg";
const labelEl = children ? (
@@ -96,7 +95,7 @@ function SelectButton({
) : null;
const button = (
<Interactive.Stateful {...statefulProps}>
<Interactive.Stateful disabled={disabled} {...statefulProps}>
<Interactive.Container
type={type}
heightVariant={size}
@@ -128,7 +127,7 @@ function SelectButton({
);
const resolvedTooltip =
tooltip ?? (foldable && isDisabled && children ? children : undefined);
tooltip ?? (foldable && disabled && children ? children : undefined);
if (!resolvedTooltip) return button;

View File

@@ -0,0 +1,59 @@
# SidebarTab
**Import:** `import { SidebarTab, type SidebarTabProps } from "@opal/components";`
A sidebar navigation tab built on `Interactive.Stateful` > `Interactive.Container`. Designed for admin and app sidebars.
## Architecture
```
div.relative
└─ Interactive.Stateful <- variant (sidebar-heavy | sidebar-light), state, disabled
└─ Interactive.Container <- rounding, height, width
├─ Link? (absolute overlay for client-side navigation)
├─ rightChildren? (absolute, above Link for inline actions)
└─ ContentAction (icon + title + truncation spacer)
```
- **`sidebar-heavy`** (default) — muted when unselected (text-03/text-02), bold when selected (text-04/text-03)
- **`sidebar-light`** (via `lowlight`) — uniformly muted across all states (text-02/text-02)
- **Disabled** — both variants use text-02 foreground, transparent background, no hover/active states
- **Navigation** uses an absolutely positioned `<Link>` overlay rather than `href` on the Interactive element, so `rightChildren` can sit above it with `pointer-events-auto`.
## Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `icon` | `IconFunctionComponent` | — | Left icon |
| `children` | `ReactNode` | — | Label text or custom content |
| `selected` | `boolean` | `false` | Active/selected state |
| `lowlight` | `boolean` | `false` | Uses muted `sidebar-light` variant |
| `disabled` | `boolean` | `false` | Disables the tab |
| `folded` | `boolean` | `false` | Collapses label, shows tooltip on hover |
| `nested` | `boolean` | `false` | Renders spacer instead of icon for indented items |
| `href` | `string` | — | Client-side navigation URL |
| `onClick` | `MouseEventHandler` | — | Click handler |
| `type` | `ButtonType` | — | HTML button type |
| `rightChildren` | `ReactNode` | — | Actions rendered on the right side |
## Usage
```tsx
import { SidebarTab } from "@opal/components";
import { SvgSettings, SvgLock } from "@opal/icons";
// Active tab
<SidebarTab icon={SvgSettings} href="/admin/settings" selected>
Settings
</SidebarTab>
// Disabled enterprise-only tab
<SidebarTab icon={SvgLock} disabled>
Groups
</SidebarTab>
// Folded sidebar (icon only, tooltip on hover)
<SidebarTab icon={SvgSettings} href="/admin/settings" folded>
Settings
</SidebarTab>
```

View File

@@ -0,0 +1,90 @@
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { SidebarTab } from "@opal/components/buttons/sidebar-tab/components";
import { SvgSettings, SvgUsers, SvgLock, SvgArrowUpCircle } from "@opal/icons";
import { Button } from "@opal/components";
import { SvgTrash } from "@opal/icons";
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
const meta: Meta<typeof SidebarTab> = {
title: "opal/components/SidebarTab",
component: SidebarTab,
tags: ["autodocs"],
decorators: [
(Story) => (
<TooltipPrimitive.Provider>
<div style={{ width: 260, background: "var(--background-neutral-01)" }}>
<Story />
</div>
</TooltipPrimitive.Provider>
),
],
};
export default meta;
type Story = StoryObj<typeof SidebarTab>;
export const Default: Story = {
args: {
icon: SvgSettings,
children: "Settings",
},
};
export const Selected: Story = {
args: {
icon: SvgSettings,
children: "Settings",
selected: true,
},
};
export const Lowlight: Story = {
args: {
icon: SvgSettings,
children: "Settings",
lowlight: true,
},
};
export const Disabled: Story = {
args: {
icon: SvgLock,
children: "Enterprise Only",
disabled: true,
},
};
export const WithRightChildren: Story = {
args: {
icon: SvgUsers,
children: "Users",
rightChildren: (
<Button
icon={SvgTrash}
size="xs"
prominence="tertiary"
variant="danger"
/>
),
},
};
export const SidebarExample: Story = {
render: () => (
<div className="flex flex-col">
<SidebarTab icon={SvgSettings} selected>
LLM Models
</SidebarTab>
<SidebarTab icon={SvgSettings}>Web Search</SidebarTab>
<SidebarTab icon={SvgUsers}>Users</SidebarTab>
<SidebarTab icon={SvgLock} disabled>
Groups
</SidebarTab>
<SidebarTab icon={SvgLock} disabled>
SCIM
</SidebarTab>
<SidebarTab icon={SvgArrowUpCircle}>Upgrade Plan</SidebarTab>
</div>
),
};

View File

@@ -0,0 +1,159 @@
"use client";
import React from "react";
import type { ButtonType, IconFunctionComponent } from "@opal/types";
import type { Route } from "next";
import { Interactive } from "@opal/core";
import { ContentAction } from "@opal/layouts";
import { Text } from "@opal/components";
import Link from "next/link";
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import "@opal/components/tooltip.css";
// ---------------------------------------------------------------------------
// Types
// ---------------------------------------------------------------------------
interface SidebarTabProps {
/** Collapses the label, showing only the icon. */
folded?: boolean;
/** Marks this tab as the currently active/selected item. */
selected?: boolean;
/** Uses the muted `sidebar-light` variant instead of `sidebar-heavy`. */
lowlight?: boolean;
/** Renders an empty spacer in place of the icon for nested items. */
nested?: boolean;
/** Disables the tab — applies muted colors and suppresses clicks. */
disabled?: boolean;
onClick?: React.MouseEventHandler<HTMLElement>;
href?: string;
type?: ButtonType;
icon?: IconFunctionComponent;
children?: React.ReactNode;
/** Content rendered on the right side (e.g. action buttons). */
rightChildren?: React.ReactNode;
}
// ---------------------------------------------------------------------------
// SidebarTab
// ---------------------------------------------------------------------------
/**
* Sidebar navigation tab built on `Interactive.Stateful` > `Interactive.Container`.
*
* Uses `sidebar-heavy` (default) or `sidebar-light` (when `lowlight`) variants
* for color styling. Supports an overlay `Link` for client-side navigation,
* `rightChildren` for inline actions, and folded mode with an auto-tooltip.
*/
function SidebarTab({
folded,
selected,
lowlight,
nested,
disabled,
onClick,
href,
type,
icon,
rightChildren,
children,
}: SidebarTabProps) {
const Icon =
icon ??
(nested
? ((() => (
<div className="w-6" aria-hidden="true" />
)) as IconFunctionComponent)
: null);
// The `rightChildren` node is absolutely positioned to sit on top of the
// overlay Link. A zero-width spacer reserves truncation space for the title.
const truncationSpacer = rightChildren && (
<div className="w-0 group-hover/SidebarTab:w-6" />
);
const content = (
<div className="relative">
<Interactive.Stateful
variant={lowlight ? "sidebar-light" : "sidebar-heavy"}
state={selected ? "selected" : "empty"}
disabled={disabled}
onClick={onClick}
type="button"
group="group/SidebarTab"
>
<Interactive.Container
roundingVariant="sm"
heightVariant="lg"
widthVariant="full"
type={type}
>
{href && !disabled && (
<Link
href={href as Route}
scroll={false}
className="absolute z-[99] inset-0 rounded-08"
tabIndex={-1}
/>
)}
{!folded && rightChildren && (
<div className="absolute z-[100] right-1.5 top-0 bottom-0 flex flex-col justify-center items-center pointer-events-auto">
{rightChildren}
</div>
)}
{typeof children === "string" ? (
<ContentAction
icon={Icon ?? undefined}
title={folded ? "" : children}
sizePreset="main-ui"
variant="body"
widthVariant="full"
paddingVariant="fit"
rightChildren={truncationSpacer}
/>
) : (
<div className="flex flex-row items-center gap-2 flex-1">
{Icon && (
<div className="flex items-center justify-center p-0.5">
<Icon className="h-[1rem] w-[1rem] text-text-03" />
</div>
)}
{children}
{truncationSpacer}
</div>
)}
</Interactive.Container>
</Interactive.Stateful>
</div>
);
if (typeof children !== "string") return content;
if (folded) {
return (
<TooltipPrimitive.Root>
<TooltipPrimitive.Trigger asChild>{content}</TooltipPrimitive.Trigger>
<TooltipPrimitive.Portal>
<TooltipPrimitive.Content
className="opal-tooltip"
side="right"
sideOffset={4}
>
<Text>{children}</Text>
</TooltipPrimitive.Content>
</TooltipPrimitive.Portal>
</TooltipPrimitive.Root>
);
}
return content;
}
export { SidebarTab, type SidebarTabProps };

View File

@@ -33,6 +33,12 @@ export {
type LineItemButtonProps,
} from "@opal/components/buttons/line-item-button/components";
/* SidebarTab */
export {
SidebarTab,
type SidebarTabProps,
} from "@opal/components/buttons/sidebar-tab/components";
/* Text */
export {
Text,

View File

@@ -1,33 +1,7 @@
"use client";
import "@opal/core/disabled/styles.css";
import React, { createContext, useContext } from "react";
import React from "react";
import { Slot } from "@radix-ui/react-slot";
// ---------------------------------------------------------------------------
// Context
// ---------------------------------------------------------------------------
interface DisabledContextValue {
isDisabled: boolean;
allowClick: boolean;
}
const DisabledContext = createContext<DisabledContextValue>({
isDisabled: false,
allowClick: false,
});
/**
* Returns the current disabled state from the nearest `<Disabled>` ancestor.
*
* Used internally by `Interactive.Stateless` and `Interactive.Stateful` to
* derive `data-disabled` and `aria-disabled` attributes automatically.
*/
function useDisabled(): DisabledContextValue {
return useContext(DisabledContext);
}
// ---------------------------------------------------------------------------
// Types
// ---------------------------------------------------------------------------
@@ -56,8 +30,8 @@ interface DisabledProps extends React.HTMLAttributes<HTMLElement> {
// ---------------------------------------------------------------------------
/**
* Wrapper component that propagates disabled state via context and applies
* baseline disabled CSS (opacity, cursor, pointer-events) to its child.
* Wrapper component that applies baseline disabled CSS (opacity, cursor,
* pointer-events) to its child element.
*
* Uses Radix `Slot` — merges props onto the single child element without
* adding any DOM node. Works correctly inside Radix `asChild` chains.
@@ -65,7 +39,7 @@ interface DisabledProps extends React.HTMLAttributes<HTMLElement> {
* @example
* ```tsx
* <Disabled disabled={!canSubmit}>
* <Button onClick={handleSubmit}>Save</Button>
* <div>...</div>
* </Disabled>
* ```
*/
@@ -77,20 +51,16 @@ function Disabled({
...rest
}: DisabledProps) {
return (
<DisabledContext.Provider
value={{ isDisabled: !!disabled, allowClick: !!allowClick }}
<Slot
ref={ref}
{...rest}
aria-disabled={disabled || undefined}
data-opal-disabled={disabled || undefined}
data-allow-click={disabled && allowClick ? "" : undefined}
>
<Slot
ref={ref}
{...rest}
aria-disabled={disabled || undefined}
data-opal-disabled={disabled || undefined}
data-allow-click={disabled && allowClick ? "" : undefined}
>
{children}
</Slot>
</DisabledContext.Provider>
{children}
</Slot>
);
}
export { Disabled, useDisabled, type DisabledProps, type DisabledContextValue };
export { Disabled, type DisabledProps };

View File

@@ -1,10 +1,5 @@
/* Disabled */
export {
Disabled,
useDisabled,
type DisabledProps,
type DisabledContextValue,
} from "@opal/core/disabled/components";
export { Disabled, type DisabledProps } from "@opal/core/disabled/components";
/* Animations (formerly Hoverable) */
export {

View File

@@ -10,7 +10,6 @@ import {
widthVariants,
type ExtremaSizeVariants,
} from "@opal/shared";
import { useDisabled } from "@opal/core/disabled/components";
// ---------------------------------------------------------------------------
// Types
@@ -102,7 +101,6 @@ function InteractiveContainer({
widthVariant = "fit",
...props
}: InteractiveContainerProps) {
const { allowClick } = useDisabled();
const {
className: slotClassName,
style: slotStyle,
@@ -148,8 +146,7 @@ function InteractiveContainer({
if (type) {
const ariaDisabled = (rest as Record<string, unknown>)["aria-disabled"];
const nativeDisabled =
(type === "submit" || !allowClick) &&
(ariaDisabled === true || ariaDisabled === "true" || undefined);
ariaDisabled === true || ariaDisabled === "true" || undefined;
return (
<button
ref={ref as React.Ref<HTMLButtonElement>}

View File

@@ -1,7 +1,6 @@
import React from "react";
import { Slot } from "@radix-ui/react-slot";
import { cn } from "@opal/utils";
import { useDisabled } from "@opal/core/disabled/components";
import { guardPortalClick } from "@opal/core/interactive/utils";
// ---------------------------------------------------------------------------
@@ -29,6 +28,11 @@ interface InteractiveSimpleProps
* Link target (e.g. `"_blank"`). Only used when `href` is provided.
*/
target?: string;
/**
* Applies disabled cursor and suppresses clicks.
*/
disabled?: boolean;
}
// ---------------------------------------------------------------------------
@@ -38,8 +42,8 @@ interface InteractiveSimpleProps
/**
* Minimal interactive surface primitive.
*
* Provides cursor styling, click handling, disabled integration, and
* optional link/group support — but **no color or background styling**.
* Provides cursor styling, click handling, and optional link/group
* support — but **no color or background styling**.
*
* Use this for elements that need interactivity (click, cursor, disabled)
* without participating in the Interactive color system.
@@ -59,9 +63,10 @@ function InteractiveSimple({
group,
href,
target,
disabled,
...props
}: InteractiveSimpleProps) {
const { isDisabled, allowClick } = useDisabled();
const isDisabled = !!disabled;
const classes = cn(
"cursor-pointer select-none",
@@ -88,7 +93,7 @@ function InteractiveSimple({
{...linkAttrs}
{...slotProps}
onClick={
isDisabled && !allowClick
isDisabled
? href
? (e: React.MouseEvent) => e.preventDefault()
: undefined

View File

@@ -3,7 +3,6 @@ import "@opal/core/interactive/stateful/styles.css";
import React from "react";
import { Slot } from "@radix-ui/react-slot";
import { cn } from "@opal/utils";
import { useDisabled } from "@opal/core/disabled/components";
import { guardPortalClick } from "@opal/core/interactive/utils";
import type { ButtonType, WithoutStyles } from "@opal/types";
@@ -87,6 +86,11 @@ interface InteractiveStatefulProps
* Link target (e.g. `"_blank"`). Only used when `href` is provided.
*/
target?: string;
/**
* Applies variant-specific disabled colors and suppresses clicks.
*/
disabled?: boolean;
}
// ---------------------------------------------------------------------------
@@ -100,8 +104,7 @@ interface InteractiveStatefulProps
* (empty/filled/selected). Applies variant/state color styling via CSS
* data-attributes and merges onto a single child element via Radix `Slot`.
*
* Disabled state is consumed from the nearest `<Disabled>` ancestor via
* context — there is no `disabled` prop on this component.
* Disabled state is controlled via the `disabled` prop.
*/
function InteractiveStateful({
ref,
@@ -112,9 +115,10 @@ function InteractiveStateful({
type,
href,
target,
disabled,
...props
}: InteractiveStatefulProps) {
const { isDisabled, allowClick } = useDisabled();
const isDisabled = !!disabled;
// onClick/href are always passed directly — Stateful is the outermost Slot,
// so Radix Slot-injected handlers don't bypass this guard.
@@ -150,7 +154,7 @@ function InteractiveStateful({
{...linkAttrs}
{...slotProps}
onClick={
isDisabled && !allowClick
isDisabled
? href
? (e: React.MouseEvent) => e.preventDefault()
: undefined

View File

@@ -550,6 +550,14 @@
) {
@apply bg-background-tint-03;
}
/* ---------------------------------------------------------------------------
Sidebar-Heavy — Disabled (all states)
--------------------------------------------------------------------------- */
.interactive[data-interactive-variant="sidebar-heavy"][data-disabled] {
@apply bg-transparent;
--interactive-foreground: var(--text-02);
--interactive-foreground-icon: var(--text-02);
}
/* ===========================================================================
Sidebar-Light
@@ -607,3 +615,11 @@
) {
@apply bg-background-tint-03;
}
/* ---------------------------------------------------------------------------
Sidebar-Light — Disabled (all states)
--------------------------------------------------------------------------- */
.interactive[data-interactive-variant="sidebar-light"][data-disabled] {
@apply bg-transparent;
--interactive-foreground: var(--text-02);
--interactive-foreground-icon: var(--text-02);
}

View File

@@ -378,18 +378,17 @@ const InputBar = memo(
side="top"
>
<span>
<Disabled disabled={disabled}>
<SelectButton
leftIcon={SvgOrganization}
engaged={demoDataEnabled}
action
folded
onClick={() => router.push(CRAFT_CONFIGURE_PATH)}
className="bg-action-link-01"
>
Demo Data Active
</SelectButton>
</Disabled>
<SelectButton
disabled={disabled}
leftIcon={SvgOrganization}
engaged={demoDataEnabled}
action
folded
onClick={() => router.push(CRAFT_CONFIGURE_PATH)}
className="bg-action-link-01"
>
Demo Data Active
</SelectButton>
</span>
</SimpleTooltip>
)}

View File

@@ -1,122 +1,4 @@
"use client";
import React from "react";
import type { ButtonType, IconFunctionComponent } from "@opal/types";
import type { Route } from "next";
import { Interactive } from "@opal/core";
import { ContentAction } from "@opal/layouts";
import Link from "next/link";
import SimpleTooltip from "@/refresh-components/SimpleTooltip";
export interface SidebarTabProps {
// Button states:
folded?: boolean;
selected?: boolean;
lowlight?: boolean;
nested?: boolean;
// Button properties:
onClick?: React.MouseEventHandler<HTMLElement>;
href?: string;
type?: ButtonType;
icon?: IconFunctionComponent;
children?: React.ReactNode;
rightChildren?: React.ReactNode;
}
export default function SidebarTab({
folded,
selected,
lowlight,
nested,
onClick,
href,
type,
icon,
rightChildren,
children,
}: SidebarTabProps) {
const Icon =
icon ??
(nested
? ((() => (
<div className="w-6" aria-hidden="true" />
)) as IconFunctionComponent)
: null);
// NOTE (@raunakab)
//
// The `rightChildren` node NEEDS to be absolutely positioned since it needs to live on top of the absolutely positioned `Link`.
// However, having the `rightChildren` be absolutely positioned means that it cannot appropriately truncate the title.
// Therefore, we add a dummy node solely for the truncation effects that we obtain.
const truncationSpacer = rightChildren && (
<div className="w-0 group-hover/SidebarTab:w-6" />
);
const content = (
<div className="relative">
<Interactive.Stateful
variant={lowlight ? "sidebar-light" : "sidebar-heavy"}
state={selected ? "selected" : "empty"}
onClick={onClick}
type="button"
group="group/SidebarTab"
>
<Interactive.Container
roundingVariant="sm"
heightVariant="lg"
widthVariant="full"
type={type}
>
{href && (
<Link
href={href as Route}
scroll={false}
className="absolute z-[99] inset-0 rounded-08"
tabIndex={-1}
/>
)}
{!folded && rightChildren && (
<div className="absolute z-[100] right-1.5 top-0 bottom-0 flex flex-col justify-center items-center pointer-events-auto">
{rightChildren}
</div>
)}
{typeof children === "string" ? (
<ContentAction
icon={Icon ?? undefined}
title={folded ? "" : children}
sizePreset="main-ui"
variant="body"
widthVariant="full"
paddingVariant="fit"
rightChildren={truncationSpacer}
/>
) : (
<div className="flex flex-row items-center gap-2 flex-1">
{Icon && (
<div className="flex items-center justify-center p-0.5">
<Icon className="h-[1rem] w-[1rem] text-text-03" />
</div>
)}
{children}
{
// NOTE (@raunakab)
//
// Adding the `truncationSpacer` here for the same reason as above.
truncationSpacer
}
</div>
)}
</Interactive.Container>
</Interactive.Stateful>
</div>
);
if (typeof children !== "string") return content;
if (folded)
return <SimpleTooltip tooltip={children}>{content}</SimpleTooltip>;
return content;
}
export {
SidebarTab as default,
type SidebarTabProps,
} from "@opal/components/buttons/sidebar-tab/components";

View File

@@ -29,7 +29,6 @@ import {
} from "@opal/icons";
import { Section } from "@/layouts/general-layouts";
import { OpenButton } from "@opal/components";
import { Disabled } from "@opal/core";
import { LLMOption, LLMOptionGroup } from "./interfaces";
export interface LLMPopoverProps {
@@ -356,21 +355,20 @@ export default function LLMPopover({
<Popover open={open} onOpenChange={setOpen}>
<div data-testid="llm-popover-trigger">
<Popover.Trigger asChild disabled={disabled}>
<Disabled disabled={disabled}>
<OpenButton
icon={
foldable
? SvgRefreshCw
: getProviderIcon(
llmManager.currentLlm.provider,
llmManager.currentLlm.modelName
)
}
foldable={foldable}
>
{currentLlmDisplayName}
</OpenButton>
</Disabled>
<OpenButton
disabled={disabled}
icon={
foldable
? SvgRefreshCw
: getProviderIcon(
llmManager.currentLlm.provider,
llmManager.currentLlm.modelName
)
}
foldable={foldable}
>
{currentLlmDisplayName}
</OpenButton>
</Popover.Trigger>
</div>

View File

@@ -539,38 +539,36 @@ const AppInputBar = React.memo(
/>
)}
{onToggleTabReading ? (
<Disabled disabled={disabled}>
<SelectButton
icon={SvgGlobe}
onClick={onToggleTabReading}
state={tabReadingEnabled ? "selected" : "empty"}
>
{tabReadingEnabled
? currentTabUrl
? (() => {
try {
return new URL(currentTabUrl).hostname;
} catch {
return currentTabUrl;
}
})()
: "Reading tab..."
: "Read this tab"}
</SelectButton>
</Disabled>
<SelectButton
disabled={disabled}
icon={SvgGlobe}
onClick={onToggleTabReading}
state={tabReadingEnabled ? "selected" : "empty"}
>
{tabReadingEnabled
? currentTabUrl
? (() => {
try {
return new URL(currentTabUrl).hostname;
} catch {
return currentTabUrl;
}
})()
: "Reading tab..."
: "Read this tab"}
</SelectButton>
) : (
showDeepResearch && (
<Disabled disabled={disabled}>
<SelectButton
variant="select-light"
icon={SvgHourglass}
onClick={toggleDeepResearch}
state={deepResearchEnabled ? "selected" : "empty"}
foldable={!deepResearchEnabled}
>
Deep Research
</SelectButton>
</Disabled>
<SelectButton
disabled={disabled}
variant="select-light"
icon={SvgHourglass}
onClick={toggleDeepResearch}
state={deepResearchEnabled ? "selected" : "empty"}
foldable={!deepResearchEnabled}
>
Deep Research
</SelectButton>
)
)}

View File

@@ -2,7 +2,6 @@
import Text from "@/refresh-components/texts/Text";
import { Button, OpenButton, SelectButton } from "@opal/components";
import { Disabled } from "@opal/core";
import { OpenAISVG } from "@/components/icons/icons";
import {
SvgPlusCircle,
@@ -29,16 +28,14 @@ export default function SharedAppInputBar() {
<div className="flex flex-row items-center">
<Button disabled icon={SvgPlusCircle} prominence="tertiary" />
<Button disabled icon={SvgSliders} prominence="tertiary" />
<Disabled disabled>
<SelectButton icon={SvgHourglass} />
</Disabled>
<SelectButton disabled icon={SvgHourglass} />
</div>
{/* Right side controls */}
<div className="flex flex-row items-center gap-1">
<Disabled disabled>
<OpenButton icon={OpenAISVG}>GPT-4o</OpenButton>
</Disabled>
<OpenButton disabled icon={OpenAISVG}>
GPT-4o
</OpenButton>
<Button disabled icon={SvgArrowUp} />
</div>
</div>