From 830689f1cb9552348c9f7b166e7fa7328fa9abd6 Mon Sep 17 00:00:00 2001 From: Ken Sternberg <133134217+kensternberg-authentik@users.noreply.github.com> Date: Tue, 23 Jan 2024 08:54:34 -0800 Subject: [PATCH] web: bad default in select (#8258) * web: fix event propogation in search-select wrappers Two different patches, an older one that extracted long search blocks that were cut-and-pasted into a standalone component, and a newer one that fixed displaying placeholder values properly, conflicted and broke a relationship that allowed for the values to be propagated through those standalone components correctly. This restores the event handling and updates the listener set-ups with more idiomatic hooks into Lit's event system. * Updated search-select to properly render with Storybook, and provided a foundation for testing the Search-Select component with Storybook. * Accidentally deleted this line while making Sonar accept my test data. * Fixing a small issue that's bugged me for awhile: there's no reason to manually duplicate what code can duplicate. * Provided a storybook for testing out the flow search. Discovered along the way that I'd mis-used a prop-drilling technique which caused the currentFlow to be "undefined" when pass forward, giving rise to Marc's bug. I *think* this shakes out the last of the bugs. Events are passed up correctly and the initial value is recorded correctly. * Added comments and prettier had opinions. * Restoring old variable names; they didn't have to change after all. * fix lint Signed-off-by: Jens Langhammer --------- Signed-off-by: Jens Langhammer Co-authored-by: Jens Langhammer --- web/.eslintrc.precommit.json | 1 + web/.storybook/css-import-maps.ts | 189 +++++++----------- web/scripts/build-storybook-import-maps.ts | 12 +- .../admin/common/ak-flow-search/FlowSearch.ts | 4 +- .../ak-flow-search/ak-flow-search.stories.ts | 136 +++++++++++++ .../stories/ak-search-select.stories.ts | 145 ++++++++++++++ .../forms/SearchSelect/ak-search-select.ts | 11 +- 7 files changed, 371 insertions(+), 127 deletions(-) create mode 100644 web/src/admin/common/ak-flow-search/ak-flow-search.stories.ts create mode 100644 web/src/components/stories/ak-search-select.stories.ts diff --git a/web/.eslintrc.precommit.json b/web/.eslintrc.precommit.json index 1e2b810a12..c2c06589ce 100644 --- a/web/.eslintrc.precommit.json +++ b/web/.eslintrc.precommit.json @@ -24,6 +24,7 @@ "semi": ["error", "always"], "@typescript-eslint/ban-ts-comment": "off", "sonarjs/cognitive-complexity": ["error", 9], + "sonarjs/no-duplicate-string": "off", "sonarjs/no-nested-template-literals": "off" } } diff --git a/web/.storybook/css-import-maps.ts b/web/.storybook/css-import-maps.ts index 13b55284e9..3941f30c73 100644 --- a/web/.storybook/css-import-maps.ts +++ b/web/.storybook/css-import-maps.ts @@ -7,123 +7,72 @@ // Sometime around 2030 or so, the Javascript community may finally get its collective act together // and we'll have one unified way of doing this. I can only hope. -export const cssImportMaps = { - 'import AKGlobal from "@goauthentik/common/styles/authentik.css";': - 'import AKGlobal from "@goauthentik/common/styles/authentik.css?inline";', - 'import PFAlert from "@patternfly/patternfly/components/Alert/alert.css";': - 'import PFAlert from "@patternfly/patternfly/components/Alert/alert.css?inline";', - 'import PFAlertGroup from "@patternfly/patternfly/components/AlertGroup/alert-group.css";': - 'import PFAlertGroup from "@patternfly/patternfly/components/AlertGroup/alert-group.css?inline";', - 'import PFAvatar from "@patternfly/patternfly/components/Avatar/avatar.css";': - 'import PFAvatar from "@patternfly/patternfly/components/Avatar/avatar.css?inline";', - 'import PFBackdrop from "@patternfly/patternfly/components/Backdrop/backdrop.css";': - 'import PFBackdrop from "@patternfly/patternfly/components/Backdrop/backdrop.css?inline";', - 'import PFBackgroundImage from "@patternfly/patternfly/components/BackgroundImage/background-image.css";': - 'import PFBackgroundImage from "@patternfly/patternfly/components/BackgroundImage/background-image.css?inline";', - 'import PFBanner from "@patternfly/patternfly/components/Banner/banner.css";': - 'import PFBanner from "@patternfly/patternfly/components/Banner/banner.css?inline";', - 'import PFBase from "@patternfly/patternfly/patternfly-base.css";': - 'import PFBase from "@patternfly/patternfly/patternfly-base.css?inline";', - 'import PFBrand from "@patternfly/patternfly/components/Brand/brand.css";': - 'import PFBrand from "@patternfly/patternfly/components/Brand/brand.css?inline";', - 'import PFBullseye from "@patternfly/patternfly/layouts/Bullseye/bullseye.css";': - 'import PFBullseye from "@patternfly/patternfly/layouts/Bullseye/bullseye.css?inline";', - 'import PFButton from "@patternfly/patternfly/components/Button/button.css";': - 'import PFButton from "@patternfly/patternfly/components/Button/button.css?inline";', - 'import PFCard from "@patternfly/patternfly/components/Card/card.css";': - 'import PFCard from "@patternfly/patternfly/components/Card/card.css?inline";', - 'import PFCheck from "@patternfly/patternfly/components/Check/check.css";': - 'import PFCheck from "@patternfly/patternfly/components/Check/check.css?inline";', - 'import PFChip from "@patternfly/patternfly/components/Chip/chip.css";': - 'import PFChip from "@patternfly/patternfly/components/Chip/chip.css?inline";', - 'import PFChipGroup from "@patternfly/patternfly/components/ChipGroup/chip-group.css";': - 'import PFChipGroup from "@patternfly/patternfly/components/ChipGroup/chip-group.css?inline";', - 'import PFContent from "@patternfly/patternfly/components/Content/content.css";': - 'import PFContent from "@patternfly/patternfly/components/Content/content.css?inline";', - 'import PFDataList from "@patternfly/patternfly/components/DataList/data-list.css";': - 'import PFDataList from "@patternfly/patternfly/components/DataList/data-list.css?inline";', - 'import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css";': - 'import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css?inline";', - 'import PFDisplay from "@patternfly/patternfly/utilities/Display/display.css";': - 'import PFDisplay from "@patternfly/patternfly/utilities/Display/display.css?inline";', - 'import PFDrawer from "@patternfly/patternfly/components/Drawer/drawer.css";': - 'import PFDrawer from "@patternfly/patternfly/components/Drawer/drawer.css?inline";', - 'import PFDropdown from "@patternfly/patternfly/components/Dropdown/dropdown.css";': - 'import PFDropdown from "@patternfly/patternfly/components/Dropdown/dropdown.css?inline";', - 'import PFEmptyState from "@patternfly/patternfly/components/EmptyState/empty-state.css";': - 'import PFEmptyState from "@patternfly/patternfly/components/EmptyState/empty-state.css?inline";', - 'import PFExpandableSection from "@patternfly/patternfly/components/ExpandableSection/expandable-section.css";': - 'import PFExpandableSection from "@patternfly/patternfly/components/ExpandableSection/expandable-section.css?inline";', - 'import PFFAIcons from "@patternfly/patternfly/base/patternfly-fa-icons.css";': - 'import PFFAIcons from "@patternfly/patternfly/base/patternfly-fa-icons.css?inline";', - 'import PFFlex from "@patternfly/patternfly/layouts/Flex/flex.css";': - 'import PFFlex from "@patternfly/patternfly/layouts/Flex/flex.css?inline";', - 'import PFForm from "@patternfly/patternfly/components/Form/form.css";': - 'import PFForm from "@patternfly/patternfly/components/Form/form.css?inline";', - 'import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css";': - 'import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css?inline";', - 'import PFGallery from "@patternfly/patternfly/layouts/Gallery/gallery.css";': - 'import PFGallery from "@patternfly/patternfly/layouts/Gallery/gallery.css?inline";', - 'import PFGlobal from "@patternfly/patternfly/patternfly-base.css";': - 'import PFGlobal from "@patternfly/patternfly/patternfly-base.css?inline";', - 'import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css";': - 'import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css?inline";', - 'import PFHint from "@patternfly/patternfly/components/Hint/hint.css";': - 'import PFHint from "@patternfly/patternfly/components/Hint/hint.css?inline";', - 'import PFInputGroup from "@patternfly/patternfly/components/InputGroup/input-group.css";': - 'import PFInputGroup from "@patternfly/patternfly/components/InputGroup/input-group.css?inline";', - 'import PFLabel from "@patternfly/patternfly/components/Label/label.css";': - 'import PFLabel from "@patternfly/patternfly/components/Label/label.css?inline";', - 'import PFList from "@patternfly/patternfly/components/List/list.css";': - 'import PFList from "@patternfly/patternfly/components/List/list.css?inline";', - 'import PFLogin from "@patternfly/patternfly/components/Login/login.css";': - 'import PFLogin from "@patternfly/patternfly/components/Login/login.css?inline";', - 'import PFModalBox from "@patternfly/patternfly/components/ModalBox/modal-box.css";': - 'import PFModalBox from "@patternfly/patternfly/components/ModalBox/modal-box.css?inline";', - 'import PFNav from "@patternfly/patternfly/components/Nav/nav.css";': - 'import PFNav from "@patternfly/patternfly/components/Nav/nav.css?inline";', - 'import PFNotificationBadge from "@patternfly/patternfly/components/NotificationBadge/notification-badge.css";': - 'import PFNotificationBadge from "@patternfly/patternfly/components/NotificationBadge/notification-badge.css?inline";', - 'import PFNotificationDrawer from "@patternfly/patternfly/components/NotificationDrawer/notification-drawer.css";': - 'import PFNotificationDrawer from "@patternfly/patternfly/components/NotificationDrawer/notification-drawer.css?inline";', - 'import PFPage from "@patternfly/patternfly/components/Page/page.css";': - 'import PFPage from "@patternfly/patternfly/components/Page/page.css?inline";', - 'import PFPagination from "@patternfly/patternfly/components/Pagination/pagination.css";': - 'import PFPagination from "@patternfly/patternfly/components/Pagination/pagination.css?inline";', - 'import PFProgressStepper from "@patternfly/patternfly/components/ProgressStepper/progress-stepper.css";': - 'import PFProgressStepper from "@patternfly/patternfly/components/ProgressStepper/progress-stepper.css?inline";', - 'import PFRadio from "@patternfly/patternfly/components/Radio/radio.css";': - 'import PFRadio from "@patternfly/patternfly/components/Radio/radio.css?inline";', - 'import PFSelect from "@patternfly/patternfly/components/Select/select.css";': - 'import PFSelect from "@patternfly/patternfly/components/Select/select.css?inline";', - 'import PFSidebar from "@patternfly/patternfly/components/Sidebar/sidebar.css";': - 'import PFSidebar from "@patternfly/patternfly/components/Sidebar/sidebar.css?inline";', - 'import PFSizing from "@patternfly/patternfly/utilities/Sizing/sizing.css";': - 'import PFSizing from "@patternfly/patternfly/utilities/Sizing/sizing.css?inline";', - 'import PFSpacing from "@patternfly/patternfly/utilities/Spacing/spacing.css";': - 'import PFSpacing from "@patternfly/patternfly/utilities/Spacing/spacing.css?inline";', - 'import PFSpinner from "@patternfly/patternfly/components/Spinner/spinner.css";': - 'import PFSpinner from "@patternfly/patternfly/components/Spinner/spinner.css?inline";', - 'import PFStack from "@patternfly/patternfly/layouts/Stack/stack.css";': - 'import PFStack from "@patternfly/patternfly/layouts/Stack/stack.css?inline";', - 'import PFSwitch from "@patternfly/patternfly/components/Switch/switch.css";': - 'import PFSwitch from "@patternfly/patternfly/components/Switch/switch.css?inline";', - 'import PFTable from "@patternfly/patternfly/components/Table/table.css";': - 'import PFTable from "@patternfly/patternfly/components/Table/table.css?inline";', - 'import PFTabs from "@patternfly/patternfly/components/Tabs/tabs.css";': - 'import PFTabs from "@patternfly/patternfly/components/Tabs/tabs.css?inline";', - 'import PFTitle from "@patternfly/patternfly/components/Title/title.css";': - 'import PFTitle from "@patternfly/patternfly/components/Title/title.css?inline";', - 'import PFToggleGroup from "@patternfly/patternfly/components/ToggleGroup/toggle-group.css";': - 'import PFToggleGroup from "@patternfly/patternfly/components/ToggleGroup/toggle-group.css?inline";', - 'import PFToolbar from "@patternfly/patternfly/components/Toolbar/toolbar.css";': - 'import PFToolbar from "@patternfly/patternfly/components/Toolbar/toolbar.css?inline";', - 'import PFTreeView from "@patternfly/patternfly/components/TreeView/tree-view.css";': - 'import PFTreeView from "@patternfly/patternfly/components/TreeView/tree-view.css?inline";', - 'import PFWizard from "@patternfly/patternfly/components/Wizard/wizard.css";': - 'import PFWizard from "@patternfly/patternfly/components/Wizard/wizard.css?inline";', - 'import ThemeDark from "@goauthentik/common/styles/theme-dark.css";': - 'import ThemeDark from "@goauthentik/common/styles/theme-dark.css?inline";', - 'import styles from "./LibraryPageImpl.css";': - 'import styles from "./LibraryPageImpl.css?inline";', -}; +const rawCssImportMaps = [ + 'import AKGlobal from "@goauthentik/common/styles/authentik.css";', + 'import PFAlert from "@patternfly/patternfly/components/Alert/alert.css";', + 'import PFAlertGroup from "@patternfly/patternfly/components/AlertGroup/alert-group.css";', + 'import PFAvatar from "@patternfly/patternfly/components/Avatar/avatar.css";', + 'import PFBackdrop from "@patternfly/patternfly/components/Backdrop/backdrop.css";', + 'import PFBackgroundImage from "@patternfly/patternfly/components/BackgroundImage/background-image.css";', + 'import PFBanner from "@patternfly/patternfly/components/Banner/banner.css";', + 'import PFBase from "@patternfly/patternfly/patternfly-base.css";', + 'import PFBrand from "@patternfly/patternfly/components/Brand/brand.css";', + 'import PFBullseye from "@patternfly/patternfly/layouts/Bullseye/bullseye.css";', + 'import PFButton from "@patternfly/patternfly/components/Button/button.css";', + 'import PFCard from "@patternfly/patternfly/components/Card/card.css";', + 'import PFCheck from "@patternfly/patternfly/components/Check/check.css";', + 'import PFChip from "@patternfly/patternfly/components/Chip/chip.css";', + 'import PFChipGroup from "@patternfly/patternfly/components/ChipGroup/chip-group.css";', + 'import PFContent from "@patternfly/patternfly/components/Content/content.css";', + 'import PFDataList from "@patternfly/patternfly/components/DataList/data-list.css";', + 'import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css";', + 'import PFDisplay from "@patternfly/patternfly/utilities/Display/display.css";', + 'import PFDrawer from "@patternfly/patternfly/components/Drawer/drawer.css";', + 'import PFDropdown from "@patternfly/patternfly/components/Dropdown/dropdown.css";', + 'import PFEmptyState from "@patternfly/patternfly/components/EmptyState/empty-state.css";', + 'import PFExpandableSection from "@patternfly/patternfly/components/ExpandableSection/expandable-section.css";', + 'import PFFAIcons from "@patternfly/patternfly/base/patternfly-fa-icons.css";', + 'import PFFlex from "@patternfly/patternfly/layouts/Flex/flex.css";', + 'import PFForm from "@patternfly/patternfly/components/Form/form.css";', + 'import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css";', + 'import PFGallery from "@patternfly/patternfly/layouts/Gallery/gallery.css";', + 'import PFGlobal from "@patternfly/patternfly/patternfly-base.css";', + 'import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css";', + 'import PFHint from "@patternfly/patternfly/components/Hint/hint.css";', + 'import PFInputGroup from "@patternfly/patternfly/components/InputGroup/input-group.css";', + 'import PFLabel from "@patternfly/patternfly/components/Label/label.css";', + 'import PFList from "@patternfly/patternfly/components/List/list.css";', + 'import PFLogin from "@patternfly/patternfly/components/Login/login.css";', + 'import PFModalBox from "@patternfly/patternfly/components/ModalBox/modal-box.css";', + 'import PFNav from "@patternfly/patternfly/components/Nav/nav.css";', + 'import PFNotificationBadge from "@patternfly/patternfly/components/NotificationBadge/notification-badge.css";', + 'import PFNotificationDrawer from "@patternfly/patternfly/components/NotificationDrawer/notification-drawer.css";', + 'import PFPage from "@patternfly/patternfly/components/Page/page.css";', + 'import PFPagination from "@patternfly/patternfly/components/Pagination/pagination.css";', + 'import PFProgressStepper from "@patternfly/patternfly/components/ProgressStepper/progress-stepper.css";', + 'import PFRadio from "@patternfly/patternfly/components/Radio/radio.css";', + 'import PFSelect from "@patternfly/patternfly/components/Select/select.css";', + 'import PFSidebar from "@patternfly/patternfly/components/Sidebar/sidebar.css";', + 'import PFSizing from "@patternfly/patternfly/utilities/Sizing/sizing.css";', + 'import PFSpacing from "@patternfly/patternfly/utilities/Spacing/spacing.css";', + 'import PFSpinner from "@patternfly/patternfly/components/Spinner/spinner.css";', + 'import PFStack from "@patternfly/patternfly/layouts/Stack/stack.css";', + 'import PFSwitch from "@patternfly/patternfly/components/Switch/switch.css";', + 'import PFTable from "@patternfly/patternfly/components/Table/table.css";', + 'import PFTabs from "@patternfly/patternfly/components/Tabs/tabs.css";', + 'import PFTitle from "@patternfly/patternfly/components/Title/title.css";', + 'import PFToggleGroup from "@patternfly/patternfly/components/ToggleGroup/toggle-group.css";', + 'import PFToolbar from "@patternfly/patternfly/components/Toolbar/toolbar.css";', + 'import PFTreeView from "@patternfly/patternfly/components/TreeView/tree-view.css";', + 'import PFWizard from "@patternfly/patternfly/components/Wizard/wizard.css";', + 'import ThemeDark from "@goauthentik/common/styles/theme-dark.css";', + 'import styles from "./LibraryPageImpl.css";', +]; + +const cssImportMaps = rawCssImportMaps.reduce( + (acc, line) => ({ ...acc, [line]: line.replace(/\.css/, ".css?inline") }), + {}, +); + +export { cssImportMaps }; +export default cssImportMaps; diff --git a/web/scripts/build-storybook-import-maps.ts b/web/scripts/build-storybook-import-maps.ts index 121ad105fb..6789fda74e 100644 --- a/web/scripts/build-storybook-import-maps.ts +++ b/web/scripts/build-storybook-import-maps.ts @@ -39,7 +39,7 @@ function createOneImportLine(line: string) { if (!importContent) { throw new Error("How did an unmatchable line get here!?"); } - return `'${importContent}";': '${importContent}?inline";',`; + return ` '${importContent}";',`; } const isSourceFile = /\.ts$/; @@ -73,9 +73,15 @@ const outputFile = ` // Sometime around 2030 or so, the Javascript community may finally get its collective act together // and we'll have one unified way of doing this. I can only hope. -export const cssImportMaps = { +const rawCssImportMaps = [ ${importLines.map(createOneImportLine).join("\n")} -}; +]; + +const cssImportMaps = rawCssImportMaps.reduce((acc, line) => ( +{...acc, [line]: line.replace(/\\.css/, ".css?inline")}), {}); + +export { cssImportMaps }; +export default cssImportMaps; `; fs.writeFileSync(path.join(__dirname, "..", ".storybook", "css-import-maps.ts"), outputFile, { diff --git a/web/src/admin/common/ak-flow-search/FlowSearch.ts b/web/src/admin/common/ak-flow-search/FlowSearch.ts index d7338c0c81..960c0f8099 100644 --- a/web/src/admin/common/ak-flow-search/FlowSearch.ts +++ b/web/src/admin/common/ak-flow-search/FlowSearch.ts @@ -46,8 +46,8 @@ export class FlowSearch extends CustomListenerElement(AKElement) * * @attr */ - @property({ attribute: false }) - currentFlow: string | undefined; + @property({ type: String }) + currentFlow?: string | undefined; /** * If true, it is not valid to leave the flow blank. diff --git a/web/src/admin/common/ak-flow-search/ak-flow-search.stories.ts b/web/src/admin/common/ak-flow-search/ak-flow-search.stories.ts new file mode 100644 index 0000000000..698b0cb42b --- /dev/null +++ b/web/src/admin/common/ak-flow-search/ak-flow-search.stories.ts @@ -0,0 +1,136 @@ +import "@goauthentik/admin/common/ak-flow-search/ak-flow-search"; +import { AkFlowSearch } from "@goauthentik/admin/common/ak-flow-search/ak-flow-search"; +import "@goauthentik/elements/forms/HorizontalFormElement"; +import { Meta } from "@storybook/web-components"; + +import { msg } from "@lit/localize"; +import { TemplateResult, html } from "lit"; + +import { Flow, FlowsInstancesListDesignationEnum } from "@goauthentik/api"; + +const mockData = { + pagination: { + next: 0, + previous: 0, + count: 2, + current: 1, + total_pages: 1, + start_index: 1, + end_index: 2, + }, + results: [ + { + pk: "41468774-bef6-4ffb-b675-332d0d8c5d25", + policybindingmodel_ptr_id: "0fb5b872-2734-44bd-ac7e-f23051481a83", + name: "Authorize Application", + slug: "default-provider-authorization-explicit-consent", + title: "Redirecting to %(app)s", + designation: "authorization", + background: "/static/dist/assets/images/flow_background.jpg", + stages: ["8adcdc74-0d3d-48a8-b628-38e3da4081e5"], + policies: [], + cache_count: 0, + policy_engine_mode: "any", + compatibility_mode: false, + export_url: + "/api/v3/flows/instances/default-provider-authorization-explicit-consent/export/", + layout: "stacked", + denied_action: "message_continue", + authentication: "require_authenticated", + }, + { + pk: "89f57fd8-fd1e-42be-a5fd-abc13b19529b", + policybindingmodel_ptr_id: "e8526408-c6ee-46e1-bbfe-a1d37c2c02c8", + name: "Authorize Application", + slug: "default-provider-authorization-implicit-consent", + title: "Redirecting to %(app)s", + designation: "authorization", + background: "/static/dist/assets/images/flow_background.jpg", + stages: [], + policies: [], + cache_count: 0, + policy_engine_mode: "any", + compatibility_mode: false, + export_url: + "/api/v3/flows/instances/default-provider-authorization-implicit-consent/export/", + layout: "stacked", + denied_action: "message_continue", + authentication: "require_authenticated", + }, + ], +}; + +const metadata: Meta> = { + title: "Elements / Select Search / Flow", + component: "ak-flow-search", + parameters: { + docs: { + description: { + component: "A Select Search for Authentication Flows", + }, + }, + mockData: [ + { + url: `${window.location.origin}/api/v3/flows/instances/?designation=authorization&ordering=slug`, + method: "GET", + status: 200, + response: () => mockData, + }, + ], + }, +}; + +export default metadata; + +const container = (testItem: TemplateResult) => { + return html`
+ + ${testItem} +
    +
    `; +}; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const displayChange = (ev: any) => { + document.getElementById("message-pad")!.innerText = `Value selected: ${JSON.stringify( + ev.target.value, + null, + 2, + )}`; +}; + +export const Default = () => + container( + html` + `, + ); + +export const WithInitialValue = () => + container( + html` + `, + ); diff --git a/web/src/components/stories/ak-search-select.stories.ts b/web/src/components/stories/ak-search-select.stories.ts new file mode 100644 index 0000000000..0f0e5edc2b --- /dev/null +++ b/web/src/components/stories/ak-search-select.stories.ts @@ -0,0 +1,145 @@ +import { groupBy } from "@goauthentik/app/common/utils"; +import { convertToSlug as slugify } from "@goauthentik/common/utils.js"; +import "@goauthentik/elements/forms/SearchSelect/ak-search-select"; +import { SearchSelect } from "@goauthentik/elements/forms/SearchSelect/ak-search-select"; +import { Meta } from "@storybook/web-components"; + +import { TemplateResult, html } from "lit"; + +type RawSample = [string, string[]]; +type Sample = { name: string; pk: string; season: string[] }; + +// prettier-ignore +const groupedSamples: RawSample[] = [ + ["Spring", [ + "Apples", "Apricots", "Asparagus", "Avocados", "Bananas", "Broccoli", + "Cabbage", "Carrots", "Celery", "Collard Greens", "Garlic", "Herbs", "Kale", "Kiwifruit", "Lemons", + "Lettuce", "Limes", "Mushrooms", "Onions", "Peas", "Pineapples", "Radishes", "Rhubarb", "Spinach", + "Strawberries", "Swiss Chard", "Turnips"]], + ["Summer", [ + "Apples", "Apricots", "Avocados", "Bananas", "Beets", "Bell Peppers", "Blackberries", "Blueberries", + "Cantaloupe", "Carrots", "Celery", "Cherries", "Corn", "Cucumbers", "Eggplant", "Garlic", + "Green Beans", "Herbs", "Honeydew Melon", "Lemons", "Lima Beans", "Limes", "Mangos", "Okra", "Peaches", + "Plums", "Raspberries", "Strawberries", "Summer Squash", "Tomatillos", "Tomatoes", "Watermelon", + "Zucchini"]], + ["Fall", [ + "Apples", "Bananas", "Beets", "Bell Peppers", "Broccoli", "Brussels Sprouts", "Cabbage", "Carrots", + "Cauliflower", "Celery", "Collard Greens", "Cranberries", "Garlic", "Ginger", "Grapes", "Green Beans", + "Herbs", "Kale", "Kiwifruit", "Lemons", "Lettuce", "Limes", "Mangos", "Mushrooms", "Onions", + "Parsnips", "Pears", "Peas", "Pineapples", "Potatoes", "Pumpkin", "Radishes", "Raspberries", + "Rutabagas", "Spinach", "Sweet Potatoes", "Swiss Chard", "Turnips", "Winter Squash"]], + ["Winter", [ + "Apples", "Avocados", "Bananas", "Beets", "Brussels Sprouts", "Cabbage", "Carrots", "Celery", + "Collard Greens", "Grapefruit", "Herbs", "Kale", "Kiwifruit", "Leeks", "Lemons", "Limes", "Onions", + "Oranges", "Parsnips", "Pears", "Pineapples", "Potatoes", "Pumpkin", "Rutabagas", + "Sweet Potatoes", "Swiss Chard", "Turnips", "Winter Squash"]] +]; + +// WAAAAY too many lines to turn the arrays above into a Sample of +// { name: "Apricots", pk: "apple", season: ["Spring", "Summer"] } +// but it does the job. + +const samples = Array.from( + groupedSamples + .reduce((acc, sample) => { + sample[1].forEach((item) => { + const update = (thing: Sample) => ({ + ...thing, + season: [...thing.season, sample[0]], + }); + acc.set( + item, + update(acc.get(item) || { name: item, pk: slugify(item), season: [] }), + ); + return acc; + }, acc); + return acc; + }, new Map()) + .values(), +); +samples.sort((a, b) => (a < b ? -1 : a > b ? 1 : 0)); + +// All we need is a promise to return our dataset. It doesn't have to be a class-based method a'la +// the authentik API. + +const getSamples = (query = "") => + Promise.resolve( + samples.filter((s) => + query !== "" ? s.name.toLowerCase().includes(query.toLowerCase()) : true, + ), + ); + +const metadata: Meta> = { + title: "Elements / Search Select ", + component: "ak-search-select", + parameters: { + docs: { + description: { + component: "An implementation of the Patternfly search select pattern", + }, + }, + }, +}; + +export default metadata; + +const container = (testItem: TemplateResult) => + html`
    + + + ${testItem} + +
      +
      `; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const displayChange = (ev: any) => { + document.getElementById("message-pad")!.innerText = `Value selected: ${JSON.stringify( + ev.detail.value, + null, + 2, + )}`; +}; + +export const Default = () => { + return container( + html` sample.name} + .value=${(sample: Sample) => sample.pk} + @ak-change=${displayChange} + >`, + ); +}; + +export const Grouped = () => { + return container( + html` sample.name} + .value=${(sample: Sample) => sample.pk} + .groupBy=${(samples: Sample[]) => + groupBy(samples, (sample: Sample) => sample.season[0] ?? "")} + @ak-change=${displayChange} + >`, + ); +}; + +export const Selected = () => { + return container( + html` sample.name} + .value=${(sample: Sample) => sample.pk} + .selected=${(sample: Sample) => sample.pk === "herbs"} + @ak-change=${displayChange} + >`, + ); +}; diff --git a/web/src/elements/forms/SearchSelect/ak-search-select.ts b/web/src/elements/forms/SearchSelect/ak-search-select.ts index 67e362577d..94fb1a1db9 100644 --- a/web/src/elements/forms/SearchSelect/ak-search-select.ts +++ b/web/src/elements/forms/SearchSelect/ak-search-select.ts @@ -3,6 +3,7 @@ import { PreventFormSubmit } from "@goauthentik/app/elements/forms/helpers"; import { EVENT_REFRESH } from "@goauthentik/common/constants"; import { ascii_letters, digits, groupBy, randomString } from "@goauthentik/common/utils"; import { AKElement } from "@goauthentik/elements/Base"; +import { ensureCSSStyleSheet } from "@goauthentik/elements/utils/ensureCSSStyleSheet"; import { CustomEmitterElement } from "@goauthentik/elements/utils/eventEmitter"; import { msg, str } from "@lit/localize"; @@ -105,12 +106,17 @@ export class SearchSelect extends CustomEmitterElement(AKElement) { @state() error?: APIErrorTypes; - static styles = [PFBase, PFForm, PFFormControl, PFSelect]; + static get styles() { + return [PFBase, PFForm, PFFormControl, PFSelect]; + } constructor() { super(); if (!document.adoptedStyleSheets.includes(PFDropdown)) { - document.adoptedStyleSheets = [...document.adoptedStyleSheets, PFDropdown]; + document.adoptedStyleSheets = [ + ...document.adoptedStyleSheets, + ensureCSSStyleSheet(PFDropdown), + ]; } this.dropdownContainer = document.createElement("div"); this.observer = new IntersectionObserver(() => { @@ -150,6 +156,7 @@ export class SearchSelect extends CustomEmitterElement(AKElement) { objects.forEach((obj) => { if (this.selected && this.selected(obj, objects || [])) { this.selectedObject = obj; + this.dispatchCustomEvent("ak-change", { value: this.selectedObject }); } }); this.objects = objects;