diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/components/index.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/components/index.ts index b32058e232..830194fb3f 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/components/index.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/components/index.ts @@ -1 +1 @@ -import './input-user-group/input-user-group.element'; +import './input-user-group/user-group-input.element'; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/components/input-user-group/input-user-group.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/components/input-user-group/input-user-group.element.ts deleted file mode 100644 index 69598dbae1..0000000000 --- a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/components/input-user-group/input-user-group.element.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { UUITextStyles } from '@umbraco-ui/uui-css'; -import { css, html, nothing } from 'lit'; -import { customElement, state } from 'lit/decorators.js'; -import { UmbInputListBaseElement } from '../../../../core/components/input-list-base/input-list-base'; -import { UmbUserGroupStore, UMB_USER_GROUP_STORE_CONTEXT_TOKEN } from '../../repository/user-group.store'; -import type { UserGroupEntity } from '../../types'; -import { UMB_USER_GROUP_PICKER_MODAL } from '@umbraco-cms/backoffice/modal'; - -@customElement('umb-input-user-group') -export class UmbInputPickerUserGroupElement extends UmbInputListBaseElement { - @state() - private _userGroups: Array = []; - - private _userGroupStore?: UmbUserGroupStore; - - connectedCallback(): void { - super.connectedCallback(); - this.pickerToken = UMB_USER_GROUP_PICKER_MODAL; - this.consumeContext(UMB_USER_GROUP_STORE_CONTEXT_TOKEN, (usersContext) => { - this._userGroupStore = usersContext; - //this._observeUserGroups(); - }); - } - - /* - private _observeUserGroups() { - if (this.value.length > 0 && this._userGroupStore) { - this.observe(this._userGroupStore.getByKeys(this.value), (userGroups) => (this._userGroups = userGroups)); - } else { - this._userGroups = []; - } - } - */ - - selectionUpdated() { - //this._observeUserGroups(); - this.dispatchEvent(new CustomEvent('change', { bubbles: true, composed: true })); - } - - private _renderUserGroupList() { - if (this._userGroups.length === 0) return nothing; - - return html`
- ${this._userGroups.map( - (userGroup) => html` -
-
- - ${userGroup.name} -
- this.removeFromSelection(userGroup.id)} - label="remove" - color="danger"> -
- ` - )} -
`; - } - - renderContent() { - return html`${this._renderUserGroupList()}`; - } - - static styles = [ - UUITextStyles, - css` - :host { - display: flex; - flex-direction: column; - gap: var(--uui-size-space-4); - } - #user-group-list { - display: flex; - flex-direction: column; - gap: var(--uui-size-space-4); - } - .user-group { - display: flex; - align-items: center; - gap: var(--uui-size-space-2); - } - .user-group div { - display: flex; - align-items: center; - gap: var(--uui-size-4); - } - .user-group uui-button { - margin-left: auto; - } - `, - ]; -} - -declare global { - interface HTMLElementTagNameMap { - 'umb-input-user-group': UmbInputPickerUserGroupElement; - } -} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/components/input-user-group/input-user-group.stories.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/components/input-user-group/input-user-group.stories.ts deleted file mode 100644 index ba4ced36d1..0000000000 --- a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/components/input-user-group/input-user-group.stories.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Meta, StoryObj } from '@storybook/web-components'; -import './input-user-group.element'; -import type { UmbInputPickerUserGroupElement } from './input-user-group.element'; - -const meta: Meta = { - title: 'Components/Inputs/User Group', - component: 'umb-user-group-input', - argTypes: { - modalType: { - control: 'inline-radio', - options: ['dialog', 'sidebar'], - defaultValue: 'sidebar', - description: 'The type of modal to use when selecting user groups', - }, - modalSize: { - control: 'select', - options: ['small', 'medium', 'large', 'full'], - defaultValue: 'small', - description: 'The size of the modal to use when selecting user groups, only applicable to sidebar not dialog', - }, - }, -}; - -export default meta; -type Story = StoryObj; - -export const Overview: Story = { - args: {}, -}; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/components/input-user-group/user-group-input.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/components/input-user-group/user-group-input.element.ts new file mode 100644 index 0000000000..1905d342ae --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/components/input-user-group/user-group-input.element.ts @@ -0,0 +1,138 @@ +import { css, html } from 'lit'; +import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; +import { customElement, property, state } from 'lit/decorators.js'; +import { FormControlMixin } from '@umbraco-ui/uui-base/lib/mixins'; +import { UmbUserGroupPickerContext } from './user-group-input.context'; +import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; +import type { UserItemResponseModel } from '@umbraco-cms/backoffice/backend-api'; + +@customElement('umb-user-group-input') +export class UmbUserGroupInputElement extends FormControlMixin(UmbLitElement) { + /** + * This is a minimum amount of selected items in this input. + * @type {number} + * @attr + * @default 0 + */ + @property({ type: Number }) + public get min(): number { + return this.#pickerContext.min; + } + public set min(value: number) { + this.#pickerContext.min = value; + } + + /** + * Min validation message. + * @type {boolean} + * @attr + * @default + */ + @property({ type: String, attribute: 'min-message' }) + minMessage = 'This field need more items'; + + /** + * This is a maximum amount of selected items in this input. + * @type {number} + * @attr + * @default Infinity + */ + @property({ type: Number }) + public get max(): number { + return this.#pickerContext.max; + } + public set max(value: number) { + this.#pickerContext.max = value; + } + + /** + * Max validation message. + * @type {boolean} + * @attr + * @default + */ + @property({ type: String, attribute: 'min-message' }) + maxMessage = 'This field exceeds the allowed amount of items'; + + public get selectedIds(): Array { + return this.#pickerContext.getSelection(); + } + public set selectedIds(ids: Array) { + this.#pickerContext.setSelection(ids); + } + + @property() + public set value(idsString: string) { + console.log('set value', idsString); + + // Its with full purpose we don't call super.value, as thats being handled by the observation of the context selection. + this.selectedIds = idsString.split(/[ ,]+/); + } + + @state() + private _items?: Array; + + #pickerContext = new UmbUserGroupPickerContext(this); + + constructor() { + super(); + + this.addValidator( + 'rangeUnderflow', + () => this.minMessage, + () => !!this.min && this.#pickerContext.getSelection().length < this.min + ); + + this.addValidator( + 'rangeOverflow', + () => this.maxMessage, + () => !!this.max && this.#pickerContext.getSelection().length > this.max + ); + + this.observe(this.#pickerContext.selection, (selection) => (super.value = selection.join(','))); + this.observe(this.#pickerContext.selectedItems, (selectedItems) => (this._items = selectedItems)); + } + + protected getFormElement() { + return undefined; + } + + render() { + return html` + ${this._items?.map((item) => this._renderItem(item))} + this.#pickerContext.openPicker()} label="open" + >Add + `; + } + + private _renderItem(item: UserItemResponseModel) { + if (!item.id) return; + return html` + + + this.#pickerContext.requestRemoveItem(item.id!)} label="Remove ${item.name}" + >Remove + + + `; + } + + static styles = [ + UUITextStyles, + css` + #add-button { + width: 100%; + } + `, + ]; +} + +export default UmbUserGroupInputElement; + +declare global { + interface HTMLElementTagNameMap { + 'umb-user-group-input': UmbUserGroupInputElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/components/input-user-group/user-group-input.stories.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/components/input-user-group/user-group-input.stories.ts new file mode 100644 index 0000000000..16c93e313c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/components/input-user-group/user-group-input.stories.ts @@ -0,0 +1,29 @@ +import { Meta, StoryObj } from '@storybook/web-components'; +import './input-user-group.element'; +import type { UmbUserGroupInputElement } from './user-group-input.element'; + +const meta: Meta = { + title: 'Components/Inputs/User Group', + component: 'umb-input-user-group', + argTypes: { + // modalType: { + // control: 'inline-radio', + // options: ['dialog', 'sidebar'], + // defaultValue: 'sidebar', + // description: 'The type of modal to use when selecting user groups', + // }, + // modalSize: { + // control: 'select', + // options: ['small', 'medium', 'large', 'full'], + // defaultValue: 'small', + // description: 'The size of the modal to use when selecting user groups, only applicable to sidebar not dialog', + // }, + }, +}; + +export default meta; +type Story = StoryObj; + +export const Overview: Story = { + args: {}, +}; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/components/input-user-group/input-user-group.test.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/components/input-user-group/user-group-input.test.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/components/input-user-group/input-user-group.test.ts rename to src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/components/input-user-group/user-group-input.test.ts