Skip to content

Commit

Permalink
Add component KolPopoverButton
Browse files Browse the repository at this point in the history
Refs: #7388
  • Loading branch information
sdvg committed Feb 18, 2025
1 parent c9c1bf7 commit 0d2d84d
Show file tree
Hide file tree
Showing 19 changed files with 463 additions and 65 deletions.
20 changes: 11 additions & 9 deletions packages/components/src/components/component-list.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import { KolAbbr } from './abbr/shadow';
import { KolAccordion } from './accordion/shadow';
import { KolAlertWc } from './alert/component';
import { KolAlert } from './alert/shadow';
import { KolAvatarWc } from './avatar/component';
import { KolAlertWc } from './alert/component';
import { KolAvatar } from './avatar/shadow';
import { KolAvatarWc } from './avatar/component';
import { KolBadge } from './badge/shadow';
import { KolBreadcrumb } from './breadcrumb/shadow';
import { KolButton } from './button/shadow';
import { KolButtonLink } from './button-link/shadow';
import { KolButtonWc } from './button/component';
import { KolButton } from './button/shadow';
import { KolCard } from './card/shadow';
import { KolCombobox } from './combobox/shadow';
import { KolDetails } from './details/shadow';
import { KolDrawer } from './drawer/shadow';
import { KolForm } from './form/shadow';
Expand All @@ -28,31 +29,31 @@ import { KolInputRange } from './input-range/shadow';
import { KolInputText } from './input-text/shadow';
import { KolInputWc } from './input/component';
import { KolKolibri } from './kolibri/shadow';
import { KolLink } from './link/shadow';
import { KolLinkButton } from './link-button/shadow';
import { KolLinkWc } from './link/component';
import { KolLink } from './link/shadow';
import { KolModal } from './modal/shadow';
import { KolNav } from './nav/shadow';
import { KolPagination } from './pagination/shadow';
import { KolPopover } from './popover/component';
import { KolPopoverButton } from './popover-button/shadow';
import { KolProcess } from './progress/shadow';
import { KolQuote } from './quote/shadow';
import { KolSelect } from './select/shadow';
import { KolSingleSelect } from './single-select/shadow';
import { KolSkipNav } from './skip-nav/shadow';
import { KolSpin } from './spin/shadow';
import { KolSingleSelect } from './single-select/shadow';
import { KolSplitButton } from './split-button/shadow';
import { KolTabs } from './tabs/shadow';
import { KolTextarea } from './textarea/shadow';
import { KolToastContainer } from './toaster/shadow';
import { KolToolbar } from './toolbar/shadow';
import { KolTooltipWc } from './tooltip/component';
import { KolVersion } from './version/shadow';
import { KolTree } from './tree/shadow';
import { KolTreeItem } from './tree-item/shadow';
import { KolTreeItemWc } from './tree-item/component';
import { KolTreeWc } from './tree/component';
import { KolCombobox } from './combobox/shadow';
import { KolVersion } from './version/shadow';

export const COMPONENTS = [
KolAbbr,
Expand All @@ -74,7 +75,6 @@ export const COMPONENTS = [
KolHeading,
KolIcon,
KolImage,
KolInputWc,
KolInputCheckbox,
KolInputColor,
KolInputDate,
Expand All @@ -85,6 +85,7 @@ export const COMPONENTS = [
KolInputRadio,
KolInputRange,
KolInputText,
KolInputWc,
KolKolibri,
KolLink,
KolLinkButton,
Expand All @@ -93,12 +94,13 @@ export const COMPONENTS = [
KolNav,
KolPagination,
KolPopover,
KolPopoverButton,
KolProcess,
KolQuote,
KolSelect,
KolSingleSelect,
KolSkipNav,
KolSpin,
KolSingleSelect,
KolSplitButton,
KolTabs,
KolTextarea,
Expand Down
224 changes: 224 additions & 0 deletions packages/components/src/components/popover-button/shadow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
import type { JSX } from '@stencil/core';
import { Component, h, Prop, State, Watch } from '@stencil/core';
import { KolButtonWcTag } from '../../core/component-names';
import { alignFloatingElements } from '../../utils/align-floating-elements';
import type { PopoverButtonProps, PopoverButtonStates } from '../../schema/components/popover-button';
import type {
AccessKeyPropType,
AlternativeButtonLinkRolePropType,
AriaDescriptionPropType,
ButtonCallbacksPropType,
ButtonTypePropType,
ButtonVariantPropType,
CustomClassPropType,
IconsPropType,
LabelWithExpertSlotPropType,
PopoverAlignPropType,
ShortKeyPropType,
StencilUnknown,
Stringified,
SyncValueBySelectorPropType,
TooltipAlignPropType,
} from '../../schema';
import { validatePopoverAlign } from '../../schema';

/**
* @slot - The popover content.
*/
@Component({
tag: 'kol-popover-button',
styleUrls: {
default: './style.scss',
},
shadow: true,
})
// class implementing PopoverButtonProps and not API because we don't want to repeat the entire state and validation for button props
export class KolPopoverButton implements PopoverButtonProps {
private refButton?: HTMLKolButtonWcElement;
private refPopover?: HTMLDivElement;

@State() public state: PopoverButtonStates = {
_label: '',
_popoverAlign: 'bottom',
};

/* Regarding type issue see https://github.com/microsoft/TypeScript/issues/54864 */
private handleToggle(event: Event) {
if ((event as ToggleEvent).newState === 'open' && this.refPopover && this.refButton) {
void alignFloatingElements({
align: this.state._popoverAlign,
floatingElement: this.refPopover,
referenceElement: this.refButton,
});
}
}

public componentDidRender() {
this.refPopover?.addEventListener('toggle', this.handleToggle.bind(this));
}

public disconnectedCallback() {
this.refPopover?.removeEventListener('toggle', this.handleToggle.bind(this));
}

public render(): JSX.Element {
return (
<div class="kol-popover-button">
<KolButtonWcTag
_accessKey={this._accessKey}
_ariaControls={this._ariaControls}
_ariaDescription={this._ariaDescription}
_ariaExpanded={this._ariaExpanded}
_ariaSelected={this._ariaSelected}
_customClass={this._customClass}
_disabled={this._disabled}
_hideLabel={this._hideLabel}
_icons={this._icons}
_id={this._id}
_label={this._label}
_name={this._name}
_on={this._on}
_role={this._role}
_shortKey={this._shortKey}
_syncValueBySelector={this._syncValueBySelector}
_tabIndex={this._tabIndex}
_tooltipAlign={this._tooltipAlign}
_type={this._type}
_value={this._value}
_variant={this._variant}
_popoverTarget="popover"
class="kol-popover-button__button"
ref={(element) => (this.refButton = element)}
>
<slot name="expert" slot="expert"></slot>
</KolButtonWcTag>

<div ref={(element) => (this.refPopover = element)} popover="auto" id="popover" class="kol-popover-button__popover">
<slot />
</div>
</div>
);
}

/**
* Defines which key combination can be used to trigger or focus the interactive element of the component.
*/
@Prop() public _accessKey?: AccessKeyPropType;

/**
* Defines which elements are controlled by this component. (https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-controls)
*/
@Prop() public _ariaControls?: string;

/**
* Defines the value for the aria-description attribute.
*/
@Prop() public _ariaDescription?: AriaDescriptionPropType;

/**
* Defines whether the interactive element of the component expanded something. (https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-expanded)
*/
@Prop() public _ariaExpanded?: boolean;

/**
* Defines whether the interactive element of the component is selected (e.g. role=tab). (https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-selected)
*/
@Prop() public _ariaSelected?: boolean;

/**
* Defines the custom class attribute if _variant="custom" is set.
*/
@Prop() public _customClass?: CustomClassPropType;

/**
* Makes the element not focusable and ignore all events.
*/
@Prop() public _disabled?: boolean = false;

/**
* Hides the caption by default and displays the caption text with a tooltip when the
* interactive element is focused or the mouse is over it.
* @TODO: Change type back to `HideLabelPropType` after Stencil#4663 has been resolved.
*/
@Prop() public _hideLabel?: boolean = false;

/**
* Defines the icon classnames (e.g. `_icons="fa-solid fa-user"`).
*/
@Prop() public _icons?: IconsPropType;

/**
* Defines the internal ID of the primary component element.
*/
@Prop() public _id?: string;

/**
* Defines the visible or semantic label of the component (e.g. aria-label, label, headline, caption, summary, etc.). Set to `false` to enable the expert slot.
*/
@Prop() public _label!: LabelWithExpertSlotPropType;

/**
* Defines the technical name of an input field.
*/
@Prop() public _name?: string;

/**
* Defines the callback functions for button events.
*/
@Prop() public _on?: ButtonCallbacksPropType<StencilUnknown>;

/**
* Defines where to show the Popover preferably: top, right, bottom or left.
*/
@Prop() public _popoverAlign?: PopoverAlignPropType = 'bottom';

/**
* Defines the role of the components primary element.
*/
@Prop() public _role?: AlternativeButtonLinkRolePropType;

/**
* Adds a visual short key hint to the component.
*/
@Prop() public _shortKey?: ShortKeyPropType;

/**
* Selector for synchronizing the value with another input element.
* @internal
*/
@Prop() public _syncValueBySelector?: SyncValueBySelectorPropType;

/**
* Defines which tab-index the primary element of the component has. (https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/tabindex)
*/
@Prop() public _tabIndex?: number;

/**
* Defines where to show the Tooltip preferably: top, right, bottom or left.
*/
@Prop() public _tooltipAlign?: TooltipAlignPropType = 'top';

/**
* Defines either the type of the component or of the components interactive element.
*/
@Prop() public _type?: ButtonTypePropType = 'button';

/**
* Defines the value that the button emits on click.
*/
@Prop() public _value?: Stringified<StencilUnknown>;

/**
* Defines which variant should be used for presentation.
*/
@Prop() public _variant?: ButtonVariantPropType = 'normal';

@Watch('_popoverAlign')
public validatePopoverAlign(value?: PopoverAlignPropType): void {
validatePopoverAlign(this, value);
}

public componentWillLoad() {
this.validatePopoverAlign(this._popoverAlign);
}
}
19 changes: 19 additions & 0 deletions packages/components/src/components/popover-button/style.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
@use '../@shared/mixins' as *;
@use '../../styles/global' as *;
@use '../@shared/kol-button-mixin' as *;

@include kol-button-styles('kol-button');

@layer kol-component {
.kol-popover-button {
&__button {
display: inline-block; // critical for floating UI to work correctly
}

&__popover {
border: 1px solid #000;
margin: 0;
padding: 0;
}
}
}
2 changes: 2 additions & 0 deletions packages/components/src/core/component-names.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export let KolModalTag = 'kol-modal' as const;
export let KolNavTag = 'kol-nav' as const;
export let KolPaginationTag = 'kol-pagination' as const;
export let KolPopoverWcTag = 'kol-popover-wc' as const;
export let KolPopoverButtonTag = 'kol-popover-button' as const;
export let KolProgressTag = 'kol-progress' as const;
export let KolQuoteTag = 'kol-quote' as const;
export let KolSelectTag = 'kol-select' as const;
Expand Down Expand Up @@ -96,6 +97,7 @@ export const setCustomTagNames = (transformTagName: (tagName: string) => string)
KolNavTag = transformTagName(KolNavTag as string) as 'kol-nav';
KolPaginationTag = transformTagName(KolPaginationTag as string) as 'kol-pagination';
KolPopoverWcTag = transformTagName(KolPopoverWcTag as string) as 'kol-popover-wc';
KolPopoverButtonTag = transformTagName(KolPopoverButtonTag as string) as 'kol-popover-button';
KolProgressTag = transformTagName(KolProgressTag as string) as 'kol-progress';
KolQuoteTag = transformTagName(KolQuoteTag as string) as 'kol-quote';
KolSelectTag = transformTagName(KolSelectTag as string) as 'kol-select';
Expand Down
12 changes: 12 additions & 0 deletions packages/components/src/schema/components/popover-button.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type { Generic } from 'adopted-style-sheets';
import type { OptionalButtonProps, RequiredButtonProps } from './button';
import type { PropLabelWithExpertSlot, PropPopoverAlign } from '../props';

export type RequiredPopoverButtonProps = RequiredButtonProps;
export type OptionalPopoverButtonProps = OptionalButtonProps & PropPopoverAlign;

export type RequiredPopoverButtonStates = PropLabelWithExpertSlot;
export type OptionalPopoverButtonStates = PropPopoverAlign;

export type PopoverButtonProps = Generic.Element.Members<RequiredPopoverButtonProps, OptionalPopoverButtonProps>;
export type PopoverButtonStates = Generic.Element.Members<RequiredPopoverButtonStates, OptionalPopoverButtonStates>;
Loading

0 comments on commit 0d2d84d

Please sign in to comment.