mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-16 15:45:13 +00:00
Improve Button and ButtonGroup styling (#3540)
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"gitbook": patch
|
||||
---
|
||||
|
||||
Improve `Button` and `ButtonGroup` styling
|
||||
@@ -231,13 +231,15 @@ function AIActionWrapper(props: {
|
||||
}
|
||||
size="xsmall"
|
||||
variant="secondary"
|
||||
label={shortLabel || label}
|
||||
label={label ?? shortLabel}
|
||||
className="bg-tint-base text-sm"
|
||||
onClick={onClick}
|
||||
href={href}
|
||||
target={href ? '_blank' : undefined}
|
||||
disabled={disabled || loading}
|
||||
/>
|
||||
>
|
||||
{shortLabel}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
} from '@/components/AIActions/AIActions';
|
||||
import { Button, ButtonGroup } from '@/components/primitives/Button';
|
||||
import { DropdownMenu, DropdownMenuSeparator } from '@/components/primitives/DropdownMenu';
|
||||
import { tString, useLanguage } from '@/intl/client';
|
||||
import type { SiteCustomizationSettings } from '@gitbook/api';
|
||||
|
||||
import { Icon } from '@gitbook/icons';
|
||||
@@ -25,6 +26,7 @@ interface AIActionsDropdownProps {
|
||||
*/
|
||||
export function AIActionsDropdown(props: AIActionsDropdownProps) {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const language = useLanguage();
|
||||
|
||||
return (
|
||||
<ButtonGroup ref={ref}>
|
||||
@@ -41,6 +43,7 @@ export function AIActionsDropdown(props: AIActionsDropdownProps) {
|
||||
className="size-3 transition-transform group-data-[state=open]/button:rotate-180"
|
||||
/>
|
||||
}
|
||||
label={tString(language, 'more')}
|
||||
iconOnly
|
||||
size="xsmall"
|
||||
variant="secondary"
|
||||
|
||||
@@ -11,8 +11,8 @@ import { AIChatIcon } from './AIChatIcon';
|
||||
/**
|
||||
* Button to open/close the AI chat.
|
||||
*/
|
||||
export function AIChatButton(props: { trademark: boolean }) {
|
||||
const { trademark } = props;
|
||||
export function AIChatButton(props: { trademark: boolean; withLabel: boolean }) {
|
||||
const { trademark, withLabel } = props;
|
||||
const chatController = useAIChatController();
|
||||
const language = useLanguage();
|
||||
|
||||
@@ -20,8 +20,8 @@ export function AIChatButton(props: { trademark: boolean }) {
|
||||
<Button
|
||||
icon={<AIChatIcon trademark={trademark} />}
|
||||
data-testid="ai-chat-button"
|
||||
iconOnly
|
||||
size="default"
|
||||
iconOnly={!withLabel}
|
||||
size="medium"
|
||||
variant="header"
|
||||
className={tcls('h-9 px-2.5')}
|
||||
label={
|
||||
@@ -33,6 +33,8 @@ export function AIChatButton(props: { trademark: boolean }) {
|
||||
onClick={() => {
|
||||
chatController.open();
|
||||
}}
|
||||
/>
|
||||
>
|
||||
{withLabel ? <span className="max-md:hidden">{t(language, 'ask')}</span> : null}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ export default function AIChatSuggestedQuestions(props: { chatController: AIChat
|
||||
{DEFAULT_SUGGESTED_QUESTIONS.map((question, index) => (
|
||||
<Button
|
||||
key={question}
|
||||
label={question}
|
||||
variant="secondary"
|
||||
size="medium"
|
||||
className="max-w-full animate-[present_500ms_both] whitespace-normal"
|
||||
|
||||
@@ -43,7 +43,7 @@ export const CodeBlockRenderer = forwardRef(function CodeBlockRenderer(
|
||||
</div>
|
||||
<CopyCodeButton
|
||||
codeId={id}
|
||||
style="z-2 mt-2 mr-2 self-start justify-self-end rounded-md bg-transparent p-1 text-tint text-xs leading-none opacity-0 ring-1 ring-tint backdrop-blur-md transition-opacity duration-75 [grid-area:2/1] hover:ring-tint-hover group-hover/codeblock:opacity-11"
|
||||
style="z-2 mt-2 mr-2 self-start justify-self-end leading-none opacity-0 backdrop-blur-md [grid-area:2/1] group-hover/codeblock:opacity-11"
|
||||
/>
|
||||
<pre
|
||||
className={tcls(
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { Button } from '@/components/primitives';
|
||||
import { t, useLanguage } from '@/intl/client';
|
||||
import { type ClassValue, tcls } from '@/lib/tailwind';
|
||||
|
||||
@@ -41,9 +42,14 @@ export function CopyCodeButton(props: { codeId: string; style: ClassValue }) {
|
||||
};
|
||||
|
||||
return (
|
||||
<button onClick={onClick} className={tcls(style, 'print:hidden')}>
|
||||
<Button
|
||||
size="xsmall"
|
||||
variant="secondary"
|
||||
onClick={onClick}
|
||||
className={tcls(style, 'translate-y-0!', 'print:hidden')}
|
||||
>
|
||||
{t(language, copied ? 'code_copied' : 'code_copy')}
|
||||
</button>
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import { t, tString } from '@/intl/translate';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
|
||||
import { useTrackEvent } from '../Insights';
|
||||
import { Button } from '../primitives';
|
||||
import { Button, ButtonGroup } from '../primitives';
|
||||
|
||||
const MAX_COMMENT_LENGTH = 512;
|
||||
|
||||
@@ -61,31 +61,29 @@ export function PageFeedbackForm(props: {
|
||||
<div className={tcls('flex flex-col gap-3 text-sm', className)}>
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<p>{t(languages, 'was_this_helpful')}</p>
|
||||
<div className="rounded-full border border-tint-subtle bg-tint-base contrast-more:border-tint-12">
|
||||
<div className="flex">
|
||||
<RatingButton
|
||||
rating={PageFeedbackRating.Good}
|
||||
label={tString(languages, 'was_this_helpful_positive')}
|
||||
onClick={() => onSubmitRating(PageFeedbackRating.Good)}
|
||||
active={rating === PageFeedbackRating.Good}
|
||||
disabled={rating !== undefined}
|
||||
/>
|
||||
<RatingButton
|
||||
rating={PageFeedbackRating.Ok}
|
||||
label={tString(languages, 'was_this_helpful_neutral')}
|
||||
onClick={() => onSubmitRating(PageFeedbackRating.Ok)}
|
||||
active={rating === PageFeedbackRating.Ok}
|
||||
disabled={rating !== undefined}
|
||||
/>
|
||||
<RatingButton
|
||||
rating={PageFeedbackRating.Bad}
|
||||
label={tString(languages, 'was_this_helpful_negative')}
|
||||
onClick={() => onSubmitRating(PageFeedbackRating.Bad)}
|
||||
active={rating === PageFeedbackRating.Bad}
|
||||
disabled={rating !== undefined}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<ButtonGroup>
|
||||
<RatingButton
|
||||
rating={PageFeedbackRating.Good}
|
||||
label={tString(languages, 'was_this_helpful_positive')}
|
||||
onClick={() => onSubmitRating(PageFeedbackRating.Good)}
|
||||
active={rating === PageFeedbackRating.Good}
|
||||
disabled={rating !== undefined}
|
||||
/>
|
||||
<RatingButton
|
||||
rating={PageFeedbackRating.Ok}
|
||||
label={tString(languages, 'was_this_helpful_neutral')}
|
||||
onClick={() => onSubmitRating(PageFeedbackRating.Ok)}
|
||||
active={rating === PageFeedbackRating.Ok}
|
||||
disabled={rating !== undefined}
|
||||
/>
|
||||
<RatingButton
|
||||
rating={PageFeedbackRating.Bad}
|
||||
label={tString(languages, 'was_this_helpful_negative')}
|
||||
onClick={() => onSubmitRating(PageFeedbackRating.Bad)}
|
||||
active={rating === PageFeedbackRating.Bad}
|
||||
disabled={rating !== undefined}
|
||||
/>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
{rating ? (
|
||||
<div className="flex flex-col gap-2">
|
||||
@@ -148,9 +146,9 @@ function RatingButton(
|
||||
}[rating] ?? null;
|
||||
|
||||
return (
|
||||
<button
|
||||
<Button
|
||||
className={tcls(
|
||||
'p-2 first:rounded-l-full first:pl-2.5 last:rounded-r-full last:pr-2.5 hover:bg-primary-hover hover:text-primary-strong',
|
||||
'border-x-0 bg-tint-base p-2 first:rounded-l-full first:border-l-1 first:pl-2.5 last:rounded-r-full last:border-r-1 last:pr-2.5 hover:bg-primary-hover hover:text-primary-strong',
|
||||
'disabled:cursor-not-allowed disabled:hover:bg-inherit disabled:hover:text-inherit dark:disabled:hover:text-inherit',
|
||||
'ring-tint contrast-more:hover:ring-1',
|
||||
active
|
||||
@@ -158,13 +156,13 @@ function RatingButton(
|
||||
: 'disabled:opacity-7 contrast-more:disabled:ring-0'
|
||||
)}
|
||||
type="button"
|
||||
{...attr}
|
||||
aria-label={label}
|
||||
title={label}
|
||||
variant="secondary"
|
||||
label={label}
|
||||
onClick={onClick}
|
||||
>
|
||||
{ratingIcon}
|
||||
</button>
|
||||
iconOnly
|
||||
{...attr}
|
||||
icon={ratingIcon}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import type { GitBookSiteContext } from '@/lib/context';
|
||||
import { CustomizationAIMode, CustomizationHeaderPreset } from '@gitbook/api';
|
||||
import {
|
||||
CustomizationAIMode,
|
||||
CustomizationHeaderPreset,
|
||||
CustomizationSearchStyle,
|
||||
} from '@gitbook/api';
|
||||
import React from 'react';
|
||||
|
||||
import { Footer } from '@/components/Footer';
|
||||
@@ -62,7 +66,13 @@ export function SpaceLayout(props: {
|
||||
/>
|
||||
</React.Suspense>
|
||||
{aiMode === CustomizationAIMode.Assistant ? (
|
||||
<AIChatButton trademark={customization.trademark.enabled} />
|
||||
<AIChatButton
|
||||
withLabel={
|
||||
withTopHeader &&
|
||||
customization.styling.search === CustomizationSearchStyle.Prominent
|
||||
}
|
||||
trademark={customization.trademark.enabled}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
'use client';
|
||||
|
||||
import { Icon, type IconName } from '@gitbook/icons';
|
||||
import type { IconName } from '@gitbook/icons';
|
||||
import { useTheme } from 'next-themes';
|
||||
import React from 'react';
|
||||
|
||||
import { tString, useLanguage } from '@/intl/client';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
import { Button, ButtonGroup } from '../primitives';
|
||||
|
||||
type ThemeMode = 'light' | 'system' | 'dark';
|
||||
|
||||
@@ -27,7 +27,7 @@ export function ThemeToggler() {
|
||||
};
|
||||
|
||||
return (
|
||||
<div role="radiogroup" className="flex flex-row gap-2">
|
||||
<ButtonGroup role="radiogroup" className="gap-2" combinedShape={false}>
|
||||
<ThemeButton
|
||||
active={mounted && theme === 'light'}
|
||||
icon="sun-bright"
|
||||
@@ -46,7 +46,7 @@ export function ThemeToggler() {
|
||||
onClick={() => onSwitchMode('dark')}
|
||||
title={tString(language, 'switch_to_dark_theme')}
|
||||
/>
|
||||
</div>
|
||||
</ButtonGroup>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -58,38 +58,21 @@ function ThemeButton(props: {
|
||||
}) {
|
||||
const { icon, onClick, title, active } = props;
|
||||
return (
|
||||
<button
|
||||
<Button
|
||||
type="button"
|
||||
role="radio"
|
||||
onClick={onClick}
|
||||
aria-label={title}
|
||||
title={title}
|
||||
label={title}
|
||||
aria-checked={active}
|
||||
className={tcls(
|
||||
'p-2',
|
||||
'rounded-sm',
|
||||
'straight-corners:rounded-none',
|
||||
'circular-corners:rounded-full',
|
||||
'transition-all',
|
||||
'text-tint',
|
||||
'contrast-more:text-tint-strong',
|
||||
'hover:bg-tint-hover',
|
||||
'contrast-more:hover:ring-2',
|
||||
'contrast-more:focus:ring-2',
|
||||
'ring-tint',
|
||||
active && [
|
||||
'bg-primary',
|
||||
'theme-muted:bg-primary-hover',
|
||||
'[html.sidebar-filled.theme-bold.tint_&]:bg-primary-hover',
|
||||
'hover:bg-primary',
|
||||
'text-primary-strong',
|
||||
'contrast-more:text-primary-strong',
|
||||
'contrast-more:ring-1',
|
||||
'ring-primary',
|
||||
]
|
||||
)}
|
||||
>
|
||||
<Icon icon={icon} className={tcls('size-4')} />
|
||||
</button>
|
||||
variant="blank"
|
||||
size="default"
|
||||
className={
|
||||
active
|
||||
? 'bg-primary theme-muted:bg-primary-hover text-primary-strong ring-primary hover:bg-primary contrast-more:text-primary-strong contrast-more:ring-1 [html.sidebar-filled.theme-bold.tint_&]:bg-primary-hover'
|
||||
: ''
|
||||
}
|
||||
icon={icon}
|
||||
iconOnly
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -135,7 +135,7 @@ export const Button = React.forwardRef<
|
||||
icon
|
||||
)
|
||||
) : null}
|
||||
{iconOnly ? null : label}
|
||||
{iconOnly ? null : (children ?? label)}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -171,7 +171,7 @@ export const Button = React.forwardRef<
|
||||
</button>
|
||||
);
|
||||
|
||||
return iconOnly && label ? (
|
||||
return (children || iconOnly) && label ? (
|
||||
<Tooltip
|
||||
rootProps={{ open: disabled === true ? false : undefined }}
|
||||
label={label}
|
||||
@@ -185,14 +185,21 @@ export const Button = React.forwardRef<
|
||||
}
|
||||
);
|
||||
|
||||
export const ButtonGroup = React.forwardRef<HTMLDivElement, ButtonProps>(({ children }, ref) => {
|
||||
export const ButtonGroup = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
ButtonProps & { combinedShape?: boolean }
|
||||
>(({ children, className, combinedShape = true, ...rest }, ref) => {
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={tcls(
|
||||
'flex h-fit items-stretch justify-start *:translate-y-0! *:shadow-none!',
|
||||
'[&>*:not(:first-child)]:border-l-0 [&>*:not(:first-child,:last-child)]:rounded-none [&>*:not(:only-child):first-child]:rounded-r-none [&>*:not(:only-child):last-child]:rounded-l-none'
|
||||
'flex h-fit items-stretch justify-start',
|
||||
combinedShape
|
||||
? '*:translate-y-0! *:shadow-none! [&>*:not(:first-child)]:border-l-0 [&>*:not(:first-child,:last-child)]:rounded-none [&>*:not(:only-child):first-child]:rounded-r-none [&>*:not(:only-child):last-child]:rounded-l-none'
|
||||
: '',
|
||||
className
|
||||
)}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
|
||||
@@ -17,7 +17,7 @@ export const ButtonStyles = [
|
||||
'depth-subtle:shadow-xs',
|
||||
'hover:depth-subtle:shadow-md',
|
||||
'focus-visible:depth-subtle:shadow-md',
|
||||
'active:shadow-none',
|
||||
'active:depth-subtle:shadow-xs',
|
||||
'shadow-tint/6',
|
||||
'dark:shadow-tint-1',
|
||||
|
||||
@@ -27,6 +27,7 @@ export const ButtonStyles = [
|
||||
|
||||
'hover:depth-subtle:-translate-y-px',
|
||||
'focus-visible:depth-subtle:-translate-y-px',
|
||||
'active:depth-subtle:translate-y-0',
|
||||
'transition-all',
|
||||
|
||||
'grow-0',
|
||||
|
||||
Reference in New Issue
Block a user