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

docs: make control tabs linkable #1423

Merged
merged 8 commits into from
Aug 22, 2024
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
8 changes: 4 additions & 4 deletions packages/documentation/docs/controls/_avatar_code.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import Events from './../auto-generated/ix-avatar/events.md';

import Playground from '@site/src/components/PlaygroundV2';

## Examples
## Development

### Basic

Expand Down Expand Up @@ -37,12 +37,12 @@ You can also add the avatar to the header, which will turn it into a clickable b
height="21rem">
</Playground>

## API
### API

### Properties
#### Properties

<Props />

### Events
#### Events

<Events />
24 changes: 13 additions & 11 deletions packages/documentation/docs/controls/_avatar_styleguide.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
Avatars are visual or textual representations of individual identities and they are most often used to represent users logged into a system. Identity providers or user management systems usually provide identity information, and the amount of information provided varies from system to system. The avatar component offers different options to handle this.
## Guidelines

Avatars are visual or textual representations of individual identities, most often used to represent users logged into a system. Identity providers or user management systems usually provide identity information, and the amount of information provided varies from system to system. The avatar component offers different options to handle this.

## Options
### Options

![Avatar overview](https://www.figma.com/design/wEptRgAezDU1z80Cn3eZ0o/iX-Pattern-Illustrations?type=design&node-id=963-565&mode=design&t=M9CowfOcGyqnSycV-4)

| Option | Description and usage |
| -------------------------- | ------------------------------------------------------------------------------------------------------------ |
| Defaultย (1) | Without any set option the visual is just a predefined placeholder graphic, it can be used when identity information is unavailable or cannot be used for other reasons.|
| Initialsย (2) | Shows a string of one or two characters, can be used when only textual information is available. Examples: a userโ€™s initials (JD for John Doe), the first character from the username (J for johndoe)|
| Imageย (3) | Shows an image, can be used when identity information includes an image|

## Behavior
| Option | Description and usage |
| ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Defaultย (1) | Without any option set, the visual is just a predefined placeholder graphic. It can be used when identity information is unavailable or cannot be used for other reasons. |
| Initialsย (2) | Shows a string of one or two characters. Can be used when only textual information is available. Examples: A userโ€™s initials (JD for John Doe) or the first character from the username (J for johndoe) |
| Imageย (3) | Shows an image. Can be used when identity information includes an image |

### Behavior

The avatar is a display-only component with no further interactions. Images provided are proportionally scaled to fill the content. A circle shape clips the image. All image formats that browser engines support can be used.

## Dos and Donโ€™ts
### Dos and Donโ€™ts

![Avatar dos and Donโ€˜ts](https://www.figma.com/design/wEptRgAezDU1z80Cn3eZ0o/iX-Pattern-Illustrations?type=design&node-id=975-13&mode=design&t=SxUA6AcHswBAiIzi-4)
![Avatar dos and donโ€˜ts](https://www.figma.com/design/wEptRgAezDU1z80Cn3eZ0o/iX-Pattern-Illustrations?type=design&node-id=975-13&mode=design&t=SxUA6AcHswBAiIzi-4)

- Don't use more than 2 characters when using the "Initials" option
7 changes: 5 additions & 2 deletions packages/documentation/docs/controls/avatar.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import DocsTabs from '@site/src/components/DocsTabs';
import LinkableDocsTabs from '@site/src/components/LinkableDocsTabs';

import DocsUx from './\_avatar_styleguide.md'
import DocsCode from './\_avatar_code.md'
Expand All @@ -12,4 +12,7 @@ import Tags from './../auto-generated/ix-avatar/tags.md';
<br/>
<br/>

<DocsTabs styleguide={DocsUx} code={DocsCode} />
<LinkableDocsTabs>
<DocsUx />
<DocsCode />
</LinkableDocsTabs>
2 changes: 2 additions & 0 deletions packages/documentation/docs/controls/messagebar.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import Events from './../auto-generated/ix-message-bar/events.md';

import Playground from '@site/src/components/PlaygroundV2';

# Message-bar

## Examples

<Playground
Expand Down
2 changes: 1 addition & 1 deletion packages/documentation/src/components/DocsTabs/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ export default (props) => {
return <BrowserOnly>{() => <DocsTabs {...props} />}</BrowserOnly>;
};

function DocTab(
export function DocTab(
props: React.PropsWithChildren<{
name: string;
active: boolean;
Expand Down
41 changes: 41 additions & 0 deletions packages/documentation/src/components/LinkableDocsTabs/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* SPDX-FileCopyrightText: 2024 Siemens AG
*
* SPDX-License-Identifier: MIT
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import { iconCode, iconNavigation } from '@siemens/ix-icons/icons';

export const docsTabQueryString = 'current-tab';
export const guidelinesTabValue = 'guidelines';
export const developmentTabValue = 'development';

export default function LinkableDocsTabs(props: { children: [any, any] }) {
return (
<Tabs queryString={docsTabQueryString}>
<TabItem
value={guidelinesTabValue}
label="Guidelines"
attributes={{
icon: iconNavigation,
}}
>
{props.children[0]}
</TabItem>

<TabItem
value={developmentTabValue}
label="Development"
attributes={{
icon: iconCode,
}}
>
{props.children[1]}
</TabItem>
</Tabs>
);
}
43 changes: 43 additions & 0 deletions packages/documentation/src/css/custom.scss
Original file line number Diff line number Diff line change
Expand Up @@ -415,3 +415,46 @@ p > img {
.Accordion__Last {
margin-block-end: 2rem;
}

.tabs__item {
display: flex;
justify-content: center;
align-items: center;
flex-grow: 1;
background-color: var(--theme-color-component-1);
border-radius: 0px;
color: var(--theme-color-std-text);
height: 2.75rem;
max-height: 2.75rem;
padding: 0;
border: none;

&:hover {
background-color: var(--theme-color-component-1);
}
}

.tabs__item--active {
background-color: var(--theme-color-dynamic);
color: var(--theme-color-primary--contrast);
border: none;

&:hover {
background-color: var(--theme-color-dynamic);
}
}

[id]::before {
content: '';
display: block;
height: 1rem;
margin-top: -1rem;
visibility: hidden;
}

// Hide the development and guidelines sections because they are shown via tabs
h2#development,
h2#guidelines {
visibility: hidden;
height: 0px;
}
25 changes: 25 additions & 0 deletions packages/documentation/src/theme/DocsRoot/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React, { useLayoutEffect } from 'react';
import DocsRoot from '@theme-original/DocsRoot';
import type DocsRootType from '@theme/DocsRoot';
import type { WrapperProps } from '@docusaurus/types';

type Props = WrapperProps<typeof DocsRootType>;

export default function DocsRootWrapper(props: Props): JSX.Element {
const { history, location } = props;

useLayoutEffect(() => {
setTimeout(() => {
history.push({
search: location.search,
hash: location.hash,
});
});
}, [location.search, location.hash]);

return (
<>
<DocsRoot {...props} />
</>
);
}
70 changes: 70 additions & 0 deletions packages/documentation/src/theme/Heading/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import React from 'react';
import clsx from 'clsx';
import { translate } from '@docusaurus/Translate';
import { useThemeConfig } from '@docusaurus/theme-common';
import Link from '@docusaurus/Link';
import useBrokenLinks from '@docusaurus/useBrokenLinks';
import type { Props } from '@theme/Heading';

import styles from './styles.module.css';
import { useLocation } from '@docusaurus/router';
import { docsTabQueryString } from '@site/src/components/LinkableDocsTabs';

export default function Heading({ as: As, id, ...props }: Props): JSX.Element {
const location = useLocation();
const brokenLinks = useBrokenLinks();
const {
navbar: { hideOnScroll },
} = useThemeConfig();

const searchParams = new URLSearchParams(location.search);
const currentTab = searchParams.get(docsTabQueryString);

// H1 headings do not need an id because they don't appear in the TOC.
if (As === 'h1' || !id) {
return <As {...props} id={undefined} />;
}

brokenLinks.collectAnchor(id);

const anchorTitle = translate(
{
id: 'theme.common.headingLinkTitle',
message: 'Direct link to {heading}',
description: 'Title for link to heading',
},
{
heading: typeof props.children === 'string' ? props.children : id,
}
);

let link = `#${id}`;

if (currentTab === 'development' || currentTab === 'guidelines') {
link = `?${docsTabQueryString}=${currentTab}${link}`;
}

return (
<As
{...props}
className={clsx(
'anchor',
hideOnScroll
? styles.anchorWithHideOnScrollNavbar
: styles.anchorWithStickyNavbar,
props.className
)}
id={id}
>
{props.children}
<Link
className="hash-link"
to={link}
aria-label={anchorTitle}
title={anchorTitle}
>
&#8203;
</Link>
</As>
);
}
28 changes: 28 additions & 0 deletions packages/documentation/src/theme/Heading/styles.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
When the navbar is sticky, ensure that on anchor click,
the browser does not scroll that anchor behind the navbar
See https://twitter.com/JoshWComeau/status/1332015868725891076
*/
.anchorWithStickyNavbar {
scroll-margin-top: calc(var(--ifm-navbar-height) + 0.5rem);
}

.anchorWithHideOnScrollNavbar {
scroll-margin-top: 0.5rem;
}

:global(.hash-link) {
opacity: 0;
padding-left: 0.5rem;
transition: opacity var(--ifm-transition-fast);
user-select: none;
}

:global(.hash-link::before) {
content: '#';
}

:global(.hash-link:focus),
:global(*:hover > .hash-link) {
opacity: 1;
}
79 changes: 79 additions & 0 deletions packages/documentation/src/theme/TOCItems/Tree.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import React, { useMemo } from 'react';
import Link from '@docusaurus/Link';
import type { Props } from '@theme/TOCItems/Tree';
import type { TOCTreeNode } from '@docusaurus/theme-common/internal';
import { useLocation } from '@docusaurus/router';
import {
developmentTabValue,
docsTabQueryString,
guidelinesTabValue,
} from '@site/src/components/LinkableDocsTabs';

type IxProps = Props & {
parentTocItems: readonly TOCTreeNode[];
};

// Recursive component rendering the toc tree
function TOCItemTree({
toc,
className,
linkClassName,
isChild,
parentTocItems,
}: IxProps): JSX.Element | null {
const _location = useLocation();
if (!toc.length) {
return null;
}

return (
<ul className={isChild ? undefined : className}>
{toc.map((heading) => {
const searchParams = new URLSearchParams(_location.search);

let tabId = '';
parentTocItems?.forEach((tab) => {
tab.children.forEach((child) => {
if (child.id === heading.id) {
searchParams.set(docsTabQueryString, tab.id);
tabId = tab.id;
}
});
});

if (tabId === '' && !parentTocItems) {
const tab = toc.find(
(firstLevelHeading) => firstLevelHeading.id === heading.id
);
tabId = tab?.id ?? '';
}

let queryParam = '';
if (tabId === guidelinesTabValue || tabId === developmentTabValue) {
queryParam = `?${docsTabQueryString}=${tabId}`;
}

return (
<li key={heading.id}>
<Link
to={`${queryParam}#${heading.id}`}
className={linkClassName ?? undefined}
// Developer provided the HTML, so assume it's safe.
dangerouslySetInnerHTML={{ __html: heading.value }}
/>
<TOCItemTree
isChild
toc={heading.children}
parentTocItems={isChild ? parentTocItems : toc}
className={className}
linkClassName={linkClassName}
/>
</li>
);
})}
</ul>
);
}

// Memo only the tree root is enough
export default React.memo(TOCItemTree);
Loading
Loading