diff --git a/packages/__docs__/versioned-components.ts b/packages/__docs__/versioned-components.ts index 622ea8c015..c4fd1df45d 100644 --- a/packages/__docs__/versioned-components.ts +++ b/packages/__docs__/versioned-components.ts @@ -24,6 +24,7 @@ import * as V11_6 from '@instructure/ui/v11_6' import * as V11_7 from '@instructure/ui/v11_7' +import * as V11_8 from '@instructure/ui/v11_8' // eslint-disable-next-line no-restricted-imports import { Guidelines } from './src/Guidelines' // eslint-disable-next-line no-restricted-imports @@ -33,11 +34,17 @@ import { ToggleBlockquote } from './src/ToggleBlockquote' // eslint-disable-next-line no-restricted-imports import { V12ChangelogTable } from './src/V12ChangelogTable' -const docsComponents = { Guidelines, Figure, ToggleBlockquote, V12ChangelogTable } +const docsComponents = { + Guidelines, + Figure, + ToggleBlockquote, + V12ChangelogTable +} const versions: Record> = { v11_6: { ...V11_6, ...docsComponents } as any, v11_7: { ...V11_7, ...docsComponents } as any, + v11_8: { ...V11_8, ...docsComponents } as any } /** @@ -45,8 +52,6 @@ const versions: Record> = { * Version exports are merged with docs-specific components (Guidelines, * Figure, ToggleBlockquote, V12ChangelogTable). Defaults to v11_6. */ -export function getComponentsForVersion( - version?: string -): Record { +export function getComponentsForVersion(version?: string): Record { return versions[version ?? 'v11_6'] ?? versions['v11_6'] } diff --git a/packages/ui-tag/package.json b/packages/ui-tag/package.json index 6907460f09..3ef3482f78 100644 --- a/packages/ui-tag/package.json +++ b/packages/ui-tag/package.json @@ -76,12 +76,19 @@ "require": "./lib/exports/b.js", "default": "./es/exports/b.js" }, + "./v11_8": { + "src": "./src/exports/c.ts", + "types": "./types/exports/c.d.ts", + "import": "./es/exports/c.js", + "require": "./lib/exports/c.js", + "default": "./es/exports/c.js" + }, "./latest": { - "src": "./src/exports/b.ts", - "types": "./types/exports/b.d.ts", - "import": "./es/exports/b.js", - "require": "./lib/exports/b.js", - "default": "./es/exports/b.js" + "src": "./src/exports/c.ts", + "types": "./types/exports/c.d.ts", + "import": "./es/exports/c.js", + "require": "./lib/exports/c.js", + "default": "./es/exports/c.js" } } } diff --git a/packages/ui-tag/src/Tag/__tests__/Tag.test.tsx b/packages/ui-tag/src/Tag/__tests__/Tag.test.tsx index 87e3599edf..1d41a9981d 100644 --- a/packages/ui-tag/src/Tag/__tests__/Tag.test.tsx +++ b/packages/ui-tag/src/Tag/__tests__/Tag.test.tsx @@ -22,51 +22,191 @@ * SOFTWARE. */ -import { render } from 'vitest-browser-react' -import { page, userEvent } from 'vitest/browser' -import { describe, it, expect, vi } from 'vitest' +import { ComponentType } from 'react' +import { render, screen, waitFor, fireEvent } from '@testing-library/react' +import { userEvent } from '@testing-library/user-event' +import '@testing-library/jest-dom' +import { vi } from 'vitest' import { runAxeCheck } from '@instructure/ui-axe-check' import type { ViewProps } from '@instructure/ui-view/latest' import { Tag } from '@instructure/ui-tag/latest' import { View } from '@instructure/ui-view/latest' +const originalOmitViewProps = View.omitViewProps + describe('', async () => { + beforeAll(() => { + // View component read Component.name instead of Component.displayName + // causing [undefined] in error messages + type TagComponentType = ComponentType & { + name: 'Tag' + } + + View.omitViewProps = (props, Component) => { + const ModifiedComponent = { + ...Component, + name: 'Tag' + } as TagComponentType + return originalOmitViewProps(props, ModifiedComponent) + } + }) + afterAll(() => { + View.omitViewProps = originalOmitViewProps + }) + it('should display text', async () => { - await render() - const tag = page.getByText('Summer').element() + render() + const tag = screen.getByText('Summer') expect(tag).toBeInTheDocument() }) - it('should render as a button and respond to onClick event', async () => { + it('should render the body as a button and respond to onClick event', async () => { const onClick = vi.fn() - await render( - - ) + render() - const button = page.getByTestId('summer-button').element() + const button = screen.getByRole('button') await userEvent.click(button) - await vi.waitFor(() => { + await waitFor(() => { expect(onClick).toHaveBeenCalledTimes(1) expect(button.tagName).toBe('BUTTON') }) }) - it('should render a close icon when it is dismissible and clickable', async () => { - const onClick = vi.fn() - const { container } = await render( - + it('should render the body as a link when an href is provided', async () => { + render() + + const link = screen.getByRole('link') + + expect(link.tagName).toBe('A') + expect(link).toHaveAttribute('href', '/summer') + }) + + it('should render a link and still fire onClick when both href and onClick are provided', async () => { + const onClick = vi.fn((e) => e.preventDefault()) + render() + + const link = screen.getByRole('link') + await userEvent.click(link) + + await waitFor(() => { + expect(link.tagName).toBe('A') + expect(onClick).toHaveBeenCalledTimes(1) + }) + }) + + it('should render a leading icon before the text', async () => { + const { container } = render( + } /> ) - const icon = container.querySelector('svg') + const icon = container.querySelector('svg[name="calendar"]') + expect(icon).toBeInTheDocument() + }) + + it('should render a close button and fire onDismiss when it is dismissible', async () => { + const onDismiss = vi.fn() + const { container } = render( + + ) + const icon = container.querySelector('svg') expect(icon).toHaveAttribute('name', 'X') + + const closeButton = screen.getByRole('button') + await userEvent.click(closeButton) + + await waitFor(() => { + expect(onDismiss).toHaveBeenCalledTimes(1) + }) + }) + + it('should only dismiss when the close button is clicked, not the body', async () => { + const onClick = vi.fn() + const onDismiss = vi.fn() + render( + + ) + + // body button (contains the text) and close button (contains the X icon) + const body = screen.getByText('Summer').closest('button')! + await userEvent.click(body) + + await waitFor(() => { + expect(onClick).toHaveBeenCalledTimes(1) + expect(onDismiss).not.toHaveBeenCalled() + }) + }) + + it('should not fire onClick or onDismiss when disabled', async () => { + const onClick = vi.fn() + const onDismiss = vi.fn() + const { container } = render( + + ) + + const buttons = container.querySelectorAll('button') + buttons.forEach((button) => { + fireEvent.click(button, { button: 0, detail: 1 }) + }) + + await waitFor(() => { + expect(onClick).not.toHaveBeenCalled() + expect(onDismiss).not.toHaveBeenCalled() + }) + }) + + it('should move focus to the link body then the close button via Tab', async () => { + render( + {}} + /> + ) + const link = screen.getByRole('link') + const closeButton = screen.getByRole('button') + + await userEvent.tab() + expect(link).toHaveFocus() + + await userEvent.tab() + expect(closeButton).toHaveFocus() }) it('should meet a11y standards', async () => { - const { container } = await render() + const { container } = render() + const axeCheck = await runAxeCheck(container) + + expect(axeCheck).toBe(true) + }) + + it('should meet a11y standards when dismissible (close button is labelled)', async () => { + const { container } = render( + {}} + /> + ) const axeCheck = await runAxeCheck(container) expect(axeCheck).toBe(true) @@ -89,7 +229,7 @@ describe('', async () => { .spyOn(console, 'error') .mockImplementation(() => {}) - await render() + render() const warning = `Warning: [Tag] prop '${prop}' is not allowed.` expect(consoleError.mock.calls[0][0]).toBe(warning) @@ -100,7 +240,7 @@ describe('', async () => { const props = { [prop]: allowedProps[prop] } const consoleError = vi.spyOn(console, 'error') - await render() + render() expect(consoleError).not.toHaveBeenCalled() consoleError.mockRestore() diff --git a/packages/ui-tag/src/Tag/v2/index.tsx b/packages/ui-tag/src/Tag/v2/index.tsx index 1e44b531cc..1ce50c36b0 100644 --- a/packages/ui-tag/src/Tag/v2/index.tsx +++ b/packages/ui-tag/src/Tag/v2/index.tsx @@ -25,8 +25,8 @@ import { Component } from 'react' import { XInstUIIcon } from '@instructure/ui-icons' -import { View } from '@instructure/ui-view/latest' -import type { ViewProps } from '@instructure/ui-view/latest' +import { View } from '@instructure/ui-view/v11_7' +import type { ViewProps } from '@instructure/ui-view/v11_7' import { omitProps } from '@instructure/ui-react-utils' import { isActiveElement } from '@instructure/ui-dom-utils' import { withStyleNew } from '@instructure/emotion' diff --git a/packages/ui-tag/src/Tag/v2/props.ts b/packages/ui-tag/src/Tag/v2/props.ts index c35080c7c8..be2ff61c2e 100644 --- a/packages/ui-tag/src/Tag/v2/props.ts +++ b/packages/ui-tag/src/Tag/v2/props.ts @@ -23,7 +23,7 @@ */ import React from 'react' -import type { ViewProps } from '@instructure/ui-view/latest' +import type { ViewProps } from '@instructure/ui-view/v11_7' import type { Spacing, WithStyleProps, diff --git a/packages/ui-tag/src/Tag/v3/README.md b/packages/ui-tag/src/Tag/v3/README.md new file mode 100644 index 0000000000..4cd4292b49 --- /dev/null +++ b/packages/ui-tag/src/Tag/v3/README.md @@ -0,0 +1,117 @@ +--- +describes: Tag +--- + +Use `` to represent a category or group in a form. + +```js +--- +type: example +--- + +``` + +### Leading icon + +Use the `renderIcon` prop to render an icon to the left +of the Tag text. The icon automatically adjusts its size +to match the Tag `size`. + +```js +--- +type: example +--- +} /> +``` + +### Dismissible + +Provide `renderDismissButtonLabel` to render a close (X) button and make the +Tag dismissible. Its value is the close button's screen reader label, so it +must describe the dismiss action. Clicking the close +button calls `onDismiss`. + +```js +--- +type: example +--- + +``` + +### Link + +Provide an `href` and the Tag body renders as a link. + +```js +--- +type: example +--- + } /> +``` + +### Disabled + +A `disabled` Tag. + +```js +--- +type: example +--- + +``` + +### Sizes + +`medium` is the default Tag size. + +```js +--- +type: example +--- +
+ + + +
+``` + +### Max-width + +> **DEPRECATED** Please do not make Tags that have overflowing text, it's an a11y anti-pattern. The issue is if text overflows it cannot be read by keyboard-only users. We could make the Tag focusable, but that would be an anti-pattern too because only elements that are interactive should be focusable (and just displaying a tooltip is not a real interaction) + +```js +--- +type: example +--- + +``` + +### Guidelines + +```js +--- +type: embed +--- + +
+ When implementing dismissible tags, provide a descriptive `renderDismissButtonLabel` (e.g. "Remove Summer") so screen readers announce what the close button does +
+
+``` diff --git a/packages/ui-tag/src/Tag/v3/index.tsx b/packages/ui-tag/src/Tag/v3/index.tsx new file mode 100644 index 0000000000..0e5b2a6e3b --- /dev/null +++ b/packages/ui-tag/src/Tag/v3/index.tsx @@ -0,0 +1,261 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2015 - present Instructure, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import { Component } from 'react' + +import { XInstUIIcon, renderIconWithProps } from '@instructure/ui-icons' +import { View } from '@instructure/ui-view/latest' +import type { ViewProps } from '@instructure/ui-view/latest' +import { omitProps, callRenderProp } from '@instructure/ui-react-utils' +import { isActiveElement } from '@instructure/ui-dom-utils' +import { withStyleNew } from '@instructure/emotion' + +import generateStyle from './styles.js' +import type { TagProps } from './props' +import { allowedProps } from './props.js' + +/** +--- +category: components +--- +**/ + +@withStyleNew(generateStyle) +class Tag extends Component { + static displayName = 'Tag' + static readonly componentId = 'Tag' + + static allowedProps = allowedProps + static defaultProps = { + size: 'medium', + disabled: false, + readOnly: false + } + + state = { + iconHovered: false + } + + ref: Element | null = null + bodyRef: HTMLElement | null = null + closeRef: HTMLElement | null = null + + componentDidMount() { + this.props.makeStyles?.() + } + + componentDidUpdate() { + this.props.makeStyles?.() + } + + get interactive() { + return !!this.props.href || typeof this.props.onClick === 'function' + } + + get isWholeTagButton() { + const { onClick, href, renderDismissButtonLabel } = this.props + return typeof onClick === 'function' && !href && !renderDismissButtonLabel + } + + get focused() { + return ( + isActiveElement(this.ref) || + isActiveElement(this.bodyRef) || + isActiveElement(this.closeRef) + ) + } + + focus = () => { + const target = this.isWholeTagButton + ? this.ref + : this.interactive && this.bodyRef + ? this.bodyRef + : this.closeRef + ;(target as HTMLElement | null)?.focus() + } + + handleIconMouseEnter = () => { + this.setState({ iconHovered: true }) + } + + handleIconMouseLeave = () => { + this.setState({ iconHovered: false }) + } + + handleClick = (e: React.MouseEvent) => { + const { disabled, readOnly, onClick } = this.props + + if (disabled || readOnly) { + e.preventDefault() + e.stopPropagation() + } else if (typeof onClick === 'function') { + onClick(e as React.MouseEvent) + } + } + + handleDismiss = (e: React.MouseEvent) => { + const { disabled, readOnly, onDismiss } = this.props + + if (disabled || readOnly) { + e.preventDefault() + e.stopPropagation() + } else if (typeof onDismiss === 'function') { + onDismiss(e as React.MouseEvent) + } + } + + handleRef = (element: Element | null) => { + this.ref = element + + if (typeof this.props.elementRef === 'function') { + this.props.elementRef(element) + } + } + + handleBodyRef = (element: HTMLElement | null) => { + this.bodyRef = element + } + + handleCloseRef = (element: HTMLElement | null) => { + this.closeRef = element + } + + getIconSize = () => { + const { size } = this.props + + const sizeMap = { + small: 'xs', + medium: 'sm', + large: 'md' + } as const + + return sizeMap[size!] + } + + render() { + const { + className, + renderDismissButtonLabel, + disabled, + readOnly, + text, + href, + renderIcon, + margin, + styles + } = this.props + + const dismissLabel = + renderDismissButtonLabel != null + ? callRenderProp(renderDismissButtonLabel) + : undefined + + const passthroughProps = View.omitViewProps( + omitProps(this.props, Tag.allowedProps), + Tag + ) + + const getIconColor = () => { + if (disabled) { + return 'mutedColor' + } + return this.state.iconHovered ? 'actionSecondaryHoverColor' : 'baseColor' + } + + const isDisabled = disabled || readOnly + const isWholeTagButton = this.isWholeTagButton + const BodyElement: React.ElementType = isWholeTagButton + ? 'span' + : href + ? 'a' + : this.props.onClick + ? 'button' + : 'span' + const isBodyInteractive = !isWholeTagButton && this.interactive + + const rootProps: Record = isWholeTagButton + ? { + as: 'button', + type: 'button', + disabled: isDisabled, + onClick: this.handleClick, + 'aria-disabled': isDisabled ? 'true' : undefined + } + : { as: 'span' } + + const bodyProps: Record = {} + if (BodyElement === 'a') { + bodyProps.href = isDisabled ? undefined : href + } else if (BodyElement === 'button') { + bodyProps.type = 'button' + bodyProps.disabled = isDisabled + } + if (isBodyInteractive) { + bodyProps.onClick = this.handleClick + bodyProps['aria-disabled'] = isDisabled ? 'true' : undefined + } + + return ( + + + {renderIcon ? ( + + {renderIconWithProps(renderIcon, this.getIconSize(), undefined)} + + ) : null} + {text} + + {dismissLabel ? ( + + ) : null} + + ) + } +} + +export default Tag +export { Tag } diff --git a/packages/ui-tag/src/Tag/v3/props.ts b/packages/ui-tag/src/Tag/v3/props.ts new file mode 100644 index 0000000000..12fbdd05d3 --- /dev/null +++ b/packages/ui-tag/src/Tag/v3/props.ts @@ -0,0 +1,110 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2015 - present Instructure, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import React from 'react' +import type { ViewProps } from '@instructure/ui-view/latest' +import type { + Spacing, + WithStyleProps, + ComponentStyle +} from '@instructure/emotion' +import type { NewComponentTypes } from '@instructure/ui-themes' +import type { OtherHTMLAttributes, Renderable } from '@instructure/shared-types' + +type TagOwnProps = { + className?: string + text: string | React.ReactNode + /** + * Whether or not to disable the tag + */ + disabled?: boolean + /** + * Works just like disabled but keeps the same styles as if it were active + */ + readOnly?: boolean + /** + * Providing this prop renders a close button and makes the Tag dismissible. + * The value is used as the close button's screen reader label. + * Clicking the close button calls `onDismiss`. + */ + renderDismissButtonLabel?: Renderable + /** + * Called when the close button is clicked. + */ + onDismiss?: (event: React.MouseEvent) => void + /** + * Add an SVG icon to the left of the Tag text. Do not add icons directly as + * children. When using Lucide icons, Tag will automatically pass the + * appropriate size prop based on the Tag's `size`. + */ + renderIcon?: Renderable + /** + * If you provide an `href`, the Tag body renders as a link (``). + */ + href?: string + /** + * Valid values are `0`, `none`, `auto`, and Spacing token values, + * see https://instructure.design/layout-spacing. Apply these values via + * familiar CSS-like shorthand. For example, `margin="general.spaceMd auto"`. + */ + margin?: Spacing + /** + * If you add an onClick prop, Tag renders as a clickable button + */ + onClick?: (event: React.MouseEvent) => void + /** + * Provides a reference to the underlying html root element + */ + elementRef?: (element: Element | null) => void + size?: 'small' | 'medium' | 'large' +} + +type PropKeys = keyof TagOwnProps + +type AllowedPropKeys = Readonly> + +type TagProps = TagOwnProps & + WithStyleProps, TagStyle> & + OtherHTMLAttributes + +type TagStyle = ComponentStyle< + 'tag' | 'body' | 'text' | 'leadIcon' | 'closeButton' | 'closeIcon' +> +const allowedProps: AllowedPropKeys = [ + 'className', + 'text', + 'disabled', + 'readOnly', + 'renderDismissButtonLabel', + 'onDismiss', + 'renderIcon', + 'href', + 'margin', + 'onClick', + 'elementRef', + 'size' +] + +export type { TagProps, TagStyle } +export { allowedProps } diff --git a/packages/ui-tag/src/Tag/v3/styles.ts b/packages/ui-tag/src/Tag/v3/styles.ts new file mode 100644 index 0000000000..d4ba6df3e9 --- /dev/null +++ b/packages/ui-tag/src/Tag/v3/styles.ts @@ -0,0 +1,171 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2015 - present Instructure, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import type { NewComponentTypes, SharedTokens } from '@instructure/ui-themes' +import type { TagProps, TagStyle } from './props' +import { calcFocusOutlineStyles } from '@instructure/emotion' + +/** + * --- + * private: true + * --- + * Generates the style object from the theme and provided additional information + * @param {Object} componentTheme The theme variable object. + * @param {Object} props the props of the component, the style is applied to + * @param {Object} sharedTokens the state of the component, the style is applied to + * @return {Object} The final style object, which will be used in the component + */ +const generateStyle = ( + componentTheme: ReturnType, + props: TagProps, + sharedTokens: SharedTokens +): TagStyle => { + const { size, onClick, href, renderDismissButtonLabel, disabled, readOnly } = + props + + const isInteractive = !!href || !!onClick + const isDisabled = !!disabled || !!readOnly + const isWholeTagButton = !!onClick && !href && !renderDismissButtonLabel + const isBodyInteractive = isInteractive && !isWholeTagButton + + const focusOutline = calcFocusOutlineStyles(sharedTokens.focusOutline) + + const sizeVariants = { + small: { + height: componentTheme.heightSmall, + paddingLeft: componentTheme.paddingHorizontalSmall, + paddingRight: componentTheme.paddingHorizontalSmall, + fontSize: componentTheme.fontSizeSmall + }, + medium: { + height: componentTheme.heightMedium, + paddingLeft: componentTheme.paddingHorizontal, + paddingRight: componentTheme.paddingHorizontal, + fontSize: `calc(${componentTheme.fontSizeMedium} - 0.0625rem)` + }, + large: { + height: componentTheme.heightLarge, + paddingLeft: componentTheme.paddingHorizontalSmall, + paddingRight: componentTheme.paddingHorizontal, + fontSize: `calc(${componentTheme.fontSizeLarge} - 0.0625rem)` + } + } + + return { + tag: { + label: 'tag', + boxSizing: 'border-box', + fontFamily: componentTheme.fontFamily, + display: 'inline-flex', + alignItems: 'center', + textAlign: 'center', + verticalAlign: 'middle', + userSelect: 'none', + backgroundColor: componentTheme.defaultBackground, + border: `${componentTheme.defaultBorderWidth} ${componentTheme.defaultBorderStyle} ${componentTheme.defaultBorderColor}`, + borderRadius: componentTheme.defaultBorderRadius, + color: componentTheme.defaultColor, + ...(isInteractive && { + transition: `background-color ${componentTheme.transitionTiming}`, + '&:hover': { backgroundColor: componentTheme.defaultBackgroundHover } + }), + ...(isWholeTagButton && { + appearance: 'none', + cursor: isDisabled ? 'not-allowed' : 'pointer', + ...focusOutline, + transition: `background-color ${componentTheme.transitionTiming}, outline-color 0.2s, outline-offset 0.25s` + }), + ...sizeVariants[size!], + ...(isDisabled && { + cursor: 'not-allowed', + pointerEvents: 'none', + opacity: 0.5 + }) + }, + body: { + label: 'tag__body', + boxSizing: 'border-box', + display: 'inline-flex', + alignItems: 'center', + minWidth: 0, + color: 'inherit', + font: 'inherit', + ...(isBodyInteractive && { + appearance: 'none', + background: 'none', + border: 0, + margin: 0, + padding: 0, + textDecoration: 'none', + touchAction: 'manipulation', + cursor: isDisabled ? 'not-allowed' : 'pointer', + borderRadius: componentTheme.inlineBorderRadius, + ...focusOutline + }) + }, + leadIcon: { + label: 'tag__leadIcon', + display: 'inline-flex', + alignItems: 'center', + marginInlineEnd: componentTheme.iconMargin, + marginInlineStart: 0 + }, + text: { + label: 'tag__text', + lineHeight: 'normal', + whiteSpace: 'nowrap', + overflow: 'hidden', + textOverflow: 'ellipsis', + maxWidth: componentTheme.maxWidth, + ...(href && { textDecoration: 'underline' }) + }, + closeButton: { + label: 'tag__closeButton', + boxSizing: 'border-box', + display: 'inline-flex', + alignItems: 'center', + justifyContent: 'center', + appearance: 'none', + background: 'none', + border: 0, + margin: 0, + padding: 0, + marginInlineStart: componentTheme.iconMargin, + marginInlineEnd: 0, + color: 'inherit', + touchAction: 'manipulation', + cursor: isDisabled ? 'not-allowed' : 'pointer', + borderRadius: componentTheme.inlineBorderRadius, + ...focusOutline + }, + closeIcon: { + label: 'tag__closeIcon', + display: 'inline-flex', + alignItems: 'center', + transition: `all ${componentTheme.transitionTiming}` + } + } +} + +export default generateStyle diff --git a/packages/ui-tag/src/exports/c.ts b/packages/ui-tag/src/exports/c.ts new file mode 100644 index 0000000000..b800e060c4 --- /dev/null +++ b/packages/ui-tag/src/exports/c.ts @@ -0,0 +1,26 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2015 - present Instructure, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +export { Tag } from '../Tag/v3/index.js' +export type { TagProps } from '../Tag/v3/props' diff --git a/packages/ui/package.json b/packages/ui/package.json index 21f888c9e1..0aae41b33e 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -136,12 +136,19 @@ "require": "./lib/v11_7.js", "default": "./es/v11_7.js" }, + "./v11_8": { + "src": "./src/v11_8.ts", + "types": "./types/v11_8.d.ts", + "import": "./es/v11_8.js", + "require": "./lib/v11_8.js", + "default": "./es/v11_8.js" + }, "./latest": { - "src": "./src/v11_7.ts", - "types": "./types/v11_7.d.ts", - "import": "./es/v11_7.js", - "require": "./lib/v11_7.js", - "default": "./es/v11_7.js" + "src": "./src/v11_8.ts", + "types": "./types/v11_8.d.ts", + "import": "./es/v11_8.js", + "require": "./lib/v11_8.js", + "default": "./es/v11_8.js" } } } diff --git a/packages/ui/src/v11_8.ts b/packages/ui/src/v11_8.ts new file mode 100644 index 0000000000..a5903f91c5 --- /dev/null +++ b/packages/ui/src/v11_8.ts @@ -0,0 +1,524 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2015 - present Instructure, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +export { InstUISettingsProvider, withStyleNew } from '@instructure/emotion' +export type { + ComponentStyle, + StyleObject, + Overrides, + ThemeOverride, + ComponentOverride, + ThemeOrLegacyOverride +} from '@instructure/emotion' +export { + AccessibleContent, + PresentationContent, + ScreenReaderContent +} from '@instructure/ui-a11y-content' +export type { + AccessibleContentProps, + ScreenReaderContentProps, + PresentationContentProps +} from '@instructure/ui-a11y-content' +export { Alert } from '@instructure/ui-alerts/v11_7' +export type { AlertProps } from '@instructure/ui-alerts/v11_7' +export { Avatar } from '@instructure/ui-avatar/v11_7' +export type { AvatarProps } from '@instructure/ui-avatar/v11_7' +export { Badge } from '@instructure/ui-badge/v11_7' +export type { BadgeProps } from '@instructure/ui-badge/v11_7' +export { Billboard } from '@instructure/ui-billboard/v11_7' +export type { BillboardProps } from '@instructure/ui-billboard/v11_7' +export { Breadcrumb, BreadcrumbLink } from '@instructure/ui-breadcrumb/v11_7' +export type { + BreadcrumbProps, + BreadcrumbLinkProps +} from '@instructure/ui-breadcrumb/v11_7' +export { + BaseButton, + Button, + CloseButton, + CondensedButton, + IconButton, + ToggleButton +} from '@instructure/ui-buttons/v11_7' +export type { + BaseButtonOwnProps, + ButtonProps, + BaseButtonProps, + ToggleButtonProps, + CloseButtonProps, + CondensedButtonProps, + IconButtonProps +} from '@instructure/ui-buttons/v11_7' +export { Byline } from '@instructure/ui-byline/v11_7' +export type { BylineProps } from '@instructure/ui-byline/v11_7' +export { Calendar, CalendarDay } from '@instructure/ui-calendar/v11_7' +export type { + CalendarDayProps, + CalendarProps +} from '@instructure/ui-calendar/v11_7' +export { + Checkbox, + CheckboxGroup, + CheckboxFacade, + ToggleFacade +} from '@instructure/ui-checkbox/v11_7' +export type { + CheckboxFacadeProps, + ToggleFacadeProps, + CheckboxGroupProps, + CheckboxProps +} from '@instructure/ui-checkbox/v11_7' +export { + ColorPicker, + ColorMixer, + ColorPreset, + ColorContrast, + ColorIndicator +} from '@instructure/ui-color-picker/v11_7' +export type { + ColorContrastProps, + ColorIndicatorProps, + ColorMixerProps, + ColorPickerProps, + ColorPresetProps +} from '@instructure/ui-color-picker/v11_7' +export { DateInput, DateInput2 } from '@instructure/ui-date-input/v11_7' +export type { + DateInputProps, + DateInput2Props +} from '@instructure/ui-date-input/v11_7' +export { DateTimeInput } from '@instructure/ui-date-time-input/v11_7' +export { Dialog } from '@instructure/ui-dialog' +export type { DialogProps } from '@instructure/ui-dialog' +export { + DrawerLayout, + DrawerContent, + DrawerTray +} from '@instructure/ui-drawer-layout/v11_7' +export type { + DrawerLayoutProps, + DrawerLayoutContentProps, + DrawerLayoutTrayProps +} from '@instructure/ui-drawer-layout/v11_7' +export { + Drilldown, + DrilldownGroup, + DrilldownOption, + DrilldownPage, + DrilldownSeparator +} from '@instructure/ui-drilldown/v11_7' +export type { + DrilldownGroupChild, + DrilldownGroupProps, + DrilldownOptionChild, + DrilldownOptionProps, + DrilldownPageChild, + DrilldownPageChildren, + DrilldownPageProps, + DrilldownProps, + DrilldownSeparatorChild, + DrilldownSeparatorProps +} from '@instructure/ui-drilldown/v11_7' +export { Editable, InPlaceEdit } from '@instructure/ui-editable/v11_7' +export type { + EditableProps, + InPlaceEditProps +} from '@instructure/ui-editable/v11_7' +export { Expandable } from '@instructure/ui-expandable' +export type { + ExpandableProps, + RenderExpandable, + ExpandableToggleProps +} from '@instructure/ui-expandable' +export { FileDrop } from '@instructure/ui-file-drop/v11_7' +export type { FileDropProps } from '@instructure/ui-file-drop/v11_7' +export { Flex, FlexItem } from '@instructure/ui-flex/v11_7' +export type { FlexProps, FlexItemProps } from '@instructure/ui-flex/v11_7' +export { Focusable } from '@instructure/ui-focusable' +export type { FocusableProps } from '@instructure/ui-focusable' +export { + FormField, + FormFieldMessage, + FormFieldMessages, + FormFieldLayout, + FormFieldGroup +} from '@instructure/ui-form-field/v11_7' +export type { + FormFieldGroupProps, + FormFieldMessageProps, + FormFieldMessagesProps, + FormFieldLayoutProps, + FormFieldOwnProps, + FormFieldProps, + FormMessage, + FormMessageType, + FormMessageChild +} from '@instructure/ui-form-field/v11_7' +export { Grid, GridRow, GridCol } from '@instructure/ui-grid/v11_7' +export type { + GridProps, + GridRowProps, + GridColProps, + GridBreakpoints +} from '@instructure/ui-grid/v11_7' +export { Heading } from '@instructure/ui-heading/v11_7' +export type { HeadingProps } from '@instructure/ui-heading/v11_7' +export { + ApplyLocale, + TextDirectionContext, + ApplyLocaleContext, + textDirectionContextConsumer, + DateTime, + getTextDirection, + Locale, + DIRECTION +} from '@instructure/ui-i18n' +export type { + Moment, + TextDirectionContextConsumerProps, + ApplyLocaleProps +} from '@instructure/ui-i18n' +export * from '@instructure/ui-icons' +export { Img } from '@instructure/ui-img/v11_7' +export type { ImgProps } from '@instructure/ui-img/v11_7' +export { + NutritionFacts, + DataPermissionLevels, + AiInformation +} from '@instructure/ui-instructure/v11_7' +export type { + NutritionFactsProps, + DataPermissionLevelsProps, + AiInformationProps +} from '@instructure/ui-instructure/v11_7' +export { Link } from '@instructure/ui-link/v11_7' +export type { LinkProps } from '@instructure/ui-link/v11_7' +export { + List, + ListItem, + InlineList, + InlineListItem +} from '@instructure/ui-list/v11_7' +export type { + ListProps, + ListItemProps, + InlineListProps, + InlineListItemProps +} from '@instructure/ui-list/v11_7' +export { + Menu, + MenuItem, + MenuItemGroup, + MenuItemSeparator +} from '@instructure/ui-menu/v11_7' +export type { + MenuProps, + MenuItemProps, + MenuGroupProps, + MenuSeparatorProps +} from '@instructure/ui-menu/v11_7' +export { MetricGroup, Metric } from '@instructure/ui-metric/v11_7' +export type { + MetricGroupProps, + MetricProps +} from '@instructure/ui-metric/v11_7' +export { + Modal, + ModalBody, + ModalFooter, + ModalHeader +} from '@instructure/ui-modal/v11_7' +export type { + ModalProps, + ModalBodyProps, + ModalFooterProps, + ModalHeaderProps +} from '@instructure/ui-modal/v11_7' +export { Transition, BaseTransition } from '@instructure/ui-motion' +export type { + TransitionProps, + TransitionType, + BaseTransitionStatesType +} from '@instructure/ui-motion' +export { AppNav, AppNavItem } from '@instructure/ui-navigation/v11_7' +export type { + AppNavProps, + AppNavItemProps +} from '@instructure/ui-navigation/v11_7' +export { NumberInput } from '@instructure/ui-number-input/v11_7' +export type { NumberInputProps } from '@instructure/ui-number-input/v11_7' +export { + Options, + OptionSeparator, + OptionItem +} from '@instructure/ui-options/v11_7' +export type { + OptionsItemProps, + OptionsItemRenderProps, + OptionsProps, + OptionsSeparatorProps +} from '@instructure/ui-options/v11_7' +export { Mask, Overlay } from '@instructure/ui-overlays/v11_7' +export type { MaskProps, OverlayProps } from '@instructure/ui-overlays/v11_7' +export { Pages, PagesPage } from '@instructure/ui-pages/v11_7' +export type { PagesPageProps, PagesProps } from '@instructure/ui-pages/v11_7' +export { Pagination, PaginationButton } from '@instructure/ui-pagination/v11_7' +export type { + PaginationNavigationProps, + PaginationPageProps, + PaginationProps +} from '@instructure/ui-pagination/v11_7' +export { Pill } from '@instructure/ui-pill/v11_7' +export type { PillProps } from '@instructure/ui-pill/v11_7' +export { Popover } from '@instructure/ui-popover/v11_7' +export type { + PopoverProps, + PopoverOwnProps +} from '@instructure/ui-popover/v11_7' +export { Portal } from '@instructure/ui-portal' +export type { PortalProps, PortalNode } from '@instructure/ui-portal' +export { + Position, + calculateElementPosition, + executeMirrorFunction, + mirrorHorizontalPlacement, + mirrorPlacement, + parsePlacement +} from '@instructure/ui-position' +export type { + PositionConstraint, + PositionObject, + PositionProps, + PlacementPropValues, + PositionMountNode +} from '@instructure/ui-position' +export { ProgressBar, ProgressCircle } from '@instructure/ui-progress/v11_7' +export type { + ProgressBarProps, + ProgressCircleProps +} from '@instructure/ui-progress/v11_7' +export { RadioInput, RadioInputGroup } from '@instructure/ui-radio-input/v11_7' +export type { + RadioInputProps, + RadioInputGroupProps +} from '@instructure/ui-radio-input/v11_7' +export { RangeInput } from '@instructure/ui-range-input/v11_7' +export type { RangeInputProps } from '@instructure/ui-range-input/v11_7' +export { Rating, RatingIcon } from '@instructure/ui-rating/v11_7' +export type { RatingIconProps, RatingProps } from '@instructure/ui-rating/v11_7' +export { + Responsive, + ResponsivePropTypes, + addElementQueryMatchListener, + addMediaQueryMatchListener, + parseQuery, + jsonToMediaQuery +} from '@instructure/ui-responsive/v11_7' +export type { + ResponsiveProps, + ResponsivePropsObject, + BreakpointQueries, + QueriesMatching, + Query, + ResponsiveByBreakpointProps, + ValidQueryKey +} from '@instructure/ui-responsive/v11_7' +export { Select, SelectGroup, SelectOption } from '@instructure/ui-select/v11_7' +export type { + SelectOwnProps, + SelectGroupProps, + SelectOptionProps, + SelectProps +} from '@instructure/ui-select/v11_7' +export { Selectable } from '@instructure/ui-selectable' +export type { + SelectableRender, + SelectableProps +} from '@instructure/ui-selectable' +export { SideNavBar, SideNavBarItem } from '@instructure/ui-side-nav-bar/v11_7' +export type { + SideNavBarProps, + SideNavBarItemProps +} from '@instructure/ui-side-nav-bar/v11_7' +export { + SimpleSelect, + SimpleSelectOption, + SimpleSelectGroup +} from '@instructure/ui-simple-select/v11_7' +export type { + SimpleSelectProps, + SimpleSelectGroupProps, + SimpleSelectOptionProps +} from '@instructure/ui-simple-select/v11_7' +export { SourceCodeEditor } from '@instructure/ui-source-code-editor/v11_7' +export type { SourceCodeEditorProps } from '@instructure/ui-source-code-editor/v11_7' +export { Spinner } from '@instructure/ui-spinner/v11_7' +export type { SpinnerProps } from '@instructure/ui-spinner/v11_7' +export { InlineSVG, SVGIcon } from '@instructure/ui-svg-images' +export type { InlineSVGProps, SVGIconProps } from '@instructure/ui-svg-images' +export { + Table, + TableContext, + TableBody, + TableHead, + TableCell, + TableRow, + TableColHeader, + TableRowHeader +} from '@instructure/ui-table/v11_7' +export type { + TableBodyProps, + TableCellProps, + TableProps, + TableColHeaderProps, + TableHeadProps, + TableRowHeaderProps, + TableRowProps +} from '@instructure/ui-table/v11_7' +export { Tabs, TabsPanel, TabsTab } from '@instructure/ui-tabs/v11_7' +export type { + TabsTabProps, + TabsPanelProps, + TabsProps +} from '@instructure/ui-tabs/v11_7' +export { Tag } from '@instructure/ui-tag/v11_8' +export type { TagProps } from '@instructure/ui-tag/v11_8' +export { Text } from '@instructure/ui-text/v11_7' +export type { TextProps } from '@instructure/ui-text/v11_7' +export { TextArea } from '@instructure/ui-text-area/v11_7' +export type { TextAreaProps } from '@instructure/ui-text-area/v11_7' +export { TextInput } from '@instructure/ui-text-input/v11_7' +export type { TextInputProps } from '@instructure/ui-text-input/v11_7' +export { + canvas, + canvasHighContrast, + dark, + light, + primitives, + additionalPrimitives, + dataVisualization +} from '@instructure/ui-themes' +export type { + ThemeMap, + BaseTheme, + Theme, + ThemeKeys, + ThemeSpecificStyle, + CanvasTheme, + CanvasBrandVariables, + CanvasHighContrastTheme, + Primitives, + AdditionalPrimitives, + DataVisualization, + UI, + Dark, + DarkTheme, + Light, + LightTheme +} from '@instructure/ui-themes' +export { TimeSelect } from '@instructure/ui-time-select/v11_7' +export type { TimeSelectProps } from '@instructure/ui-time-select/v11_7' +export { + ToggleDetails, + ToggleGroup +} from '@instructure/ui-toggle-details/v11_7' +export type { + ToggleDetailsProps, + ToggleGroupProps +} from '@instructure/ui-toggle-details/v11_7' +export { Tooltip } from '@instructure/ui-tooltip/v11_7' +export type { + TooltipProps, + TooltipRenderChildrenArgs +} from '@instructure/ui-tooltip/v11_7' +export { + TopNavBar, + TopNavBarActionItems, + TopNavBarBrand, + TopNavBarBreadcrumb, + TopNavBarItem, + TopNavBarLayout, + TopNavBarMenuItems, + TopNavBarUser +} from '@instructure/ui-top-nav-bar/v11_7' +export type { + TopNavBarProps, + TopNavBarOwnProps, + TopNavBarActionItemsProps, + TopNavBarActionItemsOwnProps, + TopNavBarBrandProps, + TopNavBarBrandOwnProps, + /** + * This is an alias for the `ItemChild` type in `TopNavBarItem` + */ + ItemChild as TopNavBarItemChild, + TopNavBarItemProps, + TopNavBarItemOwnProps, + TopNavBarItemTooltipType, + TopNavBarLayoutProps, + CommonTopNavBarLayoutProps, + TopNavBarDesktopLayoutProps, + TopNavBarDesktopLayoutOwnProps, + DesktopLayoutOwnProps, + TopNavBarSmallViewportLayoutProps, + TopNavBarSmallViewportLayoutOwnProps, + SmallViewportLayoutOwnProps, + TopNavBarMenuItemsProps, + TopNavBarMenuItemsOwnProps, + TopNavBarUserProps, + TopNavBarUserOwnProps +} from '@instructure/ui-top-nav-bar/v11_7' +export { Tray } from '@instructure/ui-tray/v11_7' +export type { TrayProps } from '@instructure/ui-tray/v11_7' +export { + TreeBrowser, + TreeNode, + TreeButton, + TreeCollection +} from '@instructure/ui-tree-browser/v11_7' +export type { + TreeBrowserButtonProps, + /** + * This is an alias for the `Collection` type in `TreeBrowser` + */ + Collection as TreeBrowserCollection, + /** + * This is an alias for the `CollectionData` type in `TreeBrowser` + */ + CollectionData as TreeBrowserCollectionData, + TreeBrowserProps, + TreeBrowserCollectionProps, + TreeBrowserNodeProps +} from '@instructure/ui-tree-browser/v11_7' +export { TruncateList } from '@instructure/ui-truncate-list' +export type { TruncateListProps } from '@instructure/ui-truncate-list' +export { TruncateText } from '@instructure/ui-truncate-text/v11_7' +export type { TruncateTextProps } from '@instructure/ui-truncate-text/v11_7' +export { View, ContextView } from '@instructure/ui-view/v11_7' +export type { + ViewProps, + ContextViewProps, + ViewOwnProps +} from '@instructure/ui-view/v11_7' diff --git a/regression-test/src/app/small-components/page.tsx b/regression-test/src/app/small-components/page.tsx index 38d07a9cad..229575b2a0 100644 --- a/regression-test/src/app/small-components/page.tsx +++ b/regression-test/src/app/small-components/page.tsx @@ -34,8 +34,7 @@ import { IconMessageLine, IconClockLine, IconEndLine, - IconCheckLine, - AccessibleContent + IconCheckLine } from '@instructure/ui/latest' const Metric = mc as any @@ -82,28 +81,79 @@ export default function SmallComponentsPage() { Notification -
Tag:
+
Tag (static / leading icon in 3 sizes):
- Dismissible tag - - } - dismissible + text="Small" + size="small" + renderIcon={} + margin="0 xx-small 0 0" + /> + } margin="0 xx-small 0 0" - onClick={function () {}} /> - - } + margin="0 xx-small 0 0" + /> +
+
Tag (clickable button / dismissible):
+
+ + } onClick={function () {}} + margin="0 xx-small 0 0" + /> + + } + renderDismissButtonLabel="Remove Dismissible with icon" + onDismiss={function () {}} + margin="0 xx-small 0 0" + /> +
+
Tag (link / disabled):
+
+ + } + margin="0 xx-small 0 0" + /> + } + renderDismissButtonLabel="Remove Link dismissible" + onDismiss={function () {}} + margin="0 xx-small 0 0" + /> + } + renderDismissButtonLabel="Remove Disabled link dismissible" + onDismiss={function () {}} + margin="0 xx-small 0 0" />
TimeSelect: