Skip to content

Commit

Permalink
playground: Add tab menu and format button
Browse files Browse the repository at this point in the history
- Change tab dropdown to menu so we can have more than 1 type of action
- Remove tab-dropdown specific properties since we don't need them
anymore.
- Add option to the menu component so we can disable the overlay
- Add menu-tab specific styling
- Render tab menu with optional title and optional format button: when
both languages & actions are present both menus show a title to clarify
the difference. If only one of them is configured the title is not shown
 and only the items are shown.
- Handle events for language option selection: close menu & call change
option function
- Handle format button click: close menu & call format function
- Fix issue onLoad which always loaded the default-example, even though
workspace policy was selected in the url.
- TODO: How can we use the WASM API to format the code?

To test:
- Click on the arrow next to the input tab name

- Enable the policy-mode to test language selection in combination with
the format option: src/config/workspaces/policy.ts > set enabled to true
 and click on the input dropdown.

Fixes: cue-lang/cue#2996

Closes #482 as merged as of commit f3ec99d4e.

Signed-off-by: Jorinde Reijnierse <[email protected]>
Change-Id: I5a2fe31cc69b7f3cb8313ad94f27f172c2b7ffd4
  • Loading branch information
JorindeUsMedia authored and jpluscplusm committed Dec 5, 2024
1 parent 597542b commit 42294bb
Show file tree
Hide file tree
Showing 8 changed files with 328 additions and 78 deletions.
4 changes: 3 additions & 1 deletion playground/src/components/menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ interface MenuProps {
id: string;
icon?: string;
open?: boolean;
showOverlay?: boolean;
title: string;
type?: 'default' | 'icon' | 'icon-mobile';
onOpen?: () => void;
Expand Down Expand Up @@ -106,7 +107,7 @@ export class Menu extends React.Component<PropsWithChildren<MenuProps>, MenuStat
</div>
}

{ this.state.open &&
{ (this.state.open && this.props.showOverlay !== false) &&
<div className="cue-menu__overlay" ref={ this.overlayRef }></div>
}
</div>
Expand All @@ -132,6 +133,7 @@ export class Menu extends React.Component<PropsWithChildren<MenuProps>, MenuStat

private onToggleClick(event: MouseEvent) {
event.preventDefault();

if (this.state.open) {
this.hide();
} else {
Expand Down
15 changes: 11 additions & 4 deletions playground/src/components/tab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,21 @@ export interface TabProps {
activeItem: DropdownItem;
children: ReactNode;
disabled?: boolean;
disabledText?: string;
groupId: string;
items: DropdownItem[];
name: string;
onDropdownSelect?: {(change: DropdownChange): void};
open?: boolean;
readonly ?: boolean;
readonly?: boolean;
type?: string;
menu?: TabMenu;
onOptionSelect?: {(change: DropdownChange): void};
onFormatClick?: { (): void }
}

export interface TabMenu {
options: DropdownItem[];
actions: {
format: boolean;
} | false;
}

const defaultProps = {
Expand Down
149 changes: 119 additions & 30 deletions playground/src/components/tabs.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import cx from 'classnames';
import * as React from 'react';
import { JSXElementConstructor, MouseEvent, PropsWithChildren } from 'react';
import cx from 'classnames';
import { Dropdown } from '@components/dropdown';

import { Menu } from '@components/menu';
import { Tab, TabProps } from '@components/tab';
import { DropdownItem } from '@models/dropdown';

interface TabsProps {
activeIndex?: number;
}

interface TabsState {
activeIndex: number;
activeMenu: string | null;
}

export class Tabs extends React.Component<PropsWithChildren<TabsProps>, TabsState> {
Expand All @@ -18,6 +21,7 @@ export class Tabs extends React.Component<PropsWithChildren<TabsProps>, TabsStat

this.state = {
activeIndex: props.activeIndex ?? 0,
activeMenu: null,
};
}

Expand All @@ -26,7 +30,7 @@ export class Tabs extends React.Component<PropsWithChildren<TabsProps>, TabsStat
this.setState({ activeIndex: index });
}

public render () {
public render() {
const tabs: TabProps[] = [];
React.Children.forEach(this.props.children, (child) => {
const type = ((child as React.ReactElement).type as JSXElementConstructor<any>);
Expand All @@ -39,42 +43,108 @@ export class Tabs extends React.Component<PropsWithChildren<TabsProps>, TabsStat
<div className="cue-tabs">
<div className="cue-tabs__nav cue-tabs-nav">
<ul className="cue-tabs-nav__tabs" role="tablist">
{ tabs.map((tab, index) => {
{tabs.map((tab, index) => {
const showMenu = tab.menu && (tab.menu.options.length > 0 || tab.menu.actions);
const isActive = index === this.state.activeIndex;
const tabName = `${ tab.name ? (tab.name + ': ') : ''}${ tab.activeItem.name ?? ''}`;
const tabName = `${tab.name ? (tab.name + ': ') : ''}${tab.activeItem.name ?? ''}`;
const tabClassNames = cx({
'is-active': isActive,
'cue-tabs-nav__tab--output': tab.type === 'output',
}, 'cue-tabs-nav__tab');

const menuItems: React.JSX.Element[] = [];
if (tab.menu?.options?.length > 1) {
for (const menuItem of tab.menu.options) {
const linkClassNames = cx({
'is-active': menuItem.value === tab.activeItem.value
}, 'cue-tab-menu__link cue-tab-menu__link--group');

menuItems.push(
<li className="cue-tab-menu__item" key={menuItem.value}>
{menuItem.link
? <a className={linkClassNames} href={menuItem.link}>{menuItem.name}</a>
: <button className={linkClassNames}
onClick={(e) => this.onOptionClick(e, tab, menuItem)}
>{menuItem.name}</button>
}
</li>
);
}
}

return (
<li className="cue-tabs-nav__item" key={`tab-${ tab.groupId }`}>
{ (isActive && tab.items && tab.items.length > 1) &&
<div className={ tabClassNames }
>
<Dropdown
activeItem={ tab.activeItem }
cssClass="cue-tabs-nav__dropdown cue-dropdown--tab"
readonly={ tab.readonly }
disabled={ tab.disabled }
disabledText={ tab.disabledText }
groupId={ tab.groupId }
items={ tab.items}
name={ tab.name }
onDropdownSelect={ tab.onDropdownSelect }
open={ tab.open }
></Dropdown>
<li className="cue-tabs-nav__item" key={`tab-${tab.groupId}`}>
{(isActive && !tab.readonly && showMenu) &&
<div className={tabClassNames}>
<Menu
id={`${tab.groupId}-${tab.name}`}
cssClass="cue-menu--tab cue-tabs-nav__menu"
title={tabName}
showOverlay={false}
open={this.state.activeMenu === tab.name}
onOpen={() => {
this.setState({ activeMenu: tab.name })
}}
onClose={() => {
this.setState({ activeMenu: null })
}}
>
<>
<div className="cue-tab-menu">
<ul className="cue-tab-menu__list">
{(menuItems.length > 0 && tab.menu.actions) &&
<li className="cue-tab-menu__item" key="language">
<p className="cue-tab-menu__title">Language</p>
<ul className="cue-tab-menu__list">
{menuItems}
</ul>
</li>
}

{(menuItems.length > 0 && !tab.menu.actions) &&
menuItems
}

{(tab.menu.actions && menuItems.length > 0) &&
<li className="cue-tab-menu__item" key="actions">
<p className="cue-tab-menu__title">Actions</p>
<ul className="cue-tab-menu__list">
{(tab.menu.actions.format) &&
<li className="cue-tab-menu__item">
<button className="cue-tab-menu__link"
onClick={(e) => this.onFormatClick(e, tab)}
>Format code
</button>
</li>
}
</ul>
</li>
}


{(tab.menu.actions && tab.menu.actions.format && menuItems.length === 0) &&
<li className="cue-tab-menu__item">
<button className="cue-tab-menu__link"
onClick={(e) => this.onFormatClick(e, tab)}
>Format code
</button>
</li>
}
</ul>
</div>
</>
</Menu>
</div>
}
{ (!isActive || !tab.items || tab.items.length <= 1) &&
{(!isActive || tab.readonly || !showMenu) &&
<button
disabled={ tab.disabled }
className={ cx('cue-tabs-nav__tab', {
disabled={tab.disabled}
className={cx('cue-tabs-nav__tab cue-tabs-nav__tab--button', {
'is-active': isActive,
'cue-tabs-nav__tab--output': tab.type === 'output',
}) }
onClick={ (e) => this.selectTab(e, index) }
><span className="cue-tabs-nav__text">{ tabName }</span></button>
})}
onClick={(e) => this.selectTab(e, index)}
><span className="cue-tabs-nav__text">{tabName}</span></button>
}
</li>
)
Expand All @@ -83,14 +153,33 @@ export class Tabs extends React.Component<PropsWithChildren<TabsProps>, TabsStat
</div>

<div className="cue-tabs__content">
{ tabs.map((tab, index) =>
<div key={`tab-content-${ index }`} className={ cx('cue-tabs__item', {
{tabs.map((tab, index) =>
<div key={`tab-content-${index}`} className={cx('cue-tabs__item', {
'is-active': index === this.state.activeIndex,
}) }>{ tab.children }
})}>{tab.children}
</div>
)}
</div>
</div>
)
}

private onOptionClick(event: MouseEvent, tab: TabProps, option: DropdownItem) {
event.preventDefault();

tab.onOptionSelect({
groupId: tab.groupId,
selected: option,
});

this.setState({ activeMenu: null });
}

private onFormatClick(event: MouseEvent, tab: TabProps) {
event.preventDefault();

tab.onFormatClick();

this.setState({ activeMenu: null });
}
}
Loading

0 comments on commit 42294bb

Please sign in to comment.