Skip to content

Establish import/export UI working area and prototype #1156

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

Merged
merged 2 commits into from
Jul 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 27 additions & 5 deletions src/components/navigation/Tab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ type ComponentProps = {
*/
textContent?: string;
selected?: boolean;
variant?: 'basic';

// Styling API
size?: 'md' | 'custom';
variant?: 'text' | 'tab' | 'custom';
unstyled?: boolean;
};

export type TabProps = PresentationalProps &
Expand All @@ -32,18 +36,35 @@ const Tab = function Tab({
icon: Icon,
textContent,
selected = false,
variant = 'basic',
size = 'md',
variant = 'text',
unstyled = false,

...htmlAttributes
}: TabProps) {
const styled = !unstyled;
const themed = styled && variant !== 'custom';
const sized = styled && size !== 'custom';

return (
<Button
data-component="Tab"
{...htmlAttributes}
classes={classnames(
'gap-x-1.5 enabled:hover:text-brand-dark',
{
'font-bold': selected && variant === 'basic',
// Buttons have a flex layout. Add a horizontal gap.
sized && 'gap-x-1.5',
themed && {
'px-4 py-2': variant === 'tab' && sized,
'font-semibold text-grey-7 rounded-t border border-transparent border-b-0':
variant === 'tab',
'aria-selected:text-color-text aria-selected:bg-white':
variant === 'tab',
'aria-selected:border aria-selected:border-grey-3 aria-selected:border-b-0':
variant === 'tab',
'enabled:hover:text-color-text disabled:text-grey-7/50':
variant === 'tab',
'enabled:hover:text-brand-dark': variant === 'text',
'aria-selected:font-bold': variant === 'text',
},
classes
)}
Expand All @@ -52,6 +73,7 @@ const Tab = function Tab({
role="tab"
variant="custom"
size="custom"
unstyled={unstyled}
>
{Icon && (
<Icon
Expand Down
6 changes: 5 additions & 1 deletion src/components/navigation/test/Tab-test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { mount } from 'enzyme';

import { ProfileIcon } from '../../icons';
import { testPresentationalComponent } from '../../test/common-tests';
import {
testPresentationalComponent,
testStyledComponent,
} from '../../test/common-tests';
import Tab from '../Tab';

const contentFn = (Component, props = {}) => {
Expand All @@ -17,6 +20,7 @@ describe('Tab', () => {
createContent: contentFn,
elementSelector: 'button[data-component="Tab"]',
});
testStyledComponent(Tab);

it('sets `aria-selected` when selected', () => {
const tab1 = contentFn(Tab, { selected: true });
Expand Down
64 changes: 64 additions & 0 deletions src/pattern-library/components/patterns/navigation/TabPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,70 @@ export default function TabPage() {
</Library.Info>
</Library.Example>
</Library.Pattern>

<Library.Pattern title="Styling API">
<p>
<code>Tab</code> accepts the following props from the{' '}
<Library.Link href="/using-components#presentational-components-styling-api">
presentational component styling API
</Library.Link>
.
</p>
<Library.Example title="variant">
<Library.Info>
<Library.InfoItem label="description">
Set to <code>custom</code> to remove theming styles and provide
your own styling with <code>classes</code>.
</Library.InfoItem>
<Library.InfoItem label="type">
<code>{`'text' | 'tab' | 'custom'`}</code>
</Library.InfoItem>
<Library.InfoItem label="default">
<code>{"'text'"}</code>
</Library.InfoItem>
</Library.Info>

<Library.Demo title="variant: 'tab'" withSource>
<div role="tablist" className="flex">
<Tab variant="tab">Share</Tab>
<Tab selected variant="tab">
Import
</Tab>
<Tab variant="tab">Export</Tab>
</div>
</Library.Demo>
</Library.Example>

<Library.Example title="size">
<Library.Info>
<Library.InfoItem label="description">
Set relative internal spacing and padding. Set to{' '}
<code>{`'custom'`}</code> to provide your own sizing styles with{' '}
<code>classes</code>.
</Library.InfoItem>
<Library.InfoItem label="type">
<code>{`'md' | 'custom'`}</code>
</Library.InfoItem>
<Library.InfoItem label="default">
<code>{`'md'`}</code>
</Library.InfoItem>
</Library.Info>
</Library.Example>

<Library.Example title="unstyled">
<Library.Info>
<Library.InfoItem label="description">
Set to remove all styling.
</Library.InfoItem>
<Library.InfoItem label="type">
<code>boolean</code>
</Library.InfoItem>
<Library.InfoItem label="default">
<code>false</code>
</Library.InfoItem>
</Library.Info>
</Library.Example>
</Library.Pattern>
</Library.Section>
<Library.Section
title="TabList"
Expand Down
215 changes: 215 additions & 0 deletions src/pattern-library/components/patterns/prototype/TabbedDialogPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
//import type { ComponentChildren } from 'preact';
import classnames from 'classnames';
import type { JSX } from 'preact';
import { useState } from 'preact/hooks';

import {
Button,
CopyIcon,
IconButton,
Input,
InputGroup,
TabList,
Tab,
CancelIcon,
Card,
CardActions,
CardTitle,
SocialTwitterIcon,
SocialFacebookIcon,
EmailIcon,
} from '../../../../';
import type { PresentationalProps } from '../../../../types';
import Library from '../../Library';
import Dialog from './import-export/Dialog';

type DividerProps = PresentationalProps & {
variant: 'full' | 'center' | 'custom';
} & JSX.HTMLAttributes<HTMLElement>;

function Divider({ variant }: DividerProps) {
return (
<hr
className={classnames('border-t border-px h-px', {
'mx-2': variant === 'center',
})}
/>
);
}

export default function TabbedDialogPage() {
const [panelOpen, setPanelOpen] = useState(false);
const [selectedTab, setSelectedTab] = useState('share');
return (
<Library.Page title="Export/Import Annotations">
<Library.Section title="Tabbed Panel (Dialog) Pattern">
<p>TODO</p>
</Library.Section>
<Library.Section title="Import/Export UI prototyping">
<Library.Callout>
NB: This section is a work in progress.
</Library.Callout>
<Library.Pattern title="Adding Export to the Share Panel">
<Library.Callout>
NB: The disabled <em>Import</em> tab is rendered in the prototyped
dialog here to demonstrate what a disabled tab might look like, but
would not appear to users in this manner.
</Library.Callout>
<Library.Demo>
<div className="w-full bg-grey-2 p-4">
<Button onClick={() => setPanelOpen(!panelOpen)}>
{panelOpen ? 'Close' : 'Show'} share panel
</Button>

<div className="w-[410px] mx-auto text-[13px] leading-normal">
{panelOpen && (
<Dialog
variant="custom"
title="Share annotations"
onClose={() => setPanelOpen(false)}
restoreFocus
>
<div
data-testid="tabbed-header"
className="flex items-center"
>
<TabList classes="grow gap-x-1 -mb-[1px] z-2">
<Tab
aria-controls="share-panel"
variant="tab"
selected={selectedTab === 'share'}
textContent="Share"
onClick={() => setSelectedTab('share')}
>
Share
</Tab>
<Tab
aria-controls="export-panel"
variant="tab"
selected={selectedTab === 'export'}
textContent="Export"
onClick={() => setSelectedTab('export')}
>
Export
</Tab>
<Tab
aria-controls="import-panel"
disabled
variant="tab"
selected={selectedTab === 'import'}
textContent="Import"
onClick={() => setSelectedTab('import')}
>
Import
</Tab>
</TabList>
<IconButton
classes="text-[16px] text-grey-6 hover:text-grey-7 hover:bg-grey-3/50"
title="Close"
icon={CancelIcon}
onClick={() => setPanelOpen(false)}
variant="custom"
size="sm"
/>
</div>
<Card>
<div
id="share-panel"
role="tabpanel"
className={classnames(
'p-3 focus-visible-ring ring-inset',
{
hidden: selectedTab !== 'share',
}
)}
tabIndex={-1}
>
<CardTitle>Share annotations from GroupName</CardTitle>
<div className="space-y-3 pt-2">
<p>
<strong>
Use this link to share these annotations with
anyone:
</strong>
</p>
<InputGroup>
<Input
id="share-annotations-url"
value="https://www.examle.com/fake"
/>
<IconButton
icon={CopyIcon}
variant="dark"
title="copy"
/>
</InputGroup>
<Divider variant="full" />
<ul className="flex flex-row gap-x-4 items-center justify-center text-grey-6">
<li>
<SocialTwitterIcon className="w-6 h-6" />
</li>
<li>
<SocialFacebookIcon className="w-6 h-6" />
</li>
<li>
<EmailIcon className="w-6 h-6" />
</li>
</ul>
</div>
</div>
<div
id="export-panel"
role="tabpanel"
tabIndex={-1}
className={classnames(
'p-3 focus-visible-ring ring-inset',
{
hidden: selectedTab !== 'export',
}
)}
>
<CardTitle>Export from GroupName</CardTitle>
<div className="space-y-3 pt-2">
<p>
<strong>2 annotations</strong> will be exported with
the following file name:
</p>
<Input
id="export-filename"
value="2023-07-10-hypothesis-export"
/>
<CardActions>
<Button variant="primary">Export</Button>
</CardActions>
</div>
</div>
<div
id="import-panel"
role="tabpanel"
tabIndex={-1}
className={classnames(
'p-3 focus-visible-ring ring-inset',
{
hidden: selectedTab !== 'import',
}
)}
>
<CardTitle>Import into GroupName</CardTitle>
<div className="mt-2 space-y-3">
<p>
TODO: We will mock this up when we work on the
import part of this project.
</p>
</div>
</div>
</Card>
</Dialog>
)}
</div>
</div>
</Library.Demo>
</Library.Pattern>
</Library.Section>
</Library.Page>
);
}
Loading