Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Toolbar] Add Toolbar components #1349

Draft
wants to merge 15 commits into
base: master
Choose a base branch
from
Prev Previous commit
Next Next commit
Make ToggleGroup work in Toolbar
  • Loading branch information
mj12albert committed Feb 6, 2025
commit 75f8b42e8bc89abaf3c62cb0e51d5805728722ab
93 changes: 93 additions & 0 deletions docs/src/app/(private)/experiments/toolbar.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
.Root {
display: flex;
gap: 0.5rem;
border: 1px solid var(--color-gray-200);
border-radius: 0.375rem;
background-color: var(--color-gray-50);
padding: 0.125rem;
text-wrap: nowrap;
}

.Separator {
width: 1px;
margin-block: 2px;
background-color: var(--color-gray-300);
}

.Link {
font-size: 0.875rem;
line-height: 1.25rem;
color: var(--color-gray-900);
text-decoration-color: var(--color-gray-400);
text-decoration-thickness: 1px;
text-decoration-line: none;
text-underline-offset: 2px;

@media (hover: hover) {
&:hover {
text-decoration-line: underline;
}
}

&:focus-visible {
border-radius: 0.125rem;
outline: 2px solid var(--color-blue);
text-decoration-line: none;
}
}

/* ToggleGroup */
.ToggleGroup {
display: flex;
gap: 1px;
border-radius: 0.375rem;
padding: 0.125rem;
}

.Toggle {
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
width: 2rem;
height: 2rem;
padding: 0;
margin: 0;
outline: 0;
border: 0;
border-radius: 0.25rem;
background-color: transparent;
color: var(--color-gray-600);
user-select: none;

&:focus-visible {
background-color: transparent;
outline: 2px solid var(--color-blue);
outline-offset: -1px;
}

@media (hover: hover) {
&:hover {
background-color: var(--color-gray-100);
}
}

&:active {
background-color: var(--color-gray-200);
}

&[data-pressed] {
background-color: var(--color-gray-100);
color: var(--color-gray-900);
}
}

.Icon {
width: 1rem;
height: 1rem;
fill: none;
stroke: currentColor;
stroke-width: 2;
stroke-linecap: round;
stroke-linejoin: round;
}
136 changes: 136 additions & 0 deletions docs/src/app/(private)/experiments/toolbar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
'use client';
import * as React from 'react';
import { Toolbar } from '@base-ui-components/react/toolbar';
import { Toggle } from '@base-ui-components/react/toggle';
import { ToggleGroup } from '@base-ui-components/react/toggle-group';
import s from './toolbar.module.css';
import '../../../demo-theme.css';

export default function App() {
return (
<Toolbar.Root className={s.Root}>
<ToggleGroup defaultValue={[]} className={s.ToggleGroup}>
<Toggle aria-label="Bold" value="bold" className={s.Toggle}>
<BoldIcon className={s.Icon} />
</Toggle>
<Toggle aria-label="Italics" value="italics" className={s.Toggle}>
<ItalicsIcon className={s.Icon} />
</Toggle>
<Toggle aria-label="Underline" value="underline" className={s.Toggle}>
<UnderlineIcon className={s.Icon} />
</Toggle>
</ToggleGroup>

<Toolbar.Separator className={s.Separator} />

<ToggleGroup defaultValue={['left']} className={s.ToggleGroup}>
<Toggle aria-label="Align left" value="left" className={s.Toggle}>
<AlignLeftIcon className={s.Icon} />
</Toggle>
<Toggle aria-label="Align center" value="center" className={s.Toggle}>
<AlignCenterIcon className={s.Icon} />
</Toggle>
<Toggle aria-label="Align right" value="right" className={s.Toggle}>
<AlignRightIcon className={s.Icon} />
</Toggle>
</ToggleGroup>
</Toolbar.Root>
);
}

function AlignLeftIcon(props: React.ComponentProps<'svg'>) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
{...props}
>
<line x1="17" y1="10" x2="3" y2="10" />
<line x1="21" y1="6" x2="3" y2="6" />
<line x1="21" y1="14" x2="3" y2="14" />
<line x1="17" y1="18" x2="3" y2="18" />
</svg>
);
}

function AlignCenterIcon(props: React.ComponentProps<'svg'>) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
{...props}
>
<line x1="18" y1="10" x2="6" y2="10" />
<line x1="21" y1="6" x2="3" y2="6" />
<line x1="21" y1="14" x2="3" y2="14" />
<line x1="18" y1="18" x2="6" y2="18" />
</svg>
);
}

function AlignRightIcon(props: React.ComponentProps<'svg'>) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
{...props}
>
<line x1="21" y1="10" x2="7" y2="10" />
<line x1="21" y1="6" x2="3" y2="6" />
<line x1="21" y1="14" x2="3" y2="14" />
<line x1="21" y1="18" x2="7" y2="18" />
</svg>
);
}

function BoldIcon(props: React.ComponentProps<'svg'>) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
{...props}
>
<path d="M6 4h8a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z" />
<path d="M6 12h9a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z" />
</svg>
);
}

function ItalicsIcon(props: React.ComponentProps<'svg'>) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
{...props}
>
<line x1="19" y1="4" x2="10" y2="4" />
<line x1="14" y1="20" x2="5" y2="20" />
<line x1="15" y1="4" x2="9" y2="20" />
</svg>
);
}

function UnderlineIcon(props: React.ComponentProps<'svg'>) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
{...props}
>
<path d="M6 3v7a6 6 0 0 0 6 6 6 6 0 0 0 6-6V3" />
<line x1="4" y1="21" x2="20" y2="21" />
</svg>
);
}
9 changes: 8 additions & 1 deletion packages/react/src/toggle-group/ToggleGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { useComponentRenderer } from '../utils/useComponentRenderer';
import type { BaseUIComponentProps } from '../utils/types';
import { CompositeRoot } from '../composite/root/CompositeRoot';
import { useDirection } from '../direction-provider/DirectionContext';
import { useToolbarRootContext } from '../toolbar/root/ToolbarRootContext';
import { useToggleGroup, type UseToggleGroup } from './useToggleGroup';
import { ToggleGroupContext } from './ToggleGroupContext';

Expand Down Expand Up @@ -42,6 +43,8 @@ const ToggleGroup = React.forwardRef(function ToggleGroup(

const direction = useDirection();

const toolbarContext = useToolbarRootContext(true);

const defaultValue = React.useMemo(() => {
if (valueProp === undefined) {
return defaultValueProp ?? [];
Expand Down Expand Up @@ -90,7 +93,11 @@ const ToggleGroup = React.forwardRef(function ToggleGroup(

return (
<ToggleGroupContext.Provider value={contextValue}>
<CompositeRoot direction={direction} loop={loop} render={renderElement()} />
{toolbarContext ? (
renderElement()
) : (
<CompositeRoot direction={direction} loop={loop} render={renderElement()} />
)}
Comment on lines +96 to +100
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it a bit sketchy to need a component to have to check another somewhat-arbitrary component's context?

</ToggleGroupContext.Provider>
);
});
Expand Down