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

Improve API documentation of banner, incubating, and spright components #2141

Merged
merged 12 commits into from
May 31, 2024
18 changes: 12 additions & 6 deletions packages/storybook/src/nimble/banner/banner.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,25 @@ import * as bannerStories from './banner.stories';

The banner is a component used to display a persistent notification to a user.

Banners span the full width of their parent element, and that parent should span the full width of the page/panel.
Banner messages should be limited to approximately three lines of text under normal display size at the intended
location. Multiple banners may be stacked vertically in order of age, with the newest at the top. Stacked banners
should be spaced apart using the <code>{bannerGapSize.cssCustomProperty}</code> design token.

<Canvas of={bannerStories._banner} />

## API

<Controls of={bannerStories._banner} />
<ComponentApisLink />

{/* ## Styling */}
## Styling

### Sizing

Banners span the full width of their parent element, and that parent should span the full width of the page/panel.
Banner messages should be limited to approximately three lines of text under normal display size at the intended
location.

### Multiple banners

Multiple banners may be stacked vertically in order of age, with the newest at the top. Stacked banners
should be spaced apart using the <code>{bannerGapSize.cssCustomProperty}</code> design token.

{/* ## Usage */}

Expand Down
25 changes: 17 additions & 8 deletions packages/storybook/src/nimble/banner/banner.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
LabelUserArgs,
addLabelUseMetadata
} from '../label-provider/base/label-user-stories-utils';
import { createUserSelectedThemeStory } from '../../utilities/storybook';
import { apiCategory, createUserSelectedThemeStory } from '../../utilities/storybook';

// eslint-disable-next-line @typescript-eslint/naming-convention
const ActionType = {
Expand Down Expand Up @@ -79,40 +79,49 @@ export const _banner: StoryObj<BannerArgs> = {
name: 'Banner',
argTypes: {
open: {
description: 'Controls whether the banner is visible.'
description: 'Controls whether the banner is visible.',
table: { category: apiCategory.attributes },
},
title: {
description:
'Primary text which is displayed when `title-hidden` is not set. Banners should **always include a title** even when `title-hidden` is set. The title is used to provide an accessible name to assistive technologies regardless of the value of `title-hidden`.<br><br>Provide the title in an `inline` element such as `<span>` that is targeted to the `title` slot.'
'Primary text which is displayed when `title-hidden` is not set. Banners should **always include a title** even when `title-hidden` is set. The title is used to provide an accessible name to assistive technologies regardless of the value of `title-hidden`.<br><br>Provide the title in an `inline` element such as `<span>`',
table: { category: apiCategory.slots },
},
text: {
name: 'default',
description:
'Secondary text that provides a detailed message. This text should be short enough to fit within three lines when displayed.<br><br>Provide this text as content of the banner element (uses the default slot).'
'Secondary text that provides a detailed message. This text should be short enough to fit within three lines when displayed.',
table: { category: apiCategory.slots },
},
severity: {
options: Object.keys(BannerSeverity),
control: { type: 'radio' },
description:
`Severity of the message presented by the banner. Controls the icon displayed within the banner and, in themes other than \`${Theme.color}\`, controls the background color of the banner. If not set, the banner has a neutral appearance.`
`Severity of the message presented by the banner. Controls the icon displayed within the banner and, in themes other than \`${Theme.color}\`, controls the background color of the banner. If not set, the banner has a neutral appearance.`,
table: { category: apiCategory.attributes },
},
action: {
options: Object.values(ActionType),
control: { type: 'radio' },
description:
'The `action` slot provides a place to display a button or anchor that you provide. If you provide a button, it should have either the `"ghost"` or `"outline"` appearance and have the `"primary"` appearance variant.'
'Display a button or anchor which the user can click to invoke an additional action. If you provide a button, it should have either the `"ghost"` or `"outline"` appearance and have the `"primary"` appearance variant.',
table: { category: apiCategory.slots },
},
preventDismiss: {
name: 'prevent-dismiss',
description:
'If set, removes the "X" button that dismisses the banner.'
'If set, removes the "X" button that dismisses the banner.',
table: { category: apiCategory.attributes },
},
titleHidden: {
name: 'title-hidden',
description: 'If set, hides the provided title.'
description: 'If set, hides the provided title.',
table: { category: apiCategory.attributes },
},
toggle: {
description:
'Event emitted by the banner when the `open` state changes. The event details include the booleans `oldState` and `newState`.',
table: { category: apiCategory.events },
control: false
}
},
Expand Down
5 changes: 5 additions & 0 deletions packages/storybook/src/nimble/card/card.mdx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Canvas, Meta, Controls, Title } from '@storybook/blocks';
import { NimbleCard } from './card.react';
import ComponentApisLink from '../../docs/component-apis-link.mdx';
import { cardTag } from '../../../../nimble-components/src/card';
import * as cardStories from './card.stories';

Expand All @@ -10,7 +11,11 @@ The <Tag name={cardTag} /> is a container that is designed to contain arbitrary
application. The <Tag name={cardTag} /> is intended for grouping related content.

<Canvas of={cardStories.card} />

## API

<Controls of={cardStories.card} />
<ComponentApisLink />

{/* ## Styling */}

Expand Down
11 changes: 10 additions & 1 deletion packages/storybook/src/nimble/card/card.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ import { numberFieldTag } from '../../../../nimble-components/src/number-field';
import { selectTag } from '../../../../nimble-components/src/select';
import { cardTag } from '../../../../nimble-components/src/card';
import {
apiCategory,
createUserSelectedThemeStory,
disableStorybookZoomTransform,
incubatingWarning
} from '../../utilities/storybook';

interface CardArgs {
title: string;
content: undefined;
}

const metadata: Meta<CardArgs> = {
Expand Down Expand Up @@ -39,7 +41,14 @@ const metadata: Meta<CardArgs> = {
argTypes: {
title: {
description:
'Text displayed as a title inside the card. This text should be short enough to fit within two lines when displayed. Cards should **always include a title**. The title is used to provide an accessible name to assistive technologies.<br><br>Provide the title in an element targeted to the `title` slot.'
'Text displayed as a title inside the card. This text should be short enough to fit within two lines when displayed. Cards should **always include a title**. The title is used to provide an accessible name to assistive technologies.',
table: { category: apiCategory.slots },
},
content: {
name: 'default',
description: 'Content to be displayed inside the card.',
table: { category: apiCategory.slots },
control: false
}
},
args: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export const userMapping: StoryObj = {
name: 'display-name',
description:
'The display name to render for the user, e.g. `Oscar Meyer 🌭`.',
control: false,
table: { category: apiCategory.attributes },
}
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
### Guidance for filtering users

Initially, the client application need not provide any user mapping elements within the children of `nimble-rich-text-mention-users`. It can be empty.
However, if the editor loads with an initial markdown string that contains user mentions, then the client should load those user mapping elements.
This is to map the user IDs in the markdown string converted to a readable user name in the editor. Therefore, it is advisable to keep two lists,
`filteredOptions` for dynamic filtering of users to populate in the @mention popup and `mentionedUsers` for storing the already mentioned users which
should be loaded initially by the client to represent the user names that are stored in markdown. Later, the `mentionedUsers` will be filtered internally
within the Nimble editor or by the client to show the names in the popup whenever texts added after @ in the editor.

The `nimble-rich-text-mention-users` component will emit an event whenever the @ character is entered into the editor. The client can listen to the
`mention-update` event and provide the other initial user lists that are not mentioned already.

It is recommended to sort the usernames in alphabetical order and send at most 20 users list at a time to reduce the number of elements in the DOM
for a single page. This dynamic loading of the user's list will help when there are a lot of @mention instances on a single page. If the users list is
very large, it may cause the page to load slower as the mapping mention element is overflowed in the DOM when there are multiple editors in a single
page or a large number of users in the client organization.

The `mention-update` event will also be triggered when the user types any character after @, containing that text along with the @ character in the
event data. For example, if a user types @ and then adds `a` the event will be emitted with data that includes the value @a. The client can listen
to this event, filter the list of users that includes the names containing the letter `a`, and then dynamically update the `nimble-mapping-mention-user`
element based on the filter data. Subsequently, a maximum of 20 filtered options should be transmitted to the editor.

The `mention-update` event will also be triggered when a user copies a mention node from the viewer/editor and pasting it in the editor. Clients should
read the `getMentionedHrefs()` method to identify the user URL(s) that are being pasted into the editor so that the mapping elements can be sent as a children.
This is necessary because the corresponding name for the newly added user URL(s) is required to render as username(s) in the components so the client
should send the mapping elements for all mentioned users.

The above event triggers for every key down event like adding/removing texts and moving the text cursor after the @ character. Updating users is quite an
expensive operation to perform for every keystroke so it is advisable to debounce the events if you're using network requests to perform the filtering
operations. For example, allow at most one request per second to filter the list for each second instead of for each keystrokes.

Note: The editor will also perform the same filtering once again to ensure the filtered options are proper and update the dropdown list in the UI.
This helps to filter the list, regardless of whether the client is loading the list dynamically by listening to the event as mentioned above or statically
providing user details at the start via `nimble-mapping-mention`.
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { html } from '@microsoft/fast-element';
import type { Meta, StoryObj } from '@storybook/html';
import { createUserSelectedThemeStory } from '../../../utilities/storybook';
import { richTextMentionUsersTag } from '@ni/nimble-components/src/rich-text-mention/users';
import { mappingUserTag } from '@ni/nimble-components/src/mapping/user';
import { mappingTextTag } from '@ni/nimble-components/src/mapping/text';
import { hiddenWrapper } from '../../../utilities/hidden';
import { apiCategory, checkValidityDescription, createUserSelectedThemeStory } from '../../../utilities/storybook';

const patternDescription = `A regex used for detecting, validating, and extracting information from mentions in the rich text markdown string.

Expand All @@ -15,7 +18,7 @@ The mention view will be rendered in the following ways based on specific inputs
const mappingUserValidityDescription = `Readonly object of boolean values that represents the validity states that the mention's configuration can be in.
The object's type is \`RichTextMentionValidity\`, and it contains the following boolean properties:

- \`unsupportedMappingType\`: \`true\` when the mention contains a mapping element other than \`nimble-mapping-user\`
- \`unsupportedMappingType\`: \`true\` when the mention contains a mapping element other than \`${mappingUserTag}\`
- \`duplicateMappingMentionHref\`: \`true\` when multiple mappings have the same \`key\` value
- \`missingMentionHrefValue\`: \`true\` when a mapping has no \`key\` value
- \`mentionHrefNotValidUrl\`: \`true\` when any one of the \`key\` is not a valid URL i.e. throws error if \`new URL(key)\`
Expand All @@ -25,7 +28,7 @@ The object's type is \`RichTextMentionValidity\`, and it contains the following
- \`missingDisplayNameValue\`: \`true\` when a mapping has no \`display-name\` value
`;

const mentionUpdateEventDescription = `For the editor, This event will be emitted on following action:
const mentionUpdateEventDescription = `Event emitted on following actions:

- Whenever the \`@\` character is entered into the editor
- When the user types any character after \`@\` into the editor
Expand All @@ -42,7 +45,9 @@ const metadata: Meta = {
docs: {
description: {
component:
'Add a `nimble-rich-text-mention-users` element as a child of the rich text components to enable support for `@mention`. Add `nimble-mapping-mention-user` elements as its children to specify the users available to be mentioned.'
`Add a \`${richTextMentionUsersTag}\` element as a child of the rich text components to enable support for \`@mention\`. Add \`${mappingUserTag}\` elements as its children to specify the users available to be mentioned.

These components facilitate the parsing of input markdown into a mention node that displays a user's name. For the editor component they are also used to populate the list of user names in the mention dropdown.`
}
}
}
Expand All @@ -55,24 +60,38 @@ export const richTextMentionUsers: StoryObj = {
argTypes: {
pattern: {
description: patternDescription,
control: false
control: false,
table: { category: apiCategory.attributes },
},
checkValidity: {
name: 'checkValidity()',
description:
'Returns `true` if the mention configuration is valid, otherwise `false`.'
description: checkValidityDescription({ componentName: 'rich text mention users' }),
table: { category: apiCategory.methods },
},
validity: {
description: mappingUserValidityDescription
description: mappingUserValidityDescription,
table: { category: apiCategory.nonAttributeProperties },
},
mentionUpdate: {
name: 'mention-update',
description: mentionUpdateEventDescription
description: mentionUpdateEventDescription,
table: { category: apiCategory.events },
},
buttonLabel: {
name: 'button-label',
description:
'Label and title text for the mention button in the footer toolbar.'
'Label and title text for the mention button in the footer toolbar.',
table: { category: apiCategory.attributes },
},
content: {
name: 'default',
description: `Configure how users are displayed by adding \`${mappingTextTag}\` elements as content. Ensure that each user mentioned in the markdown input has a corresponding user mapping.

For the rich text editor, the user mappings are used to populate the mention dropdown. Update the mention elements dynamically by listening to the \`mention-update\` event and filtering the user mappings based on the current text input.
It is recommended to limit the number of users displayed to 50 or fewer.

For more details, see **Guidance for filtering users**.`,
table: { category: apiCategory.slots },
}
}
};
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { Controls, Canvas, Meta, Title, Description } from '@storybook/blocks';
import * as editorStories from './rich-text-editor.stories';
import RichTextMentionUsersGuidance from '../../rich-text-mention/users/rich-text-mention-users-guidance.mdx';
import ComponentApisLink from '../../../docs/component-apis-link.mdx';
import * as mentionUserStories from '../../rich-text-mention/users/rich-text-mention-users.stories';
import * as userMappingStories from '../../mapping/user/mapping-user.stories';
import { richTextEditorTag } from '../../../../../nimble-components/src/rich-text/editor';
Expand All @@ -12,20 +14,20 @@ The rich text editor component allows users to add/edit text formatted with vari
See the [rich text viewer](?path=/docs/incubating-rich-text-viewer--docs) component to render markdown without allowing editing.

<Canvas of={editorStories.richTextEditor} />
<Controls of={editorStories.richTextEditor} />

## User Mention Usage

To enable support for `@mention` in the <Tag name={richTextEditorTag}/> component, include the specified configuration and mapping components as children. These components facilitate the parsing of input markdown into a mention node, displaying a user's name and shows list of user names in the mapping dropdown. Additionally, ensure that each user mentioned in the markdown input has a corresponding user mapping.
## API

It is also highly recommended to limit the number of users displayed (maximum 50 users at a time) in the mention popup when triggered in the editor and based on filtering, update the mention elements dynamically by listening to the `mention-update` event. This enhances the performance and overall user experience when interacting with the mention dropdown. For more details, see [Client Usage Guidance on Filtered Users](https://github.com/ni/nimble/blob/main/packages/nimble-components/src/rich-text/specs/mention-hld.md#client-usage-guidance-on-filtered-users).
<Controls of={editorStories.richTextEditor} />
<ComponentApisLink />

## User Configuration
### User configuration

<Description of={mentionUserStories} />
<Controls of={mentionUserStories.richTextMentionUsers} />

## User Mapping
### User mapping

<Description of={userMappingStories} />
<Controls of={userMappingStories.userMapping} />

<RichTextMentionUsersGuidance />
Loading
Loading