diff --git a/packages/ui-extensions/docs/surfaces/admin/screenshots/components/menu.png b/packages/ui-extensions/docs/surfaces/admin/screenshots/components/menu.png new file mode 100644 index 0000000000..c162cc7a69 Binary files /dev/null and b/packages/ui-extensions/docs/surfaces/admin/screenshots/components/menu.png differ diff --git a/packages/ui-extensions/docs/surfaces/admin/screenshots/components/popover.png b/packages/ui-extensions/docs/surfaces/admin/screenshots/components/popover.png new file mode 100644 index 0000000000..ca567a30c1 Binary files /dev/null and b/packages/ui-extensions/docs/surfaces/admin/screenshots/components/popover.png differ diff --git a/packages/ui-extensions/src/docs/shared/components/Menu.ts b/packages/ui-extensions/src/docs/shared/components/Menu.ts new file mode 100644 index 0000000000..f09e77f071 --- /dev/null +++ b/packages/ui-extensions/src/docs/shared/components/Menu.ts @@ -0,0 +1,12 @@ +import type {SharedReferenceEntityTemplateSchema} from '../docs-type'; + +const data: SharedReferenceEntityTemplateSchema = { + name: 'Menu', + description: + 'Use Menu to display a list of actions that can be performed on a resource.', + category: 'Polaris web components', + subCategory: 'Actions', + related: [], +}; + +export default data; diff --git a/packages/ui-extensions/src/docs/shared/components/Popover.ts b/packages/ui-extensions/src/docs/shared/components/Popover.ts new file mode 100644 index 0000000000..ba8dac2465 --- /dev/null +++ b/packages/ui-extensions/src/docs/shared/components/Popover.ts @@ -0,0 +1,12 @@ +import type {SharedReferenceEntityTemplateSchema} from '../docs-type'; + +const data: SharedReferenceEntityTemplateSchema = { + name: 'Popover', + description: + 'Popover is used to display content in an overlay that can be triggered by a button.', + category: 'Polaris web components', + subCategory: 'Overlays', + related: [], +}; + +export default data; diff --git a/packages/ui-extensions/src/surfaces/admin/components.d.ts b/packages/ui-extensions/src/surfaces/admin/components.d.ts index 835b895798..7b29ea2d3f 100644 --- a/packages/ui-extensions/src/surfaces/admin/components.d.ts +++ b/packages/ui-extensions/src/surfaces/admin/components.d.ts @@ -11,6 +11,7 @@ * https://github.com/Shopify/ui-api-design/issues/139 */ export type ComponentChildren = any; +export type StringChildren = string; export interface GlobalProps { /** * A unique identifier for the element. @@ -1855,8 +1856,16 @@ interface ChipProps$1 extends ChipProps$1, GlobalProps {} interface ChoiceProps$1 extends GlobalProps, BaseOptionProps { /** * Content to use as the choice label. + * + * @implementation (StringChildren) The label is produced by extracting and + * concatenating the text nodes from the provided content; any markup or + * element structure is ignored. + * + * @implementation (ComponentChildren) Behaves as a slot: any elements passed + * are rendered as the label content (subject to surface constraints); there + * is no coercion to a string. */ - children?: ComponentChildren; + children?: ComponentChildren | StringChildren; /** * Additional text to provide context or guidance for the input. * @@ -3248,7 +3257,7 @@ interface TableBodyProps$1 extends GlobalProps { } interface TableCellProps$1 extends GlobalProps { /** - * The content of the table data. + * The content of the table cell. */ children?: ComponentChildren; } @@ -3275,6 +3284,16 @@ interface TableHeaderProps$1 extends GlobalProps { * @default 'labeled' */ listSlot?: ListSlotType; + /** + * The format of the column. Will automatically apply styling and alignment to cell content based on the value. + * + * - `base`: The base format for columns. + * - `currency`: Formats the column as currency. + * - `numeric`: Formats the column as a number. + * + * @default 'base' + */ + format?: 'base' | 'currency' | 'numeric'; } interface TableHeaderRowProps$1 extends GlobalProps { /** @@ -3287,6 +3306,17 @@ interface TableRowProps$1 extends GlobalProps { * The content of a TableRow, which should be `TableCell` components. */ children?: ComponentChildren; + /** + * The ID of an interactive element (e.g. `s-link`) in the row that will be the target of the click when the row is clicked. + * This is the primary action for the row; it should not be used for secondary actions. + * + * This is a click-only affordance, and does not introduce any keyboard or screen reader affordances. + * Which is why the target element must be in the table; so that keyboard and screen reader users can interact with it normally. + * + * @implementation no focus or keyboard affordances are introduced by this property. No aria attributes need to be added to the table row. + * @implementation the row and/or delegate should have some affordance that indicates it is clickable. This may be a background color, a border, a hover effect, etc. + */ + clickDelegate?: string; } interface TextProps$1 extends GlobalProps, diff --git a/packages/ui-extensions/src/surfaces/admin/components/Menu/Menu.doc.ts b/packages/ui-extensions/src/surfaces/admin/components/Menu/Menu.doc.ts new file mode 100644 index 0000000000..1141e34730 --- /dev/null +++ b/packages/ui-extensions/src/surfaces/admin/components/Menu/Menu.doc.ts @@ -0,0 +1,28 @@ +import {ReferenceEntityTemplateSchema} from '@shopify/generate-docs'; +import sharedContent from '../../../../docs/shared/components/Menu'; + +const data: ReferenceEntityTemplateSchema = { + ...sharedContent, + thumbnail: '/assets/templated-apis-screenshots/admin/components/menu.png', + isVisualComponent: true, + definitions: [ + { + title: 'Properties', + description: '', + type: 'Menu', + }, + ], + defaultExample: { + codeblock: { + title: 'Code', + tabs: [ + { + code: './examples/default.html', + language: 'preview', + }, + ], + }, + }, +}; + +export default data; diff --git a/packages/ui-extensions/src/surfaces/admin/components/Menu/examples/default.html b/packages/ui-extensions/src/surfaces/admin/components/Menu/examples/default.html new file mode 100644 index 0000000000..f4ad81ee96 --- /dev/null +++ b/packages/ui-extensions/src/surfaces/admin/components/Menu/examples/default.html @@ -0,0 +1,7 @@ +Edit customer + + + Merge customer + Request customer data + Delete customer + diff --git a/packages/ui-extensions/src/surfaces/admin/components/Popover.d.ts b/packages/ui-extensions/src/surfaces/admin/components/Popover.d.ts new file mode 100644 index 0000000000..a00077068e --- /dev/null +++ b/packages/ui-extensions/src/surfaces/admin/components/Popover.d.ts @@ -0,0 +1,379 @@ +/** VERSION: 1.8.0 **/ +/* eslint-disable import/extensions */ + +/* eslint-disable @typescript-eslint/no-namespace */ +/* eslint-disable @typescript-eslint/member-ordering */ + +// eslint-disable-next-line @typescript-eslint/triple-slash-reference, spaced-comment +/// +import type { + PopoverProps$1, + BoxProps$1, + InteractionProps, + ComponentChild, + MaybeAllValuesShorthandProperty, +} from './shared.d.ts'; + +export interface PopoverProps + extends Required< + Pick< + PopoverProps$1, + | 'blockSize' + | 'inlineSize' + | 'maxBlockSize' + | 'maxInlineSize' + | 'minBlockSize' + | 'minInlineSize' + > + > {} + +export type CallbackEvent = Event & { + currentTarget: HTMLElementTagNameMap[T]; +}; +export type CallbackToggleEvent< + TTagName extends keyof HTMLElementTagNameMap, + TEvent extends ToggleEvent = ToggleEvent, +> = TEvent & { + currentTarget: HTMLElementTagNameMap[TTagName]; +}; +export type CallbackEventListener = + | (EventListener & { + (event: CallbackEvent): void; + }) + | null; +/** Used when an element does not have children. */ +export interface PreactBaseElementProps { + /** Assigns a unique key to this element. */ + key?: preact.Key; + /** Assigns a ref (generally from `useRef()`) to this element. */ + ref?: preact.Ref; + /** Assigns this element to a parent's slot. */ + slot?: Lowercase; +} +/** Used when an element has children. */ +export interface PreactBaseElementPropsWithChildren + extends PreactBaseElementProps { + children?: preact.ComponentChildren; +} + +export type Styles = string; +export type RenderImpl = Omit & { + ShadowRoot: (element: any) => ComponentChild; + styles?: Styles; +}; +export interface ActivationEventEsque { + shiftKey: boolean; + metaKey: boolean; + ctrlKey: boolean; + button: number; +} +export interface ClickOptions { + /** + * The event you want to influence the synthetic click. + */ + sourceEvent?: ActivationEventEsque; +} +/** + * Base class for creating custom elements with Preact. + * While this class could be used in both Node and the browser, the constructor will only be used in the browser. + * So we give it a type of HTMLElement to avoid typing issues later where it's used, which will only happen in the browser. + */ +declare const BaseClass: typeof globalThis.HTMLElement; +declare abstract class PreactCustomElement extends BaseClass { + /** @private */ + static get observedAttributes(): string[]; + constructor({ + styles, + ShadowRoot: renderFunction, + delegatesFocus, + ...options + }: RenderImpl); + + /** @private */ + setAttribute(name: string, value: string): void; + /** @private */ + attributeChangedCallback(name: string): void; + /** @private */ + connectedCallback(): void; + /** @private */ + disconnectedCallback(): void; + /** @private */ + adoptedCallback(): void; + /** + * Queue a run of the render function. + * You shouldn't need to call this manually - it should be handled by changes to @property values. + * @private + */ + queueRender(): void; + /** + * Like the standard `element.click()`, but you can influence the behavior with a `sourceEvent`. + * + * For example, if the `sourceEvent` was a middle click, or has particular keys held down, + * components will attempt to produce the desired behavior on links, such as opening the page in the background tab. + * @private + * @param options + */ + click({sourceEvent}?: ClickOptions): void; +} + +/** + * Shared symbols for overlay control functionality. + * These symbols are used by components that implement overlay behavior + * (like Popover, Tooltip, etc.) to communicate with the overlay control system. + */ +declare const overlayCommand: unique symbol; +declare const overlayHidden: unique symbol; +declare const overlayActivator: unique symbol; + +declare class PreactOverlayElement extends PreactCustomElement { + constructor(renderImpl: RenderImpl); + /** @private */ + [overlayHidden]: boolean; + /** @private */ + [overlayActivator]: HTMLElement | null | undefined; + /** @private */ + [overlayCommand](command: InteractionProps['command']): void; +} + +export type MakeResponsive = T | `@container${string}`; +/** + * Makes a property's value potentially responsive. + * + * @example + * type Example = { + * color: boolean; + * margin: string; + * padding: number; + * } + * type Result = MakeResponsivePick; + * // Result = { + * color: boolean | `@container${string}`; + * margin: string | `@container${string}`; + * padding: number | `@container${string}`; + * } + */ +export type MakeResponsivePick = { + [P in TProperty]: MakeResponsive; +}; + +export type RequiredBoxProps = Required; +export type BoxBorderRadii = Extract< + RequiredBoxProps['borderRadius'], + | 'none' + | 'small-200' + | 'small-100' + | 'small' + | 'base' + | 'large' + | 'large-100' + | 'large-200' +>; +export type BoxBorderStyles = Extract< + RequiredBoxProps['borderStyle'], + 'none' | 'solid' | 'dashed' | 'auto' +>; +export type ResponsiveBoxProps = MakeResponsivePick< + RequiredBoxProps, + | 'padding' + | 'paddingBlock' + | 'paddingBlockStart' + | 'paddingBlockEnd' + | 'paddingInline' + | 'paddingInlineStart' + | 'paddingInlineEnd' + | 'display' +>; +export interface BoxProps + extends Pick< + RequiredBoxProps, + | 'accessibilityLabel' + | 'accessibilityRole' + | 'accessibilityVisibility' + | 'background' + | 'blockSize' + | 'border' + | 'borderColor' + | 'borderRadius' + | 'borderStyle' + | 'borderWidth' + | 'inlineSize' + | 'maxBlockSize' + | 'maxInlineSize' + | 'minBlockSize' + | 'minInlineSize' + | 'overflow' + > { + background: Extract< + RequiredBoxProps['background'], + 'transparent' | 'base' | 'subdued' | 'strong' + >; + borderWidth: + | MaybeAllValuesShorthandProperty< + Extract< + RequiredBoxProps['borderWidth'], + 'small-100' | 'small' | 'base' | 'large' | 'large-100' | 'none' + > + > + | Extract; + borderStyle: + | MaybeAllValuesShorthandProperty + | Extract; + borderColor: Extract< + RequiredBoxProps['borderColor'], + 'subdued' | 'base' | 'strong' | '' + >; + borderRadius: MaybeAllValuesShorthandProperty; + /** + * Adjust the padding of all edges. + * + * 1-to-4-value syntax (@see https://developer.mozilla.org/en-US/docs/Web/CSS/Shorthand_properties#edges_of_a_box) is + * supported. Note that, contrary to the CSS, it uses flow-relative values and the order is: + * + * - 4 values: `block-start inline-end block-end inline-start` + * - 3 values: `block-start inline block-end` + * - 2 values: `block inline` + * + * For example: + * - `large` means block-start, inline-end, block-end and inline-start paddings are `large`. + * - `large none` means block-start and block-end paddings are `large`, inline-start and inline-end paddings are `none`. + * - `large none large` means block-start padding is `large`, inline-end padding is `none`, block-end padding is `large` and inline-start padding is `none`. + * - `large none large small` means block-start padding is `large`, inline-end padding is `none`, block-end padding is `large` and inline-start padding is `small`. + * + * A padding value of `auto` will use the default padding for the closest container that has had its usual padding removed. + * + * `padding` also accepts a container query string with the supported PaddingKeyword as a query value e.g. (`@container (inline-size > 500px) large-300, small-300`) + * + * This also accepts up to 4 values (e.g. `@container (inline-size > 500px) large-300 small-300 large-100 small-100, small-200`) + * + * @default 'none' + */ + padding: ResponsiveBoxProps['padding']; + /** + * Adjust the block-padding. + * + * - `large none` means block-start padding is `large`, block-end padding is `none`. + * + * This overrides the block value of `padding`. + * + * `paddingBlock` also accepts a container query string with the supported PaddingKeyword as a query value e.g. (`@container (inline-size > 500px) large-300, small-300`) + * + * This also accepts up to 2 values (e.g. `@container (inline-size > 500px) large-300 small-300, small-200`) + * + * @default '' - meaning no override + */ + paddingBlock: ResponsiveBoxProps['paddingBlock']; + /** + * Adjust the block-start padding. + * + * This overrides the block-start value of `paddingBlock`. + * + * `paddingBlockStart` also accepts a container query string with the supported PaddingKeyword as a query value e.g. (`@container (inline-size > 500px) large-300, small-300`) + * + * This only accepts 1 value per predicate (e.g. `@container (inline-size > 500px) large-300, small-200`) + * @default '' - meaning no override + */ + paddingBlockStart: ResponsiveBoxProps['paddingBlockStart']; + /** + * Adjust the block-end padding. + * + * This overrides the block-end value of `paddingBlock`. + * + * `paddingBlockEnd` also accepts a container query string with the supported PaddingKeyword as a query value e.g. (`@container (inline-size > 500px) large-300, small-300`) + * + * This only accepts up to 1 value per predicate (e.g. `@container (inline-size > 500px) large-300, small-200`) + * @default '' - meaning no override + */ + paddingBlockEnd: ResponsiveBoxProps['paddingBlockEnd']; + /** + * Adjust the inline padding. + * + * - `large none` means inline-start padding is `large`, inline-end padding is `none`. + * + * This overrides the inline value of `padding`. + * + * `paddingInline` also accepts a container query string with the supported PaddingKeyword as a query value e.g. (`@container (inline-size > 500px) large-300, small-300`) + * + * This also accepts up to 2 values (e.g. `@container (inline-size > 500px) large-300 small-300, small-200`) + * + * @default '' - meaning no override + */ + paddingInline: ResponsiveBoxProps['paddingInline']; + /** + * Adjust the inline-start padding. + * + * This overrides the inline-start value of `paddingInline`. + * + * `paddingInlineStart` also accepts a container query string with the supported PaddingKeyword as a query value e.g. (`@container (inline-size > 500px) large-300, small-300`) + * This only accepts 1 value per predicate (e.g. `@container (inline-size > 500px) large-300, small-200`) + * + * @default '' - meaning no override + */ + paddingInlineStart: ResponsiveBoxProps['paddingInlineStart']; + /** + * Adjust the inline-end padding. + * + * This overrides the inline-end value of `paddingInline`. + * + * `paddingInlineEnd` also accepts a container query string with the supported PaddingKeyword as a query value e.g. (`@container (inline-size > 500px) large-300, small-300`) + * This only accepts 1 value per predicate (e.g. `@container (inline-size > 500px) large-300, small-200`) + * + * @default '' - meaning no override + */ + paddingInlineEnd: ResponsiveBoxProps['paddingInlineEnd']; + /** + * Sets the outer display type of the component. The outer type sets a component's participation in [flow layout](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_flow_layout). + * + * - `auto` the component's initial value. The actual value depends on the component and context. + * - `none` hides the component from display and removes it from the accessibility tree, making it invisible to screen readers. + * + * @see https://developer.mozilla.org/en-US/docs/Web/CSS/display + * @default 'auto' + */ + display: ResponsiveBoxProps['display']; +} + +declare class PreactPopoverElement + extends PreactOverlayElement + implements PopoverProps +{ + constructor(renderImpl: RenderImpl); + accessor blockSize: BoxProps['blockSize']; + accessor minBlockSize: BoxProps['minBlockSize']; + accessor maxBlockSize: BoxProps['maxBlockSize']; + accessor inlineSize: BoxProps['inlineSize']; + accessor minInlineSize: BoxProps['minInlineSize']; + accessor maxInlineSize: BoxProps['maxInlineSize']; +} + +declare class Popover + extends PreactPopoverElement + implements PopoverProps +{ + constructor(); +} +declare global { + interface HTMLElementTagNameMap { + [tagName]: Popover; + } +} +declare module 'preact' { + namespace createElement.JSX { + interface IntrinsicElements { + [tagName]: PopoverJSXProps & PreactBaseElementPropsWithChildren; + } + } +} + +declare const tagName = 's-popover'; +export interface PopoverJSXProps extends Partial { + id?: string; + onHide?: (event: CallbackEvent) => void | null; + onShow?: (event: CallbackEvent) => void | null; + onAfterHide?: (event: CallbackEvent) => void | null; + onAfterShow?: (event: CallbackEvent) => void | null; + onToggle?: (event: CallbackToggleEvent) => void | null; + onAfterToggle?: (event: CallbackToggleEvent) => void | null; +} + +export {Popover}; +export type {PopoverJSXProps}; diff --git a/packages/ui-extensions/src/surfaces/admin/components/Popover/Popover.ab.doc.ts b/packages/ui-extensions/src/surfaces/admin/components/Popover/Popover.ab.doc.ts new file mode 100644 index 0000000000..2a52dc80f3 --- /dev/null +++ b/packages/ui-extensions/src/surfaces/admin/components/Popover/Popover.ab.doc.ts @@ -0,0 +1,35 @@ +import {ReferenceEntityTemplateSchema} from '@shopify/generate-docs'; +import sharedContent from '../../../../docs/shared/components/Popover'; + +const data: ReferenceEntityTemplateSchema = { + ...sharedContent, + category: 'Polaris web components', + thumbnail: '/assets/templated-apis-screenshots/admin/components/popover.png', + isVisualComponent: true, + definitions: [ + { + title: 'Properties', + description: '', + type: 'Popover', + }, + { + title: 'Events', + description: + 'Learn more about [registering events](/docs/api/app-home/using-polaris-components#event-handling).', + type: 'PopoverEvents', + }, + ], + defaultExample: { + codeblock: { + title: 'Code', + tabs: [ + { + code: './examples/default.html', + language: 'preview', + }, + ], + }, + }, +}; + +export default data; diff --git a/packages/ui-extensions/src/surfaces/admin/components/Popover/examples/default.html b/packages/ui-extensions/src/surfaces/admin/components/Popover/examples/default.html new file mode 100644 index 0000000000..6e49d4fa30 --- /dev/null +++ b/packages/ui-extensions/src/surfaces/admin/components/Popover/examples/default.html @@ -0,0 +1,6 @@ +Product options + + + Import + Export + diff --git a/packages/ui-extensions/src/surfaces/admin/components/StandardComponents.ts b/packages/ui-extensions/src/surfaces/admin/components/StandardComponents.ts index ae05e12863..5d8f1b3cff 100644 --- a/packages/ui-extensions/src/surfaces/admin/components/StandardComponents.ts +++ b/packages/ui-extensions/src/surfaces/admin/components/StandardComponents.ts @@ -18,6 +18,7 @@ export type StandardComponents = | 'Image' | 'Link' | 'ListItem' + | 'Menu' | 'MoneyField' | 'NumberField' | 'Option' diff --git a/packages/ui-extensions/src/surfaces/admin/components/shared.d.ts b/packages/ui-extensions/src/surfaces/admin/components/shared.d.ts index a0b6751669..184b1fa9ef 100644 --- a/packages/ui-extensions/src/surfaces/admin/components/shared.d.ts +++ b/packages/ui-extensions/src/surfaces/admin/components/shared.d.ts @@ -9,6 +9,7 @@ * https://github.com/Shopify/ui-api-design/issues/139 */ export type ComponentChildren = any; +export type StringChildren = string; export interface GlobalProps { /** * A unique identifier for the element. @@ -1853,8 +1854,16 @@ interface ChipProps$1 extends ChipProps$1, GlobalProps {} interface ChoiceProps$1 extends GlobalProps, BaseOptionProps { /** * Content to use as the choice label. + * + * @implementation (StringChildren) The label is produced by extracting and + * concatenating the text nodes from the provided content; any markup or + * element structure is ignored. + * + * @implementation (ComponentChildren) Behaves as a slot: any elements passed + * are rendered as the label content (subject to surface constraints); there + * is no coercion to a string. */ - children?: ComponentChildren; + children?: ComponentChildren | StringChildren; /** * Additional text to provide context or guidance for the input. * @@ -3246,7 +3255,7 @@ interface TableBodyProps$1 extends GlobalProps { } interface TableCellProps$1 extends GlobalProps { /** - * The content of the table data. + * The content of the table cell. */ children?: ComponentChildren; } @@ -3273,6 +3282,16 @@ interface TableHeaderProps$1 extends GlobalProps { * @default 'labeled' */ listSlot?: ListSlotType; + /** + * The format of the column. Will automatically apply styling and alignment to cell content based on the value. + * + * - `base`: The base format for columns. + * - `currency`: Formats the column as currency. + * - `numeric`: Formats the column as a number. + * + * @default 'base' + */ + format?: 'base' | 'currency' | 'numeric'; } interface TableHeaderRowProps$1 extends GlobalProps { /** @@ -3285,6 +3304,17 @@ interface TableRowProps$1 extends GlobalProps { * The content of a TableRow, which should be `TableCell` components. */ children?: ComponentChildren; + /** + * The ID of an interactive element (e.g. `s-link`) in the row that will be the target of the click when the row is clicked. + * This is the primary action for the row; it should not be used for secondary actions. + * + * This is a click-only affordance, and does not introduce any keyboard or screen reader affordances. + * Which is why the target element must be in the table; so that keyboard and screen reader users can interact with it normally. + * + * @implementation no focus or keyboard affordances are introduced by this property. No aria attributes need to be added to the table row. + * @implementation the row and/or delegate should have some affordance that indicates it is clickable. This may be a background color, a border, a hover effect, etc. + */ + clickDelegate?: string; } interface TextProps$1 extends GlobalProps,