diff --git a/web/html/src/manager/storybook/index.ts b/web/html/src/manager/storybook/index.ts new file mode 100644 index 000000000000..e6454cf7074d --- /dev/null +++ b/web/html/src/manager/storybook/index.ts @@ -0,0 +1,3 @@ +export default { + storybook: () => import("./storybook.renderer"), +}; diff --git a/web/html/src/manager/storybook/layout.module.less b/web/html/src/manager/storybook/layout.module.less new file mode 100644 index 000000000000..45efd81ba4ad --- /dev/null +++ b/web/html/src/manager/storybook/layout.module.less @@ -0,0 +1,29 @@ +:global(.old-theme), +:global(.new-theme) { + .header { + padding: 8px 16px; + background: #eee; + } + + .section { + display: flex; + flex-direction: column; + gap: 16px; + padding: 16px; + border: 1px solid #eee; + + &:not(:last-child) { + margin-bottom: 16px; + } + } + + .striped { + background: repeating-linear-gradient(-45deg, #fff, #fff 10px, #eee 10px, #eee 12px); + } + + .row { + display: flex; + flex-direction: row; + gap: 16px; + } +} diff --git a/web/html/src/manager/storybook/layout.tsx b/web/html/src/manager/storybook/layout.tsx new file mode 100644 index 000000000000..1f3481c3a800 --- /dev/null +++ b/web/html/src/manager/storybook/layout.tsx @@ -0,0 +1,31 @@ +import React from "react"; + +import styles from "./layout.module.less"; + +type Props = { + children?: React.ReactNode; +}; + +export const StorySection = (props: Props) => { + return ( + <> +
{props.children}
+ + ); +}; + +export const StripedStorySection = (props: Props) => { + return ( + <> +
{props.children}
+ + ); +}; + +type RowProps = { + children?: React.ReactNode; +}; + +export const StoryRow = (props: RowProps) => { + return
{props.children}
; +}; diff --git a/web/html/src/manager/storybook/stories.generated.ts b/web/html/src/manager/storybook/stories.generated.ts new file mode 100644 index 000000000000..bb56637d0967 --- /dev/null +++ b/web/html/src/manager/storybook/stories.generated.ts @@ -0,0 +1,370 @@ +/** + * NB! This is a generated file! + * Any changes you make here will be lost. + * See: web/html/src/build/plugins/generate-stories-plugin.js + */ + +/* eslint-disable */ + +import components_action_ActionStatus_stories_tsx_component from "components/action/ActionStatus.stories.tsx"; +import components_action_ActionStatus_stories_tsx_raw from "components/action/ActionStatus.stories.tsx?raw"; + +export const components_action_ActionStatus_stories_tsx = { + path: "components/action/ActionStatus.stories.tsx", + title: "ActionStatus.stories.tsx", + groupName: "action", + component: components_action_ActionStatus_stories_tsx_component, + raw: components_action_ActionStatus_stories_tsx_raw, +}; + +import components_buttons_stories_tsx_component from "components/buttons.stories.tsx"; +import components_buttons_stories_tsx_raw from "components/buttons.stories.tsx?raw"; + +export const components_buttons_stories_tsx = { + path: "components/buttons.stories.tsx", + title: "buttons.stories.tsx", + groupName: "components", + component: components_buttons_stories_tsx_component, + raw: components_buttons_stories_tsx_raw, +}; + +import components_datetime_DateTimePicker_stories_tsx_component from "components/datetime/DateTimePicker.stories.tsx"; +import components_datetime_DateTimePicker_stories_tsx_raw from "components/datetime/DateTimePicker.stories.tsx?raw"; + +export const components_datetime_DateTimePicker_stories_tsx = { + path: "components/datetime/DateTimePicker.stories.tsx", + title: "DateTimePicker.stories.tsx", + groupName: "datetime", + component: components_datetime_DateTimePicker_stories_tsx_component, + raw: components_datetime_DateTimePicker_stories_tsx_raw, +}; + +import components_datetime_FromNow_stories_tsx_component from "components/datetime/FromNow.stories.tsx"; +import components_datetime_FromNow_stories_tsx_raw from "components/datetime/FromNow.stories.tsx?raw"; + +export const components_datetime_FromNow_stories_tsx = { + path: "components/datetime/FromNow.stories.tsx", + title: "FromNow.stories.tsx", + groupName: "datetime", + component: components_datetime_FromNow_stories_tsx_component, + raw: components_datetime_FromNow_stories_tsx_raw, +}; + +import components_dialog_action_confirm_stories_tsx_component from "components/dialog/action-confirm.stories.tsx"; +import components_dialog_action_confirm_stories_tsx_raw from "components/dialog/action-confirm.stories.tsx?raw"; + +export const components_dialog_action_confirm_stories_tsx = { + path: "components/dialog/action-confirm.stories.tsx", + title: "action-confirm.stories.tsx", + groupName: "dialog", + component: components_dialog_action_confirm_stories_tsx_component, + raw: components_dialog_action_confirm_stories_tsx_raw, +}; + +import components_dialog_delete_stories_tsx_component from "components/dialog/delete.stories.tsx"; +import components_dialog_delete_stories_tsx_raw from "components/dialog/delete.stories.tsx?raw"; + +export const components_dialog_delete_stories_tsx = { + path: "components/dialog/delete.stories.tsx", + title: "delete.stories.tsx", + groupName: "dialog", + component: components_dialog_delete_stories_tsx_component, + raw: components_dialog_delete_stories_tsx_raw, +}; + +import components_input_check_Check_stories_tsx_component from "components/input/check/Check.stories.tsx"; +import components_input_check_Check_stories_tsx_raw from "components/input/check/Check.stories.tsx?raw"; + +export const components_input_check_Check_stories_tsx = { + path: "components/input/check/Check.stories.tsx", + title: "Check.stories.tsx", + groupName: "check", + component: components_input_check_Check_stories_tsx_component, + raw: components_input_check_Check_stories_tsx_raw, +}; + +import components_input_datetime_DateTime_stories_tsx_component from "components/input/datetime/DateTime.stories.tsx"; +import components_input_datetime_DateTime_stories_tsx_raw from "components/input/datetime/DateTime.stories.tsx?raw"; + +export const components_input_datetime_DateTime_stories_tsx = { + path: "components/input/datetime/DateTime.stories.tsx", + title: "DateTime.stories.tsx", + groupName: "datetime", + component: components_input_datetime_DateTime_stories_tsx_component, + raw: components_input_datetime_DateTime_stories_tsx_raw, +}; + +import components_input_form_multi_input_multiple_fields_stories_tsx_component from "components/input/form-multi-input/multiple-fields.stories.tsx"; +import components_input_form_multi_input_multiple_fields_stories_tsx_raw from "components/input/form-multi-input/multiple-fields.stories.tsx?raw"; + +export const components_input_form_multi_input_multiple_fields_stories_tsx = { + path: "components/input/form-multi-input/multiple-fields.stories.tsx", + title: "multiple-fields.stories.tsx", + groupName: "form-multi-input", + component: components_input_form_multi_input_multiple_fields_stories_tsx_component, + raw: components_input_form_multi_input_multiple_fields_stories_tsx_raw, +}; + +import components_input_form_multi_input_single_field_stories_tsx_component from "components/input/form-multi-input/single-field.stories.tsx"; +import components_input_form_multi_input_single_field_stories_tsx_raw from "components/input/form-multi-input/single-field.stories.tsx?raw"; + +export const components_input_form_multi_input_single_field_stories_tsx = { + path: "components/input/form-multi-input/single-field.stories.tsx", + title: "single-field.stories.tsx", + groupName: "form-multi-input", + component: components_input_form_multi_input_single_field_stories_tsx_component, + raw: components_input_form_multi_input_single_field_stories_tsx_raw, +}; + +import components_input_form_multi_input_table_fields_stories_tsx_component from "components/input/form-multi-input/table-fields.stories.tsx"; +import components_input_form_multi_input_table_fields_stories_tsx_raw from "components/input/form-multi-input/table-fields.stories.tsx?raw"; + +export const components_input_form_multi_input_table_fields_stories_tsx = { + path: "components/input/form-multi-input/table-fields.stories.tsx", + title: "table-fields.stories.tsx", + groupName: "form-multi-input", + component: components_input_form_multi_input_table_fields_stories_tsx_component, + raw: components_input_form_multi_input_table_fields_stories_tsx_raw, +}; + +import components_input_form_Form_stories_tsx_component from "components/input/form/Form.stories.tsx"; +import components_input_form_Form_stories_tsx_raw from "components/input/form/Form.stories.tsx?raw"; + +export const components_input_form_Form_stories_tsx = { + path: "components/input/form/Form.stories.tsx", + title: "Form.stories.tsx", + groupName: "form", + component: components_input_form_Form_stories_tsx_component, + raw: components_input_form_Form_stories_tsx_raw, +}; + +import components_input_password_Password_stories_tsx_component from "components/input/password/Password.stories.tsx"; +import components_input_password_Password_stories_tsx_raw from "components/input/password/Password.stories.tsx?raw"; + +export const components_input_password_Password_stories_tsx = { + path: "components/input/password/Password.stories.tsx", + title: "Password.stories.tsx", + groupName: "password", + component: components_input_password_Password_stories_tsx_component, + raw: components_input_password_Password_stories_tsx_raw, +}; + +import components_input_radio_horizontal_open_stories_tsx_component from "components/input/radio/horizontal-open.stories.tsx"; +import components_input_radio_horizontal_open_stories_tsx_raw from "components/input/radio/horizontal-open.stories.tsx?raw"; + +export const components_input_radio_horizontal_open_stories_tsx = { + path: "components/input/radio/horizontal-open.stories.tsx", + title: "horizontal-open.stories.tsx", + groupName: "radio", + component: components_input_radio_horizontal_open_stories_tsx_component, + raw: components_input_radio_horizontal_open_stories_tsx_raw, +}; + +import components_input_radio_horizontal_stories_tsx_component from "components/input/radio/horizontal.stories.tsx"; +import components_input_radio_horizontal_stories_tsx_raw from "components/input/radio/horizontal.stories.tsx?raw"; + +export const components_input_radio_horizontal_stories_tsx = { + path: "components/input/radio/horizontal.stories.tsx", + title: "horizontal.stories.tsx", + groupName: "radio", + component: components_input_radio_horizontal_stories_tsx_component, + raw: components_input_radio_horizontal_stories_tsx_raw, +}; + +import components_input_radio_vertical_open_stories_tsx_component from "components/input/radio/vertical-open.stories.tsx"; +import components_input_radio_vertical_open_stories_tsx_raw from "components/input/radio/vertical-open.stories.tsx?raw"; + +export const components_input_radio_vertical_open_stories_tsx = { + path: "components/input/radio/vertical-open.stories.tsx", + title: "vertical-open.stories.tsx", + groupName: "radio", + component: components_input_radio_vertical_open_stories_tsx_component, + raw: components_input_radio_vertical_open_stories_tsx_raw, +}; + +import components_input_radio_vertical_stories_tsx_component from "components/input/radio/vertical.stories.tsx"; +import components_input_radio_vertical_stories_tsx_raw from "components/input/radio/vertical.stories.tsx?raw"; + +export const components_input_radio_vertical_stories_tsx = { + path: "components/input/radio/vertical.stories.tsx", + title: "vertical.stories.tsx", + groupName: "radio", + component: components_input_radio_vertical_stories_tsx_component, + raw: components_input_radio_vertical_stories_tsx_raw, +}; + +import components_input_range_Range_stories_tsx_component from "components/input/range/Range.stories.tsx"; +import components_input_range_Range_stories_tsx_raw from "components/input/range/Range.stories.tsx?raw"; + +export const components_input_range_Range_stories_tsx = { + path: "components/input/range/Range.stories.tsx", + title: "Range.stories.tsx", + groupName: "range", + component: components_input_range_Range_stories_tsx_component, + raw: components_input_range_Range_stories_tsx_raw, +}; + +import components_input_select_async_stories_tsx_component from "components/input/select/async.stories.tsx"; +import components_input_select_async_stories_tsx_raw from "components/input/select/async.stories.tsx?raw"; + +export const components_input_select_async_stories_tsx = { + path: "components/input/select/async.stories.tsx", + title: "async.stories.tsx", + groupName: "select", + component: components_input_select_async_stories_tsx_component, + raw: components_input_select_async_stories_tsx_raw, +}; + +import components_input_select_custom_stories_tsx_component from "components/input/select/custom.stories.tsx"; +import components_input_select_custom_stories_tsx_raw from "components/input/select/custom.stories.tsx?raw"; + +export const components_input_select_custom_stories_tsx = { + path: "components/input/select/custom.stories.tsx", + title: "custom.stories.tsx", + groupName: "select", + component: components_input_select_custom_stories_tsx_component, + raw: components_input_select_custom_stories_tsx_raw, +}; + +import components_input_select_simple_stories_tsx_component from "components/input/select/simple.stories.tsx"; +import components_input_select_simple_stories_tsx_raw from "components/input/select/simple.stories.tsx?raw"; + +export const components_input_select_simple_stories_tsx = { + path: "components/input/select/simple.stories.tsx", + title: "simple.stories.tsx", + groupName: "select", + component: components_input_select_simple_stories_tsx_component, + raw: components_input_select_simple_stories_tsx_raw, +}; + +import components_input_text_Text_stories_tsx_component from "components/input/text/Text.stories.tsx"; +import components_input_text_Text_stories_tsx_raw from "components/input/text/Text.stories.tsx?raw"; + +export const components_input_text_Text_stories_tsx = { + path: "components/input/text/Text.stories.tsx", + title: "Text.stories.tsx", + groupName: "text", + component: components_input_text_Text_stories_tsx_component, + raw: components_input_text_Text_stories_tsx_raw, +}; + +import components_messages_severities_stories_tsx_component from "components/messages/severities.stories.tsx"; +import components_messages_severities_stories_tsx_raw from "components/messages/severities.stories.tsx?raw"; + +export const components_messages_severities_stories_tsx = { + path: "components/messages/severities.stories.tsx", + title: "severities.stories.tsx", + groupName: "messages", + component: components_messages_severities_stories_tsx_component, + raw: components_messages_severities_stories_tsx_raw, +}; + +import components_messages_utilities_stories_tsx_component from "components/messages/utilities.stories.tsx"; +import components_messages_utilities_stories_tsx_raw from "components/messages/utilities.stories.tsx?raw"; + +export const components_messages_utilities_stories_tsx = { + path: "components/messages/utilities.stories.tsx", + title: "utilities.stories.tsx", + groupName: "messages", + component: components_messages_utilities_stories_tsx_component, + raw: components_messages_utilities_stories_tsx_raw, +}; + +import components_panels_BootstrapPanel_stories_tsx_component from "components/panels/BootstrapPanel.stories.tsx"; +import components_panels_BootstrapPanel_stories_tsx_raw from "components/panels/BootstrapPanel.stories.tsx?raw"; + +export const components_panels_BootstrapPanel_stories_tsx = { + path: "components/panels/BootstrapPanel.stories.tsx", + title: "BootstrapPanel.stories.tsx", + groupName: "panels", + component: components_panels_BootstrapPanel_stories_tsx_component, + raw: components_panels_BootstrapPanel_stories_tsx_raw, +}; + +import components_panels_TopPanel_stories_tsx_component from "components/panels/TopPanel.stories.tsx"; +import components_panels_TopPanel_stories_tsx_raw from "components/panels/TopPanel.stories.tsx?raw"; + +export const components_panels_TopPanel_stories_tsx = { + path: "components/panels/TopPanel.stories.tsx", + title: "TopPanel.stories.tsx", + groupName: "panels", + component: components_panels_TopPanel_stories_tsx_component, + raw: components_panels_TopPanel_stories_tsx_raw, +}; + +import components_toastr_toastr_stories_tsx_component from "components/toastr/toastr.stories.tsx"; +import components_toastr_toastr_stories_tsx_raw from "components/toastr/toastr.stories.tsx?raw"; + +export const components_toastr_toastr_stories_tsx = { + path: "components/toastr/toastr.stories.tsx", + title: "toastr.stories.tsx", + groupName: "toastr", + component: components_toastr_toastr_stories_tsx_component, + raw: components_toastr_toastr_stories_tsx_raw, +}; + +import components_tree_tree_stories_tsx_component from "components/tree/tree.stories.tsx"; +import components_tree_tree_stories_tsx_raw from "components/tree/tree.stories.tsx?raw"; + +export const components_tree_tree_stories_tsx = { + path: "components/tree/tree.stories.tsx", + title: "tree.stories.tsx", + groupName: "tree", + component: components_tree_tree_stories_tsx_component, + raw: components_tree_tree_stories_tsx_raw, +}; + +import components_utils_HelpIcon_stories_tsx_component from "components/utils/HelpIcon.stories.tsx"; +import components_utils_HelpIcon_stories_tsx_raw from "components/utils/HelpIcon.stories.tsx?raw"; + +export const components_utils_HelpIcon_stories_tsx = { + path: "components/utils/HelpIcon.stories.tsx", + title: "HelpIcon.stories.tsx", + groupName: "utils", + component: components_utils_HelpIcon_stories_tsx_component, + raw: components_utils_HelpIcon_stories_tsx_raw, +}; + +import components_utils_HelpLink_stories_tsx_component from "components/utils/HelpLink.stories.tsx"; +import components_utils_HelpLink_stories_tsx_raw from "components/utils/HelpLink.stories.tsx?raw"; + +export const components_utils_HelpLink_stories_tsx = { + path: "components/utils/HelpLink.stories.tsx", + title: "HelpLink.stories.tsx", + groupName: "utils", + component: components_utils_HelpLink_stories_tsx_component, + raw: components_utils_HelpLink_stories_tsx_raw, +}; + +import components_utils_loading_simple_stories_tsx_component from "components/utils/loading/simple.stories.tsx"; +import components_utils_loading_simple_stories_tsx_raw from "components/utils/loading/simple.stories.tsx?raw"; + +export const components_utils_loading_simple_stories_tsx = { + path: "components/utils/loading/simple.stories.tsx", + title: "simple.stories.tsx", + groupName: "loading", + component: components_utils_loading_simple_stories_tsx_component, + raw: components_utils_loading_simple_stories_tsx_raw, +}; + +import components_utils_loading_text_border_stories_tsx_component from "components/utils/loading/text-border.stories.tsx"; +import components_utils_loading_text_border_stories_tsx_raw from "components/utils/loading/text-border.stories.tsx?raw"; + +export const components_utils_loading_text_border_stories_tsx = { + path: "components/utils/loading/text-border.stories.tsx", + title: "text-border.stories.tsx", + groupName: "loading", + component: components_utils_loading_text_border_stories_tsx_component, + raw: components_utils_loading_text_border_stories_tsx_raw, +}; + +import components_utils_loading_text_stories_tsx_component from "components/utils/loading/text.stories.tsx"; +import components_utils_loading_text_stories_tsx_raw from "components/utils/loading/text.stories.tsx?raw"; + +export const components_utils_loading_text_stories_tsx = { + path: "components/utils/loading/text.stories.tsx", + title: "text.stories.tsx", + groupName: "loading", + component: components_utils_loading_text_stories_tsx_component, + raw: components_utils_loading_text_stories_tsx_raw, +}; \ No newline at end of file diff --git a/web/html/src/manager/storybook/stories.ts b/web/html/src/manager/storybook/stories.ts new file mode 100644 index 000000000000..3580ea03c530 --- /dev/null +++ b/web/html/src/manager/storybook/stories.ts @@ -0,0 +1,5 @@ +import * as generatedStories from "./stories.generated"; + +const storyGroups = Object.groupBy(Object.values(generatedStories), (item) => item.groupName); + +export default Object.entries(storyGroups).map(([title, stories]) => ({ title, stories })); diff --git a/web/html/src/manager/storybook/storybook.module.less b/web/html/src/manager/storybook/storybook.module.less new file mode 100644 index 000000000000..da4763d8a6f2 --- /dev/null +++ b/web/html/src/manager/storybook/storybook.module.less @@ -0,0 +1,29 @@ +:global(.old-theme), +:global(.new-theme) { + .header { + // position: sticky; + top: 8px; + padding-bottom: 8px; + background: #fff; + z-index: 1; + } + + .story { + display: flex; + gap: 16px; + + > div { + flex: 1 0 auto; + } + + > pre { + flex: 1 1 auto; + overflow: auto; + max-width: 50%; + + code { + white-space: pre; + } + } + } +} diff --git a/web/html/src/manager/storybook/storybook.renderer.tsx b/web/html/src/manager/storybook/storybook.renderer.tsx new file mode 100644 index 000000000000..b0d3cb350413 --- /dev/null +++ b/web/html/src/manager/storybook/storybook.renderer.tsx @@ -0,0 +1,16 @@ +import * as React from "react"; + +import SpaRenderer from "core/spa/spa-renderer"; + +import { MessagesContainer } from "components/toastr"; + +import { Storybook } from "./storybook"; + +export const renderer = (id: string) => + SpaRenderer.renderNavigationReact( + <> + + + , + document.getElementById(id) + ); diff --git a/web/html/src/manager/storybook/storybook.tsx b/web/html/src/manager/storybook/storybook.tsx new file mode 100644 index 000000000000..a11d3671c195 --- /dev/null +++ b/web/html/src/manager/storybook/storybook.tsx @@ -0,0 +1,112 @@ +import { Fragment, useEffect, useState } from "react"; + +import debugUtils from "core/debugUtils"; + +import { Button } from "components/buttons"; +import { IconTag } from "components/icontag"; + +import { StoryRow } from "./layout"; +import stories from "./stories"; +import styles from "./storybook.module.less"; + +const STORAGE_KEY = "storybook-show-code"; + +export const Storybook = () => { + const [_hash, setHash] = useState(window.location.hash); + const hash = _hash.replace(/^#/, ""); + const normalize = (input: string = "") => input.replaceAll(" ", "-").toLowerCase(); + + const activeTabHash = normalize(hash) || normalize(stories[0]?.title); + + const [, _invalidate] = useState(0); + const invalidate = () => _invalidate((ii) => ii + 1); + + const [showCode, _setShowCode] = useState(!!localStorage.getItem(STORAGE_KEY)); + const setShowCode = (value: boolean) => { + _setShowCode(value); + if (value) { + localStorage.setItem(STORAGE_KEY, "true"); + } else { + localStorage.removeItem(STORAGE_KEY); + } + }; + + useEffect(() => { + const listener = () => setHash(window.location.hash); + window.addEventListener("hashchange", listener); + return () => { + window.removeEventListener("hashchange", listener); + }; + }, []); + + return ( + <> +
+

+ + {t("Development debugging page")} +

+

{t("This is a hidden page used by developers, if you found it by accident, good job!")}

+

+ {document.body.className} +

+ + +
+ +
+
    + {stories + .sort((a, b) => a.title.localeCompare(b.title)) + .map((item) => { + const tabHash = normalize(item.title); + return ( +
  • + {item.title} +
  • + ); + })} +
+
+ + {stories.map((group) => ( +
+ {normalize(group.title) === activeTabHash && group.stories?.map((item) => ( + +

+ {item.title} +

+
+
{item.component ? : null}
+ {showCode ? ( +
+                    {item.raw}
+                  
+ ) : null} +
+
+
+ ))} +
+ ))} + + ); +};