From 93cd63aca941d4289a59da394b176e356acd0760 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Pretto?= Date: Fri, 27 Sep 2024 17:08:20 +0200 Subject: [PATCH 01/19] test(sdk): test that "Custom" fonts work with the sdk (#48160) --- .../metabase-sdk-styles-tests.cy.spec.ts | 45 +++++++++++++++++++ .../tests/styling-sdk-tests.stories.tsx | 2 +- 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/e2e/test/scenarios/embedding-sdk/metabase-sdk-styles-tests.cy.spec.ts b/e2e/test/scenarios/embedding-sdk/metabase-sdk-styles-tests.cy.spec.ts index f3b05af7126be..0f7c2702ea501 100644 --- a/e2e/test/scenarios/embedding-sdk/metabase-sdk-styles-tests.cy.spec.ts +++ b/e2e/test/scenarios/embedding-sdk/metabase-sdk-styles-tests.cy.spec.ts @@ -166,5 +166,50 @@ describeSDK("scenarios > embedding-sdk > static-dashboard", () => { '"Roboto Mono", sans-serif', ); }); + + it("should work with 'Custom' fontFamily, using the font files linked in the instance", () => { + cy.signInAsAdmin(); + + const fontUrl = + Cypress.config().baseUrl + + "/app/fonts/Open_Sans/OpenSans-Regular.woff2"; + // setting `application-font-files` will make getFont return "Custom" + cy.request("PUT", "/api/setting/application-font-files", { + value: [ + { + src: fontUrl, + fontWeight: 400, + fontFormat: "woff2", + }, + ], + }); + + cy.signOut(); + + cy.intercept("GET", fontUrl).as("fontFile"); + + visitFullAppEmbeddingUrl({ + url: EMBEDDING_SDK_STORY_HOST, + qs: { + id: STORIES.NO_STYLES_SUCCESS, + viewMode: "story", + }, + onBeforeLoad: (window: any) => { + window.JWT_SHARED_SECRET = JWT_SHARED_SECRET; + window.METABASE_INSTANCE_URL = Cypress.config().baseUrl; + window.QUESTION_ID = ORDERS_QUESTION_ID; + }, + }); + + // this test only tests if the file is loaded, not really if it is rendered + // we'll probably need visual regression tests for that + cy.wait("@fontFile"); + + cy.findByText("Product ID").should( + "have.css", + "font-family", + "Custom, sans-serif", + ); + }); }); }); diff --git a/enterprise/frontend/src/embedding-sdk/tests/styling-sdk-tests.stories.tsx b/enterprise/frontend/src/embedding-sdk/tests/styling-sdk-tests.stories.tsx index ef9b445fcd761..163ce7954ea24 100644 --- a/enterprise/frontend/src/embedding-sdk/tests/styling-sdk-tests.stories.tsx +++ b/enterprise/frontend/src/embedding-sdk/tests/styling-sdk-tests.stories.tsx @@ -11,7 +11,7 @@ export default { const configThatWillError: SDKConfig = { apiKey: "TEST", - metabaseInstanceUrl: "http://localhost", + metabaseInstanceUrl: "http://fake-host:1234", }; /** From e8deecc58f3804f1710e92f779a7d1e90cbba39e Mon Sep 17 00:00:00 2001 From: Braden Shepherdson Date: Fri, 27 Sep 2024 13:38:06 -0400 Subject: [PATCH 02/19] [QP] Don't attach `:temporal-unit :default` to every temporal clause (#48085) This was old logic to support certain drivers (eg. pre-JDBC Druid) and isn't required for most. It's perfectly sound to filter or even break out on a datetime column without bucketing. Adds a new `:temporal/requires-default-unit` driver feature, and enables it only for the legacy Druid driver. Fixes #47341 --- docs/developers-guide/driver-changelog.md | 5 ++ .../binning/binning-reproductions.cy.spec.js | 2 +- .../custom-column/custom-column.cy.spec.js | 2 +- .../multiple-column-breakouts.cy.spec.ts | 65 ++++++-------- .../column_extract_drill.cy.spec.js | 2 +- .../row_level_restrictions_test.clj | 9 +- .../druid/src/metabase/driver/druid.clj | 7 +- .../metabase/driver/druid/query_processor.clj | 7 +- src/metabase/driver.clj | 5 ++ src/metabase/lib/expression.cljc | 7 ++ src/metabase/lib/field.cljc | 22 ++--- src/metabase/lib/temporal_bucket.cljc | 24 ++++++ .../middleware/add_default_temporal_unit.clj | 17 +++- .../middleware/add_implicit_clauses.clj | 4 +- .../middleware/wrap_value_literals.clj | 17 +--- test/metabase/lib/temporal_bucket_test.cljc | 11 ++- .../add_default_temporal_unit_test.clj | 85 ------------------- .../middleware/add_implicit_clauses_test.clj | 2 +- .../middleware/add_implicit_joins_test.clj | 4 +- .../middleware/add_source_metadata_test.clj | 8 +- .../middleware/resolve_joins_test.clj | 2 +- test/metabase/query_processor/test_util.clj | 5 +- .../util/add_alias_info_test.clj | 6 +- .../query_processor/util/nest_query_test.clj | 6 +- .../nested_queries_test.clj | 6 +- 25 files changed, 129 insertions(+), 201 deletions(-) delete mode 100644 test/metabase/query_processor/middleware/add_default_temporal_unit_test.clj diff --git a/docs/developers-guide/driver-changelog.md b/docs/developers-guide/driver-changelog.md index fad52eda73072..d48ebf16b6b78 100644 --- a/docs/developers-guide/driver-changelog.md +++ b/docs/developers-guide/driver-changelog.md @@ -126,6 +126,11 @@ title: Driver interface changelog - `:test/dynamic-dataset-loading` feature has been added. It enables drivers to bail out of tests that require creation of new, not pre-loaded, dataset during test run time. +- The `:temporal/requires-default-unit` feature has been added. It should be false for most drivers, but it's necessary + for a few (like the old, pre-JDBC Druid driver) to find all temporal field refs and put a `:temporal-unit :default` on them. + That default setting was previously done for all drivers, but it introduced some downstream issues, so now only those + drivers which need it can set the feature. + ## Metabase 0.50.17 - Added method `metabase.driver/incorporate-auth-provider-details` for driver specific behavior required to diff --git a/e2e/test/scenarios/binning/binning-reproductions.cy.spec.js b/e2e/test/scenarios/binning/binning-reproductions.cy.spec.js index fe880333dae64..56ead25977729 100644 --- a/e2e/test/scenarios/binning/binning-reproductions.cy.spec.js +++ b/e2e/test/scenarios/binning/binning-reproductions.cy.spec.js @@ -48,7 +48,7 @@ describe("binning related reproductions", () => { // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage cy.findByText(/CREATED_AT/i).realHover(); // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage - cy.findByText("by day").click({ force: true }); + cy.findByText("by month").click({ force: true }); // Implicit assertion - it fails if there is more than one instance of the string, which is exactly what we need for this repro // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage diff --git a/e2e/test/scenarios/custom-column/custom-column.cy.spec.js b/e2e/test/scenarios/custom-column/custom-column.cy.spec.js index 85207891ff577..6efec2cecd056 100644 --- a/e2e/test/scenarios/custom-column/custom-column.cy.spec.js +++ b/e2e/test/scenarios/custom-column/custom-column.cy.spec.js @@ -146,7 +146,7 @@ describe("scenarios > question > custom column", () => { .click(); getNotebookStep("summarize") - .findByText("Product Date: Day") + .findByText("Product Date: Month") .should("be.visible"); }); diff --git a/e2e/test/scenarios/question/multiple-column-breakouts.cy.spec.ts b/e2e/test/scenarios/question/multiple-column-breakouts.cy.spec.ts index 38055af3ec53a..39d0596929b8b 100644 --- a/e2e/test/scenarios/question/multiple-column-breakouts.cy.spec.ts +++ b/e2e/test/scenarios/question/multiple-column-breakouts.cy.spec.ts @@ -272,6 +272,19 @@ function getNestedQuestionDetails(cardId: number) { }; } +// This is used in several places for the same query. +function assertTableDataForFilteredTemporalBreakouts() { + assertTableData({ + columns: ["Created At: Year", "Created At: Month", "Count"], + firstRows: [ + ["2023", "March 2023", "256"], + ["2023", "April 2023", "238"], + ["2023", "May 2023", "271"], + ], + }); + assertQueryBuilderRowCount(3); +} + describe("scenarios > question > multiple column breakouts", () => { beforeEach(() => { restore(); @@ -776,16 +789,16 @@ describe("scenarios > question > multiple column breakouts", () => { }); assertTableData({ columns: [ - "Created At", - "Created At", + "Created At: Year", + "Created At: Month", "Count", "Expression1", "Expression2", ], firstRows: [ [ - "January 1, 2022, 12:00 AM", - "April 1, 2022, 12:00 AM", + "2022", + "April 2022", "1", "January 1, 2023, 12:00 AM", "May 1, 2022, 12:00 AM", @@ -955,15 +968,7 @@ describe("scenarios > question > multiple column breakouts", () => { column2MinValue: "March 1, 2023", column2MaxValue: "May 31, 2023", }); - assertTableData({ - columns: ["Created At", "Created At", "Count"], - firstRows: [ - ["January 1, 2023, 12:00 AM", "March 1, 2023, 12:00 AM", "256"], - ["January 1, 2023, 12:00 AM", "April 1, 2023, 12:00 AM", "238"], - ["January 1, 2023, 12:00 AM", "May 1, 2023, 12:00 AM", "271"], - ], - }); - assertQueryBuilderRowCount(3); + assertTableDataForFilteredTemporalBreakouts(); cy.log("'num-bins' breakouts"); testNumericPostAggregationFilter({ @@ -1285,15 +1290,7 @@ describe("scenarios > question > multiple column breakouts", () => { column2MinValue: "March 1, 2023", column2MaxValue: "May 31, 2023", }); - assertTableData({ - columns: ["Created At", "Created At", "Count"], - firstRows: [ - ["January 1, 2023, 12:00 AM", "March 1, 2023, 12:00 AM", "256"], - ["January 1, 2023, 12:00 AM", "April 1, 2023, 12:00 AM", "238"], - ["January 1, 2023, 12:00 AM", "May 1, 2023, 12:00 AM", "271"], - ], - }); - assertQueryBuilderRowCount(3); + assertTableDataForFilteredTemporalBreakouts(); cy.log("'num-bins' breakouts"); testNumericPostAggregationFilter({ @@ -1393,8 +1390,8 @@ describe("scenarios > question > multiple column breakouts", () => { questionDetails: multiStageQuestionWith2TemporalBreakoutsDetails, queryColumn1Name: "Created At: Year", queryColumn2Name: "Created At: Month", - tableColumn1Name: "Created At", - tableColumn2Name: "Created At", + tableColumn1Name: "Created At: Year", + tableColumn2Name: "Created At: Month", }); cy.log("'num-bins' breakouts"); @@ -1564,15 +1561,7 @@ describe("scenarios > question > multiple column breakouts", () => { column2MinValue: "March 1, 2023", column2MaxValue: "May 31, 2023", }); - assertTableData({ - columns: ["Created At", "Created At", "Count"], - firstRows: [ - ["January 1, 2023, 12:00 AM", "March 1, 2023, 12:00 AM", "256"], - ["January 1, 2023, 12:00 AM", "April 1, 2023, 12:00 AM", "238"], - ["January 1, 2023, 12:00 AM", "May 1, 2023, 12:00 AM", "271"], - ], - }); - assertQueryBuilderRowCount(3); + assertTableDataForFilteredTemporalBreakouts(); cy.log("'num-bins' breakouts"); testNumericPostAggregationFilter({ @@ -1784,8 +1773,10 @@ describe("scenarios > question > multiple column breakouts", () => { visitQuestion: true, }); }); + const columnNameYear = columnName + ": Year"; + const columnNameMonth = columnName + ": Month"; assertTableData({ - columns: [columnName, columnName, "Count"], + columns: [columnNameYear, columnNameMonth, "Count"], }); cy.findByTestId("viz-settings-button").click(); @@ -1794,7 +1785,7 @@ describe("scenarios > question > multiple column breakouts", () => { .click(); toggleColumn(columnName, 0, false); cy.wait("@dataset"); - assertTableData({ columns: [columnName, "Count"] }); + assertTableData({ columns: [columnNameMonth, "Count"] }); toggleColumn(columnName, 1, false); cy.wait("@dataset"); @@ -1802,11 +1793,11 @@ describe("scenarios > question > multiple column breakouts", () => { toggleColumn(columnName, 0, true); cy.wait("@dataset"); - assertTableData({ columns: ["Count", columnName] }); + assertTableData({ columns: ["Count", columnNameYear] }); toggleColumn(columnName, 1, true); assertTableData({ - columns: ["Count", columnName, columnName], + columns: ["Count", columnNameYear, columnNameMonth], }); } diff --git a/e2e/test/scenarios/visualizations-tabular/drillthroughs/column_extract_drill.cy.spec.js b/e2e/test/scenarios/visualizations-tabular/drillthroughs/column_extract_drill.cy.spec.js index 9617ad422e7b5..d3c157b59e034 100644 --- a/e2e/test/scenarios/visualizations-tabular/drillthroughs/column_extract_drill.cy.spec.js +++ b/e2e/test/scenarios/visualizations-tabular/drillthroughs/column_extract_drill.cy.spec.js @@ -204,7 +204,7 @@ describeWithSnowplow("extract action", () => { it("should add an expression based on an aggregation column", () => { cy.createQuestion(DATE_QUESTION, { visitQuestion: true }); extractColumnAndCheck({ - column: "Min of Created At: Default", + column: "Min of Created At", option: "Year", value: "2,022", extraction: "Extract day, month…", diff --git a/enterprise/backend/test/metabase_enterprise/sandbox/query_processor/middleware/row_level_restrictions_test.clj b/enterprise/backend/test/metabase_enterprise/sandbox/query_processor/middleware/row_level_restrictions_test.clj index deb146fe8fa8e..8f94870b413fa 100644 --- a/enterprise/backend/test/metabase_enterprise/sandbox/query_processor/middleware/row_level_restrictions_test.clj +++ b/enterprise/backend/test/metabase_enterprise/sandbox/query_processor/middleware/row_level_restrictions_test.clj @@ -185,8 +185,15 @@ (is (=? (mt/query checkins {:type :query :query {:source-query {:source-table $$checkins - :fields [$id !default.$date $user_id $venue_id] + :fields [$id $date $user_id $venue_id] :filter [:and + ;; This still gets :default bucketing! + ;; auto-bucket-datetimes puts :day bucketing + ;; on both parts of this filter, since it's + ;; matching a YYYY-mm-dd string. Then + ;; optimize-temporal-filters sees that the + ;; :type/Date column already has :day + ;; granularity, and switches both to :default [:> !default.date [:absolute-datetime #t "2014-01-01" :default]] diff --git a/modules/drivers/druid/src/metabase/driver/druid.clj b/modules/drivers/druid/src/metabase/driver/druid.clj index 584af57040439..2a5e7155edd54 100644 --- a/modules/drivers/druid/src/metabase/driver/druid.clj +++ b/modules/drivers/druid/src/metabase/driver/druid.clj @@ -13,9 +13,10 @@ (driver/register! :druid) -(doseq [[feature supported?] {:expression-aggregations true - :schemas false - :set-timezone true}] +(doseq [[feature supported?] {:expression-aggregations true + :schemas false + :set-timezone true + :temporal/requires-default-unit true}] (defmethod driver/database-supports? [:druid feature] [_driver _feature _db] supported?)) (defmethod driver/can-connect? :druid diff --git a/modules/drivers/druid/src/metabase/driver/druid/query_processor.clj b/modules/drivers/druid/src/metabase/driver/druid/query_processor.clj index 5b76ebacc9334..95aaba9b7bbdc 100644 --- a/modules/drivers/druid/src/metabase/driver/druid/query_processor.clj +++ b/modules/drivers/druid/src/metabase/driver/druid/query_processor.clj @@ -936,9 +936,10 @@ :extractionFn (unit->extraction-fn unit)}) (defmethod ->dimension-rvalue :field - [[_ _ {:keys [temporal-unit]} :as clause]] - (if temporal-unit - (temporal-dimension-rvalue temporal-unit) + [[_ _ {:keys [base-type temporal-unit]} :as clause]] + (if (or temporal-unit + (isa? base-type :type/Temporal)) + (temporal-dimension-rvalue (or temporal-unit :default)) (->rvalue clause))) (defmulti ^:private handle-breakout diff --git a/src/metabase/driver.clj b/src/metabase/driver.clj index c663a778aa5e5..e6eaf52ab5101 100644 --- a/src/metabase/driver.clj +++ b/src/metabase/driver.clj @@ -677,6 +677,11 @@ ;; Does this driver support UUID type :uuid-type + ;; True if this driver requires `:temporal-unit :default` on all temporal field refs, even if no temporal + ;; bucketing was specified in the query. + ;; Generally false, but a few time-series based analytics databases (eg. Druid) require it. + :temporal/requires-default-unit + ;; Does this driver support window functions like cumulative count and cumulative sum? (default: false) :window-functions/cumulative diff --git a/src/metabase/lib/expression.cljc b/src/metabase/lib/expression.cljc index 635a87706fcf8..476303fcb0995 100644 --- a/src/metabase/lib/expression.cljc +++ b/src/metabase/lib/expression.cljc @@ -70,6 +70,13 @@ (when-let [unit (lib.temporal-bucket/raw-temporal-bucket expression-ref-clause)] {:metabase.lib.field/temporal-unit unit}))) +(defmethod lib.temporal-bucket/available-temporal-buckets-method :expression + [query stage-number [_expression opts _expr-name, :as expr-clause]] + (lib.temporal-bucket/available-temporal-buckets-for-type + (lib.metadata.calculation/type-of query stage-number expr-clause) + :month + (:temporal-unit opts))) + (defmethod lib.metadata.calculation/display-name-method :dispatch-type/integer [_query _stage-number n _style] (str n)) diff --git a/src/metabase/lib/field.cljc b/src/metabase/lib/field.cljc index 9e14b4219b5fe..34ac4d3234e15 100644 --- a/src/metabase/lib/field.cljc +++ b/src/metabase/lib/field.cljc @@ -348,25 +348,13 @@ 365 :week :month)))))) -(defn- mark-unit [options option-key unit] - (cond->> options - (some #(= (:unit %) unit) options) - (mapv (fn [option] - (cond-> option - (contains? option option-key) (dissoc option option-key) - (= (:unit option) unit) (assoc option-key true)))))) - (defmethod lib.temporal-bucket/available-temporal-buckets-method :metadata/column [_query _stage-number field-metadata] - (let [effective-type ((some-fn :effective-type :base-type) field-metadata) - fingerprint-default (some-> field-metadata :fingerprint fingerprint-based-default-unit)] - (cond-> (cond - (isa? effective-type :type/DateTime) lib.temporal-bucket/datetime-bucket-options - (isa? effective-type :type/Date) lib.temporal-bucket/date-bucket-options - (isa? effective-type :type/Time) lib.temporal-bucket/time-bucket-options - :else []) - fingerprint-default (mark-unit :default fingerprint-default) - (::temporal-unit field-metadata) (mark-unit :selected (::temporal-unit field-metadata))))) + (lib.temporal-bucket/available-temporal-buckets-for-type + ((some-fn :effective-type :base-type) field-metadata) + (or (some-> field-metadata :fingerprint fingerprint-based-default-unit) + :month) + (::temporal-unit field-metadata))) ;;; ---------------------------------------- Binning --------------------------------------------- diff --git a/src/metabase/lib/temporal_bucket.cljc b/src/metabase/lib/temporal_bucket.cljc index 01fbe95f0ca7a..9a48aab6066f3 100644 --- a/src/metabase/lib/temporal_bucket.cljc +++ b/src/metabase/lib/temporal_bucket.cljc @@ -227,6 +227,30 @@ [_query _stage-number _x] #{}) +(defn- mark-unit [options option-key unit] + (cond->> options + (some #(= (:unit %) unit) options) + (mapv (fn [option] + (cond-> option + (contains? option option-key) (dissoc option option-key) + (= (:unit option) unit) (assoc option-key true)))))) + +(defn available-temporal-buckets-for-type + "Given the type of this column and nillable `default-unit` and `selected-unit`s, return the correct list of buckets." + [column-type default-unit selected-unit] + (let [options (cond + (isa? column-type :type/DateTime) datetime-bucket-options + (isa? column-type :type/Date) date-bucket-options + (isa? column-type :type/Time) time-bucket-options + :else []) + fallback-unit (if (isa? column-type :type/Time) + :hour + :month) + default-unit (or default-unit fallback-unit)] + (cond-> options + default-unit (mark-unit :default default-unit) + selected-unit (mark-unit :selected selected-unit)))) + (mu/defn available-temporal-buckets :- [:sequential [:ref ::lib.schema.temporal-bucketing/option]] "Get a set of available temporal bucketing units for `x`. Returns nil if no units are available." ([query x] diff --git a/src/metabase/query_processor/middleware/add_default_temporal_unit.clj b/src/metabase/query_processor/middleware/add_default_temporal_unit.clj index 28dfb5b68dd2f..ec72f398d6be3 100644 --- a/src/metabase/query_processor/middleware/add_default_temporal_unit.clj +++ b/src/metabase/query_processor/middleware/add_default_temporal_unit.clj @@ -1,14 +1,13 @@ (ns metabase.query-processor.middleware.add-default-temporal-unit (:require + [metabase.driver :as driver] + [metabase.driver.util :as driver.u] [metabase.legacy-mbql.util :as mbql.u] [metabase.lib.metadata :as lib.metadata] [metabase.lib.util.match :as lib.util.match] [metabase.query-processor.store :as qp.store])) -(defn add-default-temporal-unit - "Add `:temporal-unit` `:default` to any temporal `:field` clauses that don't already have a `:temporal-unit`. This - makes things more consistent because code downstream can rely on the key being present." - [query] +(defn- add-default-temporal-unit* [query] (lib.util.match/replace-in query [:query] [:field (_ :guard string?) (_ :guard (every-pred :base-type @@ -20,3 +19,13 @@ (let [{:keys [base-type effective-type]} (lib.metadata/field (qp.store/metadata-provider) id)] (cond-> &match (isa? (or effective-type base-type) :type/Temporal) (mbql.u/with-temporal-unit :default))))) + +(defn add-default-temporal-unit + "Add `:temporal-unit` `:default` to any temporal `:field` clauses that don't already have a `:temporal-unit`. This + makes things more consistent because code downstream can rely on the key being present. + + Only activates for drivers with the `:temporal/requires-default-unit` feature." + [query] + (let [database (lib.metadata/database (qp.store/metadata-provider))] + (cond-> query + (driver.u/supports? driver/*driver* :temporal/requires-default-unit database) add-default-temporal-unit*))) diff --git a/src/metabase/query_processor/middleware/add_implicit_clauses.clj b/src/metabase/query_processor/middleware/add_implicit_clauses.clj index 8e9cec02ccfe5..4152473839cf5 100644 --- a/src/metabase/query_processor/middleware/add_implicit_clauses.clj +++ b/src/metabase/query_processor/middleware/add_implicit_clauses.clj @@ -6,7 +6,6 @@ [metabase.legacy-mbql.util :as mbql.u] [metabase.lib.metadata :as lib.metadata] [metabase.lib.schema.id :as lib.schema.id] - [metabase.lib.types.isa :as lib.types.isa] [metabase.lib.util.match :as lib.util.match] [metabase.query-processor.error-type :as qp.error-type] [metabase.query-processor.store :as qp.store] @@ -40,8 +39,7 @@ (fn [field] ;; implicit datetime Fields get bucketing of `:default`. This is so other middleware doesn't try to give it ;; default bucketing of `:day` - [:field (u/the-id field) (when (lib.types.isa/temporal? field) - {:temporal-unit :default})]) + [:field (u/the-id field) nil]) fields))) (defn- multiply-bucketed-field-refs diff --git a/src/metabase/query_processor/middleware/wrap_value_literals.clj b/src/metabase/query_processor/middleware/wrap_value_literals.clj index 8b1ee12419d52..409f80409652c 100644 --- a/src/metabase/query_processor/middleware/wrap_value_literals.clj +++ b/src/metabase/query_processor/middleware/wrap_value_literals.clj @@ -39,15 +39,8 @@ (defmethod type-info :metadata/column [field] ;; Opts should probably override all of these - (let [field-info (-> (select-keys field [:base-type :effective-type :coercion-strategy :semantic-type :database-type :name]) - (update-keys u/->snake_case_en))] - (merge - field-info - ;; add in a default unit for this Field so we know to wrap datetime strings in `absolute-datetime` below based on - ;; its presence. Its unit will get replaced by the`:temporal-unit` in `:field` options in the method below if - ;; present - (when (types/temporal-field? field-info) - {:unit :default})))) + (-> (select-keys field [:base-type :effective-type :coercion-strategy :semantic-type :database-type :name]) + (update-keys u/->snake_case_en))) (defn- str-id-field->type-info "Return _type info_ for `_field` with string `field-name`, coming from the source query or joins." @@ -79,8 +72,6 @@ (defmethod type-info :expression [[_ _name opts]] (merge - (when (isa? (:base-type opts) :type/Temporal) - {:unit :default}) (when (:temporal-unit opts) {:unit (:temporal-unit opts)}) (when (:base-type opts) @@ -260,11 +251,11 @@ (defmethod add-type-info String [s {:keys [unit], :as info} & {:keys [parse-datetime-strings?] :or {parse-datetime-strings? true}}] - (if (and unit + (if (and (or unit (when info (types/temporal-field? info))) parse-datetime-strings? (seq s)) (let [effective-type ((some-fn :effective_type :base_type) info)] - (parse-temporal-string-literal effective-type s unit)) + (parse-temporal-string-literal effective-type s (or unit :default))) [:value s info])) ;;; -------------------------------------------- wrap-literals-in-clause --------------------------------------------- diff --git a/test/metabase/lib/temporal_bucket_test.cljc b/test/metabase/lib/temporal_bucket_test.cljc index 76c0be3097c88..798706100f8b0 100644 --- a/test/metabase/lib/temporal_bucket_test.cljc +++ b/test/metabase/lib/temporal_bucket_test.cljc @@ -110,7 +110,7 @@ :minute-of-hour :hour-of-day :day-of-week :day-of-month :day-of-year :week-of-year :month-of-year :quarter-of-year} - expected-defaults [{:lib/type :option/temporal-bucketing, :unit :day, :default true}]] + expected-defaults [{:lib/type :option/temporal-bucketing, :unit :month, :default true}]] (testing "missing fingerprint" (let [column (dissoc column :fingerprint) options (lib.temporal-bucket/available-temporal-buckets-method nil -1 column)] @@ -123,8 +123,8 @@ "2017-04-15T13:34:19.931Z" :week "2016-05-15T13:34:19.931Z" :day "2016-04-27T13:34:19.931Z" :minute - nil :day - "garbage" :day}] + nil :month + "garbage" :month}] (testing latest (let [bounds {:earliest "2016-04-26T19:29:55.147Z" :latest latest} @@ -166,12 +166,11 @@ (deftest ^:parallel temporal-bucketing-options-expressions-test (testing "Temporal bucketing should be available for Date and DateTime-valued expressions" - ;; TODO: Why is the default :month for a Field and :day for an expression? (is (=? [{:unit :minute} {:unit :hour} - {:unit :day, :default true} + {:unit :day} {:unit :week} - {:unit :month} + {:unit :month, :default true} {:unit :quarter} {:unit :year} {:unit :minute-of-hour} diff --git a/test/metabase/query_processor/middleware/add_default_temporal_unit_test.clj b/test/metabase/query_processor/middleware/add_default_temporal_unit_test.clj deleted file mode 100644 index 10c3017399139..0000000000000 --- a/test/metabase/query_processor/middleware/add_default_temporal_unit_test.clj +++ /dev/null @@ -1,85 +0,0 @@ -(ns metabase.query-processor.middleware.add-default-temporal-unit-test - (:require - [clojure.test :refer :all] - [metabase.lib.test-metadata :as meta] - [metabase.lib.test-util.macros :as lib.tu.macros] - [metabase.query-processor.middleware.add-default-temporal-unit - :as add-default-temporal-unit] - [metabase.query-processor.store :as qp.store])) - -(defn- add-default-temporal-unit [query] - (qp.store/with-metadata-provider meta/metadata-provider - (add-default-temporal-unit/add-default-temporal-unit query))) - -(deftest ^:parallel add-default-temporal-unit-test - (testing "Should add temporal-unit :default to a :field clause" - (testing "with a Field ID" - (is (= (lib.tu.macros/mbql-query checkins - {:filter [:> !default.date "2021-05-13T00:00:00Z"]}) - (add-default-temporal-unit - (lib.tu.macros/mbql-query checkins - {:filter [:> $date "2021-05-13T00:00:00Z"]}))))))) - -(deftest ^:parallel add-default-temporal-unit-test-2 - (testing "Should add temporal-unit :default to a :field clause" - (testing "with a Field name and temporal base type" - (is (= (lib.tu.macros/mbql-query checkins - {:filter [:> - [:field "date" {:base-type :type/TimeWithLocalTZ, :temporal-unit :default}] - "2021-05-13T00:00:00Z"]}) - (add-default-temporal-unit - (lib.tu.macros/mbql-query checkins - {:filter [:> - [:field "date" {:base-type :type/TimeWithLocalTZ}] - "2021-05-13T00:00:00Z"]}))))))) - -(deftest ^:parallel add-default-temporal-unit-test-3 - (testing "Should ignore fields that already have a temporal unit" - (testing "with an ID" - (let [query (lib.tu.macros/mbql-query checkins - {:filter [:> !month.date "2021-05-13T00:00:00Z"]})] - (is (= query - (add-default-temporal-unit query))))) - (testing "with a field name" - (let [query (lib.tu.macros/mbql-query checkins - {:filter [:> - [:field "date" {:base-type :type/TimeWithLocalTZ, :temporal-unit :month}] - "2021-05-13T00:00:00Z"]})] - (is (= query - (add-default-temporal-unit query))))))) - -(deftest ^:parallel add-default-temporal-unit-test-4 - (testing "Should add temporal-unit :default to a :field clause" - (testing "with a Field ID" - (is (= (lib.tu.macros/mbql-query checkins - {:filter [:> !default.date "2021-05-13T00:00:00Z"]}) - (add-default-temporal-unit - (lib.tu.macros/mbql-query checkins - {:filter [:> $date "2021-05-13T00:00:00Z"]}))))))) - -(deftest ^:parallel add-default-temporal-unit-test-5 - (testing "Should add temporal-unit :default to a :field clause" - (testing "with a Field name and temporal base type" - (is (= (lib.tu.macros/mbql-query checkins - {:filter [:> - [:field "date" {:base-type :type/TimeWithLocalTZ, :temporal-unit :default}] - "2021-05-13T00:00:00Z"]}) - (add-default-temporal-unit - (lib.tu.macros/mbql-query checkins - {:filter [:> - [:field "date" {:base-type :type/TimeWithLocalTZ}] - "2021-05-13T00:00:00Z"]}))))))) - -(deftest ^:parallel ignore-parameters-test - (testing "Don't try to update query `:parameters`" - (let [query {:database (meta/id) - :type :native - :native {:query "select 111 as my_number, 'foo' as my_string"} - :parameters [{:type "category" - :value [:param-value] - :target [:dimension - [:field - (meta/id :categories :id) - {:source-field (meta/id :venues :category-id)}]]}]}] - (is (= query - (add-default-temporal-unit query)))))) diff --git a/test/metabase/query_processor/middleware/add_implicit_clauses_test.clj b/test/metabase/query_processor/middleware/add_implicit_clauses_test.clj index 27687df355554..609ab21720ed8 100644 --- a/test/metabase/query_processor/middleware/add_implicit_clauses_test.clj +++ b/test/metabase/query_processor/middleware/add_implicit_clauses_test.clj @@ -151,7 +151,7 @@ (is (query= (:query (lib.tu.macros/mbql-query venues {:fields [$id $name - [:field 1 {:temporal-unit :default}] + [:field 1 nil] $category-id $latitude $longitude $price]})) (add-implicit-fields (:query (lib.tu.macros/mbql-query venues)))))))) diff --git a/test/metabase/query_processor/middleware/add_implicit_joins_test.clj b/test/metabase/query_processor/middleware/add_implicit_joins_test.clj index 5b0d7a196db59..cfc48a9a87e02 100644 --- a/test/metabase/query_processor/middleware/add_implicit_joins_test.clj +++ b/test/metabase/query_processor/middleware/add_implicit_joins_test.clj @@ -274,7 +274,7 @@ $tax $total $discount - !default.created-at + $created-at $quantity [:field %products.category {:source-field %product-id :join-alias "PRODUCTS__via__PRODUCT_ID"}]] @@ -374,7 +374,7 @@ (is (= (lib.tu.macros/mbql-query checkins {:source-query {:source-table $$checkins :fields [$id - !default.date + $date $user-id $venue-id] :filter [:> $date "2014-01-01"]} diff --git a/test/metabase/query_processor/middleware/add_source_metadata_test.clj b/test/metabase/query_processor/middleware/add_source_metadata_test.clj index 64dbb49c8b1cf..d319419d5ab59 100644 --- a/test/metabase/query_processor/middleware/add_source_metadata_test.clj +++ b/test/metabase/query_processor/middleware/add_source_metadata_test.clj @@ -366,13 +366,7 @@ ;; the actual metadata this middleware should return. Doesn't have all the columns that come back from ;; `qp.preprocess/query->expected-cols` expected-metadata (for [col metadata] - (cond-> (merge (results-col col) (select-keys col [:source_alias])) - ;; for some reason this middleware returns temporal fields with a `:default` unit, - ;; whereas `query->expected-cols` does not return the unit. It ulimately makes zero - ;; difference, so I haven't looked into why this is the case yet. - (isa? (:base_type col) :type/Temporal) - (update :field_ref (fn [[_ id-or-name opts]] - [:field id-or-name (assoc opts :temporal-unit :default)]))))] + (merge (results-col col) (select-keys col [:source_alias])))] (letfn [(added-metadata [query] (get-in (add-source-metadata query) [:query :source-metadata]))] (testing "\nShould add source metadata if there's none already" diff --git a/test/metabase/query_processor/middleware/resolve_joins_test.clj b/test/metabase/query_processor/middleware/resolve_joins_test.clj index c14e96b9e4eb2..f0aa38e5c9f1e 100644 --- a/test/metabase/query_processor/middleware/resolve_joins_test.clj +++ b/test/metabase/query_processor/middleware/resolve_joins_test.clj @@ -212,7 +212,7 @@ :strategy :left-join :condition [:= $id [:field "USER_ID" {:base-type :type/Integer, :join-alias "c"}]] :fields [&c.checkins.id - !default.&c.checkins.date + &c.checkins.date &c.checkins.user_id &c.checkins.venue_id]}] :aggregation [[:sum [:field "id" {:base-type :type/Float, :join-alias "c"}]]] diff --git a/test/metabase/query_processor/test_util.clj b/test/metabase/query_processor/test_util.clj index 260820190942e..0b86cdfa7b656 100644 --- a/test/metabase/query_processor/test_util.clj +++ b/test/metabase/query_processor/test_util.clj @@ -100,10 +100,7 @@ (t2/select-one [:model/Field :id :table_id :semantic_type :base_type :effective_type :coercion_strategy :name :display_name :fingerprint] :id (data/id table-kw field-kw))) - {:field_ref [:field (data/id table-kw field-kw) nil]} - (when (#{:last_login :date} field-kw) - {:unit :default - :field_ref [:field (data/id table-kw field-kw) {:temporal-unit :default}]}))) + {:field_ref [:field (data/id table-kw field-kw) nil]})) (defn- expected-column-names "Get a sequence of keyword names of Fields belonging to a Table in the order they'd normally appear in QP results." diff --git a/test/metabase/query_processor/util/add_alias_info_test.clj b/test/metabase/query_processor/util/add_alias_info_test.clj index cff464c9d0cdd..79c68c4f9d8fd 100644 --- a/test/metabase/query_processor/util/add_alias_info_test.clj +++ b/test/metabase/query_processor/util/add_alias_info_test.clj @@ -197,16 +197,14 @@ ::add/desired-alias "count" ::add/position 0}]] :filter [:!= - [:field %date {:temporal-unit :default - ::add/source-table $$checkins + [:field %date {::add/source-table $$checkins ::add/source-alias "DATE"}] [:value nil {:base_type :type/Date :effective_type :type/Date :coercion_strategy nil :semantic_type nil :database_type "DATE" - :name "DATE" - :unit :default}]]}) + :name "DATE"}]]}) (add-alias-info (lib.tu.macros/mbql-query checkins {:aggregation [[:count]] diff --git a/test/metabase/query_processor/util/nest_query_test.clj b/test/metabase/query_processor/util/nest_query_test.clj index f66aac0545c0f..cfa089415e835 100644 --- a/test/metabase/query_processor/util/nest_query_test.clj +++ b/test/metabase/query_processor/util/nest_query_test.clj @@ -480,8 +480,7 @@ (is (partial= (lib.tu.macros/$ids venues {:source-query {:source-table $$venues :expressions {"test" [:* 1 1]} - :fields [[:field %price {:temporal-unit :default - ::add/source-table $$venues + :fields [[:field %price {::add/source-table $$venues ::add/source-alias "PRICE" ::add/desired-alias "PRICE" ::add/position 0}] @@ -517,8 +516,7 @@ ::add/source-alias "PRODUCT_ID" ::add/desired-alias "PRODUCT_ID" ::add/position 0}] - created-at [:field %created-at {:temporal-unit :default - ::add/source-table $$orders + created-at [:field %created-at {::add/source-table $$orders ::add/source-alias "CREATED_AT" ::add/desired-alias "CREATED_AT" ::add/position 1}] diff --git a/test/metabase/query_processor_test/nested_queries_test.clj b/test/metabase/query_processor_test/nested_queries_test.clj index dc48a7ec39e89..3a9f863d2381d 100644 --- a/test/metabase/query_processor_test/nested_queries_test.clj +++ b/test/metabase/query_processor_test/nested_queries_test.clj @@ -670,7 +670,7 @@ ;; the `:year` bucketing if you used this query in another subsequent query, so the field ref doesn't ;; include the unit; however `:unit` is still `:year` so the frontend can use the correct formatting to ;; display values of the column. - (is (=? [(assoc date-col :field_ref [:field (mt/id :checkins :date) {:temporal-unit :default}], :unit :year) + (is (=? [(assoc date-col :field_ref [:field (mt/id :checkins :date) nil], :unit :year) (assoc count-col :field_ref [:field "count" {:base-type :type/Integer}])] (mt/cols (qp/process-query (query-with-source-card 1))))))))))) @@ -1267,7 +1267,7 @@ $tax $total $discount - !default.created_at + $created_at $quantity &ℙ.products.id &ℙ.products.ean @@ -1276,7 +1276,7 @@ &ℙ.products.vendor &ℙ.products.price &ℙ.products.rating - !default.&ℙ.products.created_at]) + &ℙ.products.created_at]) (map :field_ref metadata)))) (testing "\nShould be able to use the query as a source query" (letfn [(test-query [query] From e12e36044e588761a3fb9aed10900a9d506d7fd6 Mon Sep 17 00:00:00 2001 From: Noah Moss <32746338+noahmoss@users.noreply.github.com> Date: Fri, 27 Sep 2024 15:38:05 -0400 Subject: [PATCH 03/19] Fix non-bool value in CSV upload availability check for stats ping (#48167) --- src/metabase/analytics/stats.clj | 12 +++++------ test/metabase/analytics/stats_test.clj | 29 ++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/src/metabase/analytics/stats.clj b/src/metabase/analytics/stats.clj index 55b7543f47edc..257d7f47a107f 100644 --- a/src/metabase/analytics/stats.clj +++ b/src/metabase/analytics/stats.clj @@ -588,12 +588,12 @@ (defn- csv-upload-available? "Is CSV upload currently available to be used on this instance?" [] - (let [major-version (config/current-major-version) - minor-version (config/current-minor-version) - engines (t2/select-fn-set :engine :model/Database - {:where [:in :engine (map name (keys csv-upload-version-availability))]})] - (when (and major-version minor-version) - (boolean + (boolean + (let [major-version (config/current-major-version) + minor-version (config/current-minor-version) + engines (t2/select-fn-set :engine :model/Database + {:where [:in :engine (map name (keys csv-upload-version-availability))]})] + (when (and major-version minor-version) (some (fn [engine] (when-let [[required-major required-minor] (csv-upload-version-availability engine)] diff --git a/test/metabase/analytics/stats_test.clj b/test/metabase/analytics/stats_test.clj index 3173b75e5057e..e5c4fd89bc0bb 100644 --- a/test/metabase/analytics/stats_test.clj +++ b/test/metabase/analytics/stats_test.clj @@ -4,6 +4,7 @@ [clojure.test :refer :all] [java-time.api :as t] [metabase.analytics.stats :as stats :refer [legacy-anonymous-usage-stats]] + [metabase.config :as config] [metabase.core :as mbc] [metabase.db :as mdb] [metabase.email :as email] @@ -392,6 +393,34 @@ (mt/with-temp [:model/QueryExecution _ query-execution-defaults] (is (true? (@#'stats/sufficient-queries? 1))))))) +(deftest csv-upload-available-test + (mt/with-temp-empty-app-db [_conn :h2] + (mdb/setup-db! :create-sample-content? true) + + (testing "csv-upload-available? currently detects upload availability based on the current MB version" + (mt/with-temp [:model/Database _ {:engine :postgres}] + (with-redefs [config/current-major-version (constantly 46) + config/current-minor-version (constantly 0)] + (is false? (@#'stats/csv-upload-available?))) + + (with-redefs [config/current-major-version (constantly 47) + config/current-minor-version (constantly 1)] + (is true? (@#'stats/csv-upload-available?)))) + + (mt/with-temp [:model/Database _ {:engine :redshift}] + (with-redefs [config/current-major-version (constantly 49) + config/current-minor-version (constantly 5)] + (is false? (@#'stats/csv-upload-available?))) + + (with-redefs [config/current-major-version (constantly 49) + config/current-minor-version (constantly 6)] + (is true? (@#'stats/csv-upload-available?)))) + + ;; If we can't detect the MB version, return nil + (with-redefs [config/current-major-version (constantly nil) + config/current-minor-version (constantly nil)] + (is false? (@#'stats/csv-upload-available?)))))) + (def ^:private excluded-features "Set of features intentionally excluded from the daily stats ping. If you add a new feature, either add it to the stats ping or to this set, so that [[every-feature-is-accounted-for-test]] passes." From 268b6c48adf53775512d36b5e922f2a55dcad88e Mon Sep 17 00:00:00 2001 From: Uladzimir Havenchyk <125459446+uladzimirdev@users.noreply.github.com> Date: Mon, 30 Sep 2024 15:25:07 +0300 Subject: [PATCH 04/19] refactor: rework stories from MDX to MDX + CSF (#48054) * refactor: rework stories from MDX to MDX + CSF * short syntax * fix or bypass typescript errors * fix leftovers * cleanup leftovers * convert js stories to tsx * restore some comments * fix linting --- .../SdkUsageProblemBanner.stories.tsx | 25 +- .../SdkUsageProblem/SdkUsageProblemBanner.tsx | 6 +- .../CreateDashboardModal.stories.tsx | 20 +- .../EditableDashboard.stories.tsx | 18 +- .../InteractiveDashboard.stories.tsx | 18 +- .../StaticDashboard.stories.tsx | 15 +- .../DeprecationNotice.stories.tsx | 22 +- .../SlackSetup/SlackSetup.stories.tsx | 22 +- .../SlackStatus/SlackStatus.stories.tsx | 18 +- .../admin/upsells/components/UpsellCard.mdx | 20 + ...ard.stories.mdx => UpsellCard.stories.tsx} | 52 +- .../admin/upsells/components/UpsellCard.tsx | 2 +- .../admin/upsells/components/UpsellPill.mdx | 19 + ...ill.stories.mdx => UpsellPill.stories.tsx} | 55 +- .../PinnedItemCard/PinnedItemCard.stories.tsx | 125 +- .../PinnedItemCard/PinnedItemCard.tsx | 4 +- .../common/components/Sidesheet/Sidesheet.mdx | 60 + .../Sidesheet/Sidesheet.stories.mdx | 165 --- .../Sidesheet/Sidesheet.stories.tsx | 120 ++ .../components/Calendar/Calendar.stories.tsx | 10 +- .../DateMonthYearWidget.stories.tsx | 33 +- .../DateQuarterYearWidget.stories.tsx | 33 +- .../DateRelativeWidget.stories.tsx | 42 +- .../components/EntityMenu.stories.tsx | 15 +- .../components/HelpCard/HelpCard.stories.tsx | 21 +- .../ModalContent/ModalContent.stories.tsx | 48 +- .../components/Schedule/Schedule.stories.tsx | 25 +- .../SchedulePicker/SchedulePicker.stories.tsx | 25 +- .../SegmentedControl.stories.tsx | 87 +- .../SegmentedControl/SegmentedControl.tsx | 4 +- .../SelectList/SelectListItem.stories.tsx | 14 +- .../TextWidget/TextWidget.stories.tsx | 37 +- .../components/TextWidget/TextWidget.tsx | 8 +- .../components/Toaster/Toaster.stories.tsx | 28 +- .../TokenFieldItem/TokenFieldItem.stories.tsx | 41 +- .../UserAvatar/UserAvartar.stories.tsx | 66 +- .../YearPicker/YearPicker.stories.tsx | 15 +- ...t.stories.js => AccordionList.stories.tsx} | 12 +- .../core/components/Alert/Alert.stories.tsx | 43 +- .../metabase/core/components/Alert/Alert.tsx | 2 +- .../AutocompleteInput.stories.tsx | 36 +- .../BookmarkToggle/BookmarkToggle.stories.tsx | 15 +- .../core/components/Button/Button.stories.tsx | 46 +- .../ButtonGroup/ButtonGroup.stories.tsx | 8 +- .../components/CheckBox/CheckBox.stories.tsx | 37 +- .../ColorInput/ColorInput.stories.tsx | 10 +- .../ColorPicker/ColorPicker.stories.tsx | 17 +- .../ColorPill/ColorPill.stories.tsx | 26 +- .../ColorRange/ColorRange.stories.tsx | 33 +- .../ColorRangeSelector.stories.tsx | 63 +- .../ColorSelector/ColorSelector.stories.tsx | 19 +- .../DateInput/DateInput.stories.tsx | 17 +- .../DateSelector/DateSelector.stories.tsx | 21 +- .../DateWidget/DateWidget.stories.tsx | 17 +- .../EditableText/EditableText.stories.tsx | 62 +- .../Ellipsified/Ellipsified.stories.tsx | 16 +- .../ExternalLink/ExternalLink.stories.tsx | 15 +- .../FileInput/FileInput.stories.tsx | 13 +- .../FormCheckBox/FormCheckBox.stories.tsx | 24 +- .../FormDateInput/FormDateInput.stories.tsx | 24 +- .../FormField/FormField.stories.tsx | 45 +- .../FormFileInput/FormFileInput.stories.tsx | 24 +- .../FormInput/FormInput.stories.tsx | 39 +- .../FormNumericInput.stories.tsx | 24 +- .../FormRadio/FormRadio.stories.tsx | 24 +- .../FormSelect/FormSelect.stories.tsx | 28 +- .../FormTextArea/FormTextArea.stories.tsx | 24 +- .../FormToggle/FormToggle.stories.tsx | 24 +- .../core/components/Input/Input.stories.tsx | 36 +- .../core/components/Link/Link.stories.tsx | 16 +- .../components/Markdown/Markdown.stories.tsx | 23 +- .../MarkdownPreview.stories.tsx | 36 +- .../MarkdownPreview/MarkdownPreview.tsx | 4 +- .../NumericInput/NumericInput.stories.tsx | 8 +- .../core/components/Radio/Radio.stories.tsx | 41 +- .../core/components/Select/Select.stories.tsx | 171 +-- .../SelectButton/SelectButton.stories.tsx | 49 +- .../core/components/Slider/Slider.stories.tsx | 11 +- .../TabContent/TabContent.stories.tsx | 15 +- .../components/TabList/TabList.stories.tsx | 15 +- .../core/components/TabRow/TabRow.stories.tsx | 37 +- .../components/TextArea/TextArea.stories.tsx | 10 +- .../TimeInput/TimeInput.stories.tsx | 8 +- .../core/components/Toggle/Toggle.stories.tsx | 15 +- .../components/Tooltip/Tooltip.stories.tsx | 48 +- .../DatabaseEngineWarning.stories.tsx | 52 +- .../NumberInputWidget.stories.tsx | 56 +- .../StringInputWidget.stories.tsx | 28 +- ...rEmbeddedDashboardView-filters.stories.tsx | 870 ++++++----- .../PublicOrEmbeddedDashboardView.stories.tsx | 261 ++-- .../PublicOrEmbeddedDashboardView.tsx | 10 +- .../PublicOrEmbeddedQuestionView.stories.tsx | 366 ++--- .../PublicOrEmbeddedQuestionView.tsx | 2 +- .../ExpressionEditorHelpText.stories.tsx | 4 +- .../ComboChart/ComboChart.stories.tsx | 1317 +++++++++++------ .../FunnelBarChart/FunnelBarChart.stories.tsx | 39 +- .../FunnelChart/FunnelChart.stories.tsx | 18 +- .../components/Gauge/Gauge.stories.tsx | 26 +- .../components/PieChart/PieChart.stories.tsx | 457 +++--- .../ProgressBar/ProgressBar.stories.tsx | 30 +- .../components/ProgressBar/ProgressBar.tsx | 2 +- .../components/RowChart/RowChart.stories.tsx | 23 +- .../ScalarChart/ScalarChart.stories.tsx | 17 +- .../ScatterPlot/ScatterPlot.stories.tsx | 193 ++- .../WaterfallChart/WaterfallChart.stories.tsx | 446 ++++-- .../DownloadsStatusLarge.stories.tsx | 104 +- .../FileUploadStatusLarge.stories.tsx | 91 +- .../TimelinePicker/TimelinePicker.stories.tsx | 53 +- .../ui/{Intro.stories.mdx => Intro.mdx} | 8 +- .../ui/components/buttons/Button/Button.mdx | 139 ++ .../buttons/Button/Button.stories.mdx | 341 ----- .../buttons/Button/Button.stories.tsx | 252 ++++ .../PopoverBackButton/PopoverBackButton.mdx | 14 + ...ries.mdx => PopoverBackButton.stories.tsx} | 45 +- .../ui/components/data-display/Card/Card.mdx | 38 + .../data-display/Card/Card.stories.mdx | 108 -- .../data-display/Card/Card.stories.tsx | 89 ++ .../components/data-display/Image/Image.mdx | 27 + .../data-display/Image/Image.stories.mdx | 72 - .../data-display/Image/Image.stories.tsx | 56 + .../ui/components/feedback/Alert/Alert.mdx | 16 + .../{Alert.stories.mdx => Alert.stories.tsx} | 39 +- .../ui/components/feedback/Loader/Loader.mdx | 33 + .../feedback/Loader/Loader.stories.mdx | 69 - .../feedback/Loader/Loader.stories.tsx | 47 + .../components/feedback/Progress/Progress.mdx | 20 + .../feedback/Progress/Progress.stories.mdx | 40 - .../feedback/Progress/Progress.stories.tsx | 23 + .../ui/components/icons/Icon/Icon.mdx | 23 + .../ui/components/icons/Icon/Icon.stories.mdx | 62 - .../ui/components/icons/Icon/Icon.stories.tsx | 51 + .../inputs/Autocomplete/Autocomplete.mdx | 127 ++ .../Autocomplete/Autocomplete.stories.mdx | 340 ----- .../Autocomplete/Autocomplete.stories.tsx | 270 ++++ .../components/inputs/Checkbox/Checkbox.mdx | 71 + .../inputs/Checkbox/Checkbox.stories.mdx | 175 --- .../inputs/Checkbox/Checkbox.stories.tsx | 126 ++ .../components/inputs/DateInput/DateInput.mdx | 129 ++ .../inputs/DateInput/DateInput.stories.mdx | 322 ---- .../inputs/DateInput/DateInput.stories.tsx | 250 ++++ .../inputs/DatePicker/DatePicker.mdx | 58 + .../inputs/DatePicker/DatePicker.stories.mdx | 163 -- .../inputs/DatePicker/DatePicker.stories.tsx | 117 ++ .../components/inputs/FileInput/FileInput.mdx | 92 ++ .../inputs/FileInput/FileInput.stories.mdx | 237 --- .../inputs/FileInput/FileInput.stories.tsx | 184 +++ .../MultiAutocomplete/MultiAutocomplete.mdx | 104 ++ .../MultiAutocomplete.stories.mdx | 268 ---- .../MultiAutocomplete.stories.tsx | 216 +++ .../inputs/MultiSelect/MultiSelect.mdx | 164 ++ .../MultiSelect/MultiSelect.stories.mdx | 434 ------ .../MultiSelect/MultiSelect.stories.tsx | 345 +++++ .../inputs/NumberInput/NumberInput.mdx | 118 ++ .../NumberInput/NumberInput.stories.mdx | 299 ---- .../NumberInput/NumberInput.stories.tsx | 235 +++ .../ui/components/inputs/Radio/Radio.mdx | 65 + .../components/inputs/Radio/Radio.stories.mdx | 135 -- .../components/inputs/Radio/Radio.stories.tsx | 94 ++ .../SegmentedControl/SegmentedControl.mdx | 26 + .../SegmentedControl.stories.mdx | 76 - .../SegmentedControl.stories.tsx | 56 + .../ui/components/inputs/Select/Select.mdx | 152 ++ .../inputs/Select/Select.stories.mdx | 402 ----- .../inputs/Select/Select.stories.tsx | 319 ++++ .../ui/components/inputs/Switch/Switch.mdx | 53 + .../inputs/Switch/Switch.stories.mdx | 122 -- .../inputs/Switch/Switch.stories.tsx | 81 + .../components/inputs/TextInput/TextInput.mdx | 129 ++ .../inputs/TextInput/TextInput.stories.mdx | 324 ---- .../inputs/TextInput/TextInput.stories.tsx | 252 ++++ .../components/inputs/Textarea/Textarea.mdx | 116 ++ .../inputs/Textarea/Textarea.stories.mdx | 305 ---- .../inputs/Textarea/Textarea.stories.tsx | 242 +++ .../components/inputs/TimeInput/TimeInput.mdx | 104 ++ .../inputs/TimeInput/TimeInput.stories.mdx | 265 ---- .../inputs/TimeInput/TimeInput.stories.tsx | 213 +++ .../ui/components/navigation/Tabs/Tabs.mdx | 36 + .../navigation/Tabs/Tabs.stories.mdx | 108 -- .../navigation/Tabs/Tabs.stories.tsx | 92 ++ .../overlays/HoverCard/HoverCard.mdx | 29 + ...Card.stories.mdx => HoverCard.stories.tsx} | 61 +- .../ui/components/overlays/Menu/Menu.mdx | 67 + .../{Menu.stories.mdx => Menu.stories.tsx} | 104 +- .../ui/components/overlays/Modal/Modal.mdx | 87 ++ .../overlays/Modal/Modal.stories.mdx | 224 --- .../overlays/Modal/Modal.stories.tsx | 165 +++ .../components/overlays/Popover/Popover.mdx | 29 + ...opover.stories.mdx => Popover.stories.tsx} | 61 +- .../components/overlays/Tooltip/Tooltip.mdx | 20 + .../overlays/Tooltip/Tooltip.stories.mdx | 62 - .../overlays/Tooltip/Tooltip.stories.tsx | 49 + .../components/typography/Anchor/Anchor.mdx | 36 + .../typography/Anchor/Anchor.stories.mdx | 92 -- .../typography/Anchor/Anchor.stories.tsx | 68 + .../ui/components/typography/List/List.mdx | 18 + .../{List.stories.mdx => List.stories.tsx} | 54 +- .../ui/components/typography/Text/Text.mdx | 48 + .../typography/Text/Text.stories.mdx | 139 -- .../typography/Text/Text.stories.tsx | 106 ++ .../ui/components/typography/Title/Title.mdx | 48 + .../typography/Title/Title.stories.mdx | 140 -- .../typography/Title/Title.stories.tsx | 111 ++ .../ui/components/utils/Divider/Divider.mdx | 26 + .../utils/Divider/Divider.stories.mdx | 60 - .../utils/Divider/Divider.stories.tsx | 41 + .../ui/components/utils/Paper/Paper.mdx | 33 + .../{Paper.stories.mdx => Paper.stories.tsx} | 69 +- .../TableInteractive.stories.tsx | 4 +- .../TableSimple/TableSimple.stories.tsx | 6 +- .../ChartSkeleton/ChartSkeleton.stories.tsx | 160 +- .../StaticSkeleton/StaticSkeleton.stories.tsx | 23 +- .../components/RowChart/RowChart.stories.tsx | 12 +- .../BarChart/BarChart.stories.tsx | 6 +- .../visualizations/Funnel/Funnel.stories.tsx | 4 +- .../LineChart/LineChart.stories.tsx | 50 +- .../PieChart/PieChart.stories.tsx | 34 +- .../PivotTable/PivotTable.stories.tsx | 12 +- .../SmartScalar/SmartScalar.stories.tsx | 6 +- 218 files changed, 11072 insertions(+), 8669 deletions(-) create mode 100644 frontend/src/metabase/admin/upsells/components/UpsellCard.mdx rename frontend/src/metabase/admin/upsells/components/{UpsellCard.stories.mdx => UpsellCard.stories.tsx} (50%) create mode 100644 frontend/src/metabase/admin/upsells/components/UpsellPill.mdx rename frontend/src/metabase/admin/upsells/components/{UpsellPill.stories.mdx => UpsellPill.stories.tsx} (53%) create mode 100644 frontend/src/metabase/common/components/Sidesheet/Sidesheet.mdx delete mode 100644 frontend/src/metabase/common/components/Sidesheet/Sidesheet.stories.mdx create mode 100644 frontend/src/metabase/common/components/Sidesheet/Sidesheet.stories.tsx rename frontend/src/metabase/core/components/AccordionList/{AccordionList.stories.js => AccordionList.stories.tsx} (65%) rename frontend/src/metabase/ui/{Intro.stories.mdx => Intro.mdx} (84%) create mode 100644 frontend/src/metabase/ui/components/buttons/Button/Button.mdx delete mode 100644 frontend/src/metabase/ui/components/buttons/Button/Button.stories.mdx create mode 100644 frontend/src/metabase/ui/components/buttons/Button/Button.stories.tsx create mode 100644 frontend/src/metabase/ui/components/buttons/PopoverBackButton/PopoverBackButton.mdx rename frontend/src/metabase/ui/components/buttons/PopoverBackButton/{PopoverBackButton.stories.mdx => PopoverBackButton.stories.tsx} (65%) create mode 100644 frontend/src/metabase/ui/components/data-display/Card/Card.mdx delete mode 100644 frontend/src/metabase/ui/components/data-display/Card/Card.stories.mdx create mode 100644 frontend/src/metabase/ui/components/data-display/Card/Card.stories.tsx create mode 100644 frontend/src/metabase/ui/components/data-display/Image/Image.mdx delete mode 100644 frontend/src/metabase/ui/components/data-display/Image/Image.stories.mdx create mode 100644 frontend/src/metabase/ui/components/data-display/Image/Image.stories.tsx create mode 100644 frontend/src/metabase/ui/components/feedback/Alert/Alert.mdx rename frontend/src/metabase/ui/components/feedback/Alert/{Alert.stories.mdx => Alert.stories.tsx} (53%) create mode 100644 frontend/src/metabase/ui/components/feedback/Loader/Loader.mdx delete mode 100644 frontend/src/metabase/ui/components/feedback/Loader/Loader.stories.mdx create mode 100644 frontend/src/metabase/ui/components/feedback/Loader/Loader.stories.tsx create mode 100644 frontend/src/metabase/ui/components/feedback/Progress/Progress.mdx delete mode 100644 frontend/src/metabase/ui/components/feedback/Progress/Progress.stories.mdx create mode 100644 frontend/src/metabase/ui/components/feedback/Progress/Progress.stories.tsx create mode 100644 frontend/src/metabase/ui/components/icons/Icon/Icon.mdx delete mode 100644 frontend/src/metabase/ui/components/icons/Icon/Icon.stories.mdx create mode 100644 frontend/src/metabase/ui/components/icons/Icon/Icon.stories.tsx create mode 100644 frontend/src/metabase/ui/components/inputs/Autocomplete/Autocomplete.mdx delete mode 100644 frontend/src/metabase/ui/components/inputs/Autocomplete/Autocomplete.stories.mdx create mode 100644 frontend/src/metabase/ui/components/inputs/Autocomplete/Autocomplete.stories.tsx create mode 100644 frontend/src/metabase/ui/components/inputs/Checkbox/Checkbox.mdx delete mode 100644 frontend/src/metabase/ui/components/inputs/Checkbox/Checkbox.stories.mdx create mode 100644 frontend/src/metabase/ui/components/inputs/Checkbox/Checkbox.stories.tsx create mode 100644 frontend/src/metabase/ui/components/inputs/DateInput/DateInput.mdx delete mode 100644 frontend/src/metabase/ui/components/inputs/DateInput/DateInput.stories.mdx create mode 100644 frontend/src/metabase/ui/components/inputs/DateInput/DateInput.stories.tsx create mode 100644 frontend/src/metabase/ui/components/inputs/DatePicker/DatePicker.mdx delete mode 100644 frontend/src/metabase/ui/components/inputs/DatePicker/DatePicker.stories.mdx create mode 100644 frontend/src/metabase/ui/components/inputs/DatePicker/DatePicker.stories.tsx create mode 100644 frontend/src/metabase/ui/components/inputs/FileInput/FileInput.mdx delete mode 100644 frontend/src/metabase/ui/components/inputs/FileInput/FileInput.stories.mdx create mode 100644 frontend/src/metabase/ui/components/inputs/FileInput/FileInput.stories.tsx create mode 100644 frontend/src/metabase/ui/components/inputs/MultiAutocomplete/MultiAutocomplete.mdx delete mode 100644 frontend/src/metabase/ui/components/inputs/MultiAutocomplete/MultiAutocomplete.stories.mdx create mode 100644 frontend/src/metabase/ui/components/inputs/MultiAutocomplete/MultiAutocomplete.stories.tsx create mode 100644 frontend/src/metabase/ui/components/inputs/MultiSelect/MultiSelect.mdx delete mode 100644 frontend/src/metabase/ui/components/inputs/MultiSelect/MultiSelect.stories.mdx create mode 100644 frontend/src/metabase/ui/components/inputs/MultiSelect/MultiSelect.stories.tsx create mode 100644 frontend/src/metabase/ui/components/inputs/NumberInput/NumberInput.mdx delete mode 100644 frontend/src/metabase/ui/components/inputs/NumberInput/NumberInput.stories.mdx create mode 100644 frontend/src/metabase/ui/components/inputs/NumberInput/NumberInput.stories.tsx create mode 100644 frontend/src/metabase/ui/components/inputs/Radio/Radio.mdx delete mode 100644 frontend/src/metabase/ui/components/inputs/Radio/Radio.stories.mdx create mode 100644 frontend/src/metabase/ui/components/inputs/Radio/Radio.stories.tsx create mode 100644 frontend/src/metabase/ui/components/inputs/SegmentedControl/SegmentedControl.mdx delete mode 100644 frontend/src/metabase/ui/components/inputs/SegmentedControl/SegmentedControl.stories.mdx create mode 100644 frontend/src/metabase/ui/components/inputs/SegmentedControl/SegmentedControl.stories.tsx create mode 100644 frontend/src/metabase/ui/components/inputs/Select/Select.mdx delete mode 100644 frontend/src/metabase/ui/components/inputs/Select/Select.stories.mdx create mode 100644 frontend/src/metabase/ui/components/inputs/Select/Select.stories.tsx create mode 100644 frontend/src/metabase/ui/components/inputs/Switch/Switch.mdx delete mode 100644 frontend/src/metabase/ui/components/inputs/Switch/Switch.stories.mdx create mode 100644 frontend/src/metabase/ui/components/inputs/Switch/Switch.stories.tsx create mode 100644 frontend/src/metabase/ui/components/inputs/TextInput/TextInput.mdx delete mode 100644 frontend/src/metabase/ui/components/inputs/TextInput/TextInput.stories.mdx create mode 100644 frontend/src/metabase/ui/components/inputs/TextInput/TextInput.stories.tsx create mode 100644 frontend/src/metabase/ui/components/inputs/Textarea/Textarea.mdx delete mode 100644 frontend/src/metabase/ui/components/inputs/Textarea/Textarea.stories.mdx create mode 100644 frontend/src/metabase/ui/components/inputs/Textarea/Textarea.stories.tsx create mode 100644 frontend/src/metabase/ui/components/inputs/TimeInput/TimeInput.mdx delete mode 100644 frontend/src/metabase/ui/components/inputs/TimeInput/TimeInput.stories.mdx create mode 100644 frontend/src/metabase/ui/components/inputs/TimeInput/TimeInput.stories.tsx create mode 100644 frontend/src/metabase/ui/components/navigation/Tabs/Tabs.mdx delete mode 100644 frontend/src/metabase/ui/components/navigation/Tabs/Tabs.stories.mdx create mode 100644 frontend/src/metabase/ui/components/navigation/Tabs/Tabs.stories.tsx create mode 100644 frontend/src/metabase/ui/components/overlays/HoverCard/HoverCard.mdx rename frontend/src/metabase/ui/components/overlays/HoverCard/{HoverCard.stories.mdx => HoverCard.stories.tsx} (52%) create mode 100644 frontend/src/metabase/ui/components/overlays/Menu/Menu.mdx rename frontend/src/metabase/ui/components/overlays/Menu/{Menu.stories.mdx => Menu.stories.tsx} (60%) create mode 100644 frontend/src/metabase/ui/components/overlays/Modal/Modal.mdx delete mode 100644 frontend/src/metabase/ui/components/overlays/Modal/Modal.stories.mdx create mode 100644 frontend/src/metabase/ui/components/overlays/Modal/Modal.stories.tsx create mode 100644 frontend/src/metabase/ui/components/overlays/Popover/Popover.mdx rename frontend/src/metabase/ui/components/overlays/Popover/{Popover.stories.mdx => Popover.stories.tsx} (53%) create mode 100644 frontend/src/metabase/ui/components/overlays/Tooltip/Tooltip.mdx delete mode 100644 frontend/src/metabase/ui/components/overlays/Tooltip/Tooltip.stories.mdx create mode 100644 frontend/src/metabase/ui/components/overlays/Tooltip/Tooltip.stories.tsx create mode 100644 frontend/src/metabase/ui/components/typography/Anchor/Anchor.mdx delete mode 100644 frontend/src/metabase/ui/components/typography/Anchor/Anchor.stories.mdx create mode 100644 frontend/src/metabase/ui/components/typography/Anchor/Anchor.stories.tsx create mode 100644 frontend/src/metabase/ui/components/typography/List/List.mdx rename frontend/src/metabase/ui/components/typography/List/{List.stories.mdx => List.stories.tsx} (61%) create mode 100644 frontend/src/metabase/ui/components/typography/Text/Text.mdx delete mode 100644 frontend/src/metabase/ui/components/typography/Text/Text.stories.mdx create mode 100644 frontend/src/metabase/ui/components/typography/Text/Text.stories.tsx create mode 100644 frontend/src/metabase/ui/components/typography/Title/Title.mdx delete mode 100644 frontend/src/metabase/ui/components/typography/Title/Title.stories.mdx create mode 100644 frontend/src/metabase/ui/components/typography/Title/Title.stories.tsx create mode 100644 frontend/src/metabase/ui/components/utils/Divider/Divider.mdx delete mode 100644 frontend/src/metabase/ui/components/utils/Divider/Divider.stories.mdx create mode 100644 frontend/src/metabase/ui/components/utils/Divider/Divider.stories.tsx create mode 100644 frontend/src/metabase/ui/components/utils/Paper/Paper.mdx rename frontend/src/metabase/ui/components/utils/Paper/{Paper.stories.mdx => Paper.stories.tsx} (54%) diff --git a/enterprise/frontend/src/embedding-sdk/components/private/SdkUsageProblem/SdkUsageProblemBanner.stories.tsx b/enterprise/frontend/src/embedding-sdk/components/private/SdkUsageProblem/SdkUsageProblemBanner.stories.tsx index d9f31fad6ee1a..3a72563e6b400 100644 --- a/enterprise/frontend/src/embedding-sdk/components/private/SdkUsageProblem/SdkUsageProblemBanner.stories.tsx +++ b/enterprise/frontend/src/embedding-sdk/components/private/SdkUsageProblem/SdkUsageProblemBanner.stories.tsx @@ -1,15 +1,18 @@ -import type { ComponentStory } from "@storybook/react"; +import type { StoryFn } from "@storybook/react"; import { Box } from "metabase/ui"; -import { SdkUsageProblemBanner } from "./SdkUsageProblemBanner"; +import { + SdkUsageProblemBanner, + type SdkUsageProblemBannerProps, +} from "./SdkUsageProblemBanner"; export default { title: "EmbeddingSDK/SdkUsageProblemBanner", component: SdkUsageProblemBanner, }; -const Template: ComponentStory = args => { +const Template: StoryFn = args => { return ( @@ -20,14 +23,18 @@ const Template: ComponentStory = args => { const MESSAGE = "The embedding SDK is using API keys. This is intended for evaluation purposes and works only on localhost. To use on other sites, implement SSO."; -export const Warning = Template.bind({}); +export const Warning = { + render: Template, -Warning.args = { - problem: { severity: "warning", message: MESSAGE }, + args: { + problem: { severity: "warning", message: MESSAGE }, + }, }; -export const Error = Template.bind({}); +export const Error = { + render: Template, -Error.args = { - problem: { severity: "error", message: MESSAGE }, + args: { + problem: { severity: "error", message: MESSAGE }, + }, }; diff --git a/enterprise/frontend/src/embedding-sdk/components/private/SdkUsageProblem/SdkUsageProblemBanner.tsx b/enterprise/frontend/src/embedding-sdk/components/private/SdkUsageProblem/SdkUsageProblemBanner.tsx index 6a4137627e10b..17a8df46e4b68 100644 --- a/enterprise/frontend/src/embedding-sdk/components/private/SdkUsageProblem/SdkUsageProblemBanner.tsx +++ b/enterprise/frontend/src/embedding-sdk/components/private/SdkUsageProblem/SdkUsageProblemBanner.tsx @@ -20,7 +20,7 @@ import { import S from "./SdkUsageProblemBanner.module.css"; -interface Props { +export interface SdkUsageProblemBannerProps { problem: SdkUsageProblem | null; } @@ -30,7 +30,9 @@ const unthemedBrand = originalColors["brand"]; const unthemedTextDark = originalColors["text-dark"]; const unthemedTextMedium = originalColors["text-medium"]; -export const SdkUsageProblemBanner = ({ problem }: Props) => { +export const SdkUsageProblemBanner = ({ + problem, +}: SdkUsageProblemBannerProps) => { const theme = useMantineTheme(); const [expanded, setExpanded] = useState(false); diff --git a/enterprise/frontend/src/embedding-sdk/components/public/CreateDashboardModal/CreateDashboardModal.stories.tsx b/enterprise/frontend/src/embedding-sdk/components/public/CreateDashboardModal/CreateDashboardModal.stories.tsx index c34718cbe6a31..06ba7b5b442dc 100644 --- a/enterprise/frontend/src/embedding-sdk/components/public/CreateDashboardModal/CreateDashboardModal.stories.tsx +++ b/enterprise/frontend/src/embedding-sdk/components/public/CreateDashboardModal/CreateDashboardModal.stories.tsx @@ -1,5 +1,5 @@ import { action } from "@storybook/addon-actions"; -import type { ComponentStory } from "@storybook/react"; +import type { StoryFn } from "@storybook/react"; import { type JSXElementConstructor, useState } from "react"; import { EditableDashboard } from "embedding-sdk/components/public"; @@ -19,16 +19,18 @@ export default { decorators: [CommonSdkStoryWrapper], }; -const Template: ComponentStory = () => ( +const Template: StoryFn = () => ( ); -export const Default = Template.bind({}); +export const Default = { + render: Template, +}; -const HookTemplate: ComponentStory< +const HookTemplate: StoryFn< JSXElementConstructor> > = () => { const [dashboard, setDashboard] = useState(null); @@ -57,9 +59,11 @@ const HookTemplate: ComponentStory< ); }; -export const useCreateDashboardApiHook = HookTemplate.bind({}); +export const useCreateDashboardApiHook = { + render: HookTemplate, +}; -const FullWorkflowExampleTemplate: ComponentStory< +const FullWorkflowExampleTemplate: StoryFn< JSXElementConstructor> > = () => { const [dashboard, setDashboard] = useState(null); @@ -73,4 +77,6 @@ const FullWorkflowExampleTemplate: ComponentStory< ); }; -export const FullWorkflowExample = FullWorkflowExampleTemplate.bind({}); +export const FullWorkflowExample = { + render: FullWorkflowExampleTemplate, +}; diff --git a/enterprise/frontend/src/embedding-sdk/components/public/InteractiveDashboard/EditableDashboard.stories.tsx b/enterprise/frontend/src/embedding-sdk/components/public/InteractiveDashboard/EditableDashboard.stories.tsx index 61877c1707edc..37d41d9436389 100644 --- a/enterprise/frontend/src/embedding-sdk/components/public/InteractiveDashboard/EditableDashboard.stories.tsx +++ b/enterprise/frontend/src/embedding-sdk/components/public/InteractiveDashboard/EditableDashboard.stories.tsx @@ -1,8 +1,11 @@ -import type { ComponentStory } from "@storybook/react"; +import type { StoryFn } from "@storybook/react"; import { CommonSdkStoryWrapper } from "embedding-sdk/test/CommonSdkStoryWrapper"; -import { EditableDashboard } from "./EditableDashboard"; +import { + EditableDashboard, + type EditableDashboardProps, +} from "./EditableDashboard"; const DASHBOARD_ID = (window as any).DASHBOARD_ID || 1; @@ -15,11 +18,14 @@ export default { decorators: [CommonSdkStoryWrapper], }; -const Template: ComponentStory = args => { +const Template: StoryFn = args => { return ; }; -export const Default = Template.bind({}); -Default.args = { - dashboardId: DASHBOARD_ID, +export const Default = { + render: Template, + + args: { + dashboardId: DASHBOARD_ID, + }, }; diff --git a/enterprise/frontend/src/embedding-sdk/components/public/InteractiveDashboard/InteractiveDashboard.stories.tsx b/enterprise/frontend/src/embedding-sdk/components/public/InteractiveDashboard/InteractiveDashboard.stories.tsx index 020a3033c138c..13889910356f4 100644 --- a/enterprise/frontend/src/embedding-sdk/components/public/InteractiveDashboard/InteractiveDashboard.stories.tsx +++ b/enterprise/frontend/src/embedding-sdk/components/public/InteractiveDashboard/InteractiveDashboard.stories.tsx @@ -1,8 +1,11 @@ -import type { ComponentStory } from "@storybook/react"; +import type { StoryFn } from "@storybook/react"; import { CommonSdkStoryWrapper } from "embedding-sdk/test/CommonSdkStoryWrapper"; -import { InteractiveDashboard } from "./InteractiveDashboard"; +import { + InteractiveDashboard, + type InteractiveDashboardProps, +} from "./InteractiveDashboard"; const DASHBOARD_ID = (window as any).DASHBOARD_ID || 1; @@ -15,11 +18,14 @@ export default { decorators: [CommonSdkStoryWrapper], }; -const Template: ComponentStory = args => { +const Template: StoryFn = args => { return ; }; -export const Default = Template.bind({}); -Default.args = { - dashboardId: DASHBOARD_ID, +export const Default = { + render: Template, + + args: { + dashboardId: DASHBOARD_ID, + }, }; diff --git a/enterprise/frontend/src/embedding-sdk/components/public/StaticDashboard/StaticDashboard.stories.tsx b/enterprise/frontend/src/embedding-sdk/components/public/StaticDashboard/StaticDashboard.stories.tsx index 2ef2c7f5be445..b3cb34bdb712a 100644 --- a/enterprise/frontend/src/embedding-sdk/components/public/StaticDashboard/StaticDashboard.stories.tsx +++ b/enterprise/frontend/src/embedding-sdk/components/public/StaticDashboard/StaticDashboard.stories.tsx @@ -1,8 +1,10 @@ -import type { ComponentStory } from "@storybook/react"; +import type { StoryFn } from "@storybook/react"; import { StaticDashboard } from "embedding-sdk"; import { CommonSdkStoryWrapper } from "embedding-sdk/test/CommonSdkStoryWrapper"; +import type { StaticDashboardProps } from "./StaticDashboard"; + const DASHBOARD_ID = (window as any).DASHBOARD_ID || "1"; export default { @@ -14,11 +16,14 @@ export default { decorators: [CommonSdkStoryWrapper], }; -const Template: ComponentStory = args => { +const Template: StoryFn = args => { return ; }; -export const Default = Template.bind({}); -Default.args = { - dashboardId: DASHBOARD_ID, +export const Default = { + render: Template, + + args: { + dashboardId: DASHBOARD_ID, + }, }; diff --git a/frontend/src/metabase/admin/app/components/DeprecationNotice/DeprecationNotice.stories.tsx b/frontend/src/metabase/admin/app/components/DeprecationNotice/DeprecationNotice.stories.tsx index ab72dd7918226..a0d425024ead1 100644 --- a/frontend/src/metabase/admin/app/components/DeprecationNotice/DeprecationNotice.stories.tsx +++ b/frontend/src/metabase/admin/app/components/DeprecationNotice/DeprecationNotice.stories.tsx @@ -1,6 +1,8 @@ -import type { ComponentStory } from "@storybook/react"; +import type { StoryObj } from "@storybook/react"; -import DeprecationNotice from "./DeprecationNotice"; +import DeprecationNotice, { + type DeprecationNoticeProps, +} from "./DeprecationNotice"; export default { title: "Admin/App/DeprecationNotice", @@ -10,12 +12,14 @@ export default { }, }; -export const Default: ComponentStory = args => { - return ; -}; +export const Default: StoryObj = { + render: args => { + return ; + }, -Default.args = { - hasSlackBot: true, - hasDeprecatedDatabase: true, - isEnabled: true, + args: { + hasSlackBot: true, + hasDeprecatedDatabase: true, + isEnabled: true, + }, }; diff --git a/frontend/src/metabase/admin/settings/slack/components/SlackSetup/SlackSetup.stories.tsx b/frontend/src/metabase/admin/settings/slack/components/SlackSetup/SlackSetup.stories.tsx index a8cc5e93444a5..49c6d3428177e 100644 --- a/frontend/src/metabase/admin/settings/slack/components/SlackSetup/SlackSetup.stories.tsx +++ b/frontend/src/metabase/admin/settings/slack/components/SlackSetup/SlackSetup.stories.tsx @@ -1,6 +1,6 @@ -import type { ComponentStory } from "@storybook/react"; +import type { StoryObj } from "@storybook/react"; -import SlackSetup from "./SlackSetup"; +import SlackSetup, { type SlackSetupProps } from "./SlackSetup"; export default { title: "Admin/Settings/Slack/SlackSetup", @@ -11,13 +11,15 @@ export default { }, }; -export const Default: ComponentStory = args => { - return ; -}; +export const Default: StoryObj = { + render: args => { + return ; + }, -Default.args = { - Form: () =>
, - manifest: "app: token", - isBot: false, - isValid: true, + args: { + Form: () =>
, + manifest: "app: token", + isBot: false, + isValid: true, + }, }; diff --git a/frontend/src/metabase/admin/settings/slack/components/SlackStatus/SlackStatus.stories.tsx b/frontend/src/metabase/admin/settings/slack/components/SlackStatus/SlackStatus.stories.tsx index c2f0b23cd4c5f..a30fa6e14a553 100644 --- a/frontend/src/metabase/admin/settings/slack/components/SlackStatus/SlackStatus.stories.tsx +++ b/frontend/src/metabase/admin/settings/slack/components/SlackStatus/SlackStatus.stories.tsx @@ -1,6 +1,6 @@ -import type { ComponentStory } from "@storybook/react"; +import type { StoryObj } from "@storybook/react"; -import SlackStatus from "./SlackStatus"; +import SlackStatus, { type SlackStatusProps } from "./SlackStatus"; export default { title: "Admin/Settings/Slack/SlackStatus", @@ -11,11 +11,13 @@ export default { }, }; -export const Default: ComponentStory = args => { - return ; -}; +export const Default: StoryObj = { + render: args => { + return ; + }, -Default.args = { - Form: () =>
, - isValid: true, + args: { + Form: () =>
, + isValid: true, + }, }; diff --git a/frontend/src/metabase/admin/upsells/components/UpsellCard.mdx b/frontend/src/metabase/admin/upsells/components/UpsellCard.mdx new file mode 100644 index 0000000000000..947adc51a199d --- /dev/null +++ b/frontend/src/metabase/admin/upsells/components/UpsellCard.mdx @@ -0,0 +1,20 @@ +import { Canvas, Story, Meta } from "@storybook/blocks"; +import { ReduxProvider } from "__support__/storybook"; +import { _UpsellCard } from "./UpsellCard"; +import * as UpsellCardStories from "./UpsellCard.stories"; + + + +# Upsell Card + +- Use as a small, visible upsell, with or without an image + +## Examples + + + + + + + + diff --git a/frontend/src/metabase/admin/upsells/components/UpsellCard.stories.mdx b/frontend/src/metabase/admin/upsells/components/UpsellCard.stories.tsx similarity index 50% rename from frontend/src/metabase/admin/upsells/components/UpsellCard.stories.mdx rename to frontend/src/metabase/admin/upsells/components/UpsellCard.stories.tsx index 12d7f22cf7b52..56b8f6e6520da 100644 --- a/frontend/src/metabase/admin/upsells/components/UpsellCard.stories.mdx +++ b/frontend/src/metabase/admin/upsells/components/UpsellCard.stories.tsx @@ -1,8 +1,9 @@ -import { Canvas, Story, Meta } from "@storybook/addon-docs"; import { ReduxProvider } from "__support__/storybook"; -import { _UpsellCard } from "./UpsellCard"; +import { Flex } from "metabase/ui"; -export const args = { +import { type UpsellCardProps, _UpsellCard } from "./UpsellCard"; + +const args = { title: "Ice Cream", buttonText: "Get Some", buttonLink: "https://www.metabase.com", @@ -12,7 +13,7 @@ export const args = { children: "You wouldn't believe how great this stuff is.", }; -export const argTypes = { +const argTypes = { children: { control: { type: "text" }, }, @@ -31,25 +32,9 @@ export const argTypes = { source: { control: { type: "text" }, }, - children: { - control: { type: "text" }, - } }; - - -# Upsell Card - -- Use as a small, visible upsell, with or without an image - -## Examples - -export const DefaultTemplate = (args) => ( +const DefaultTemplate = (args: UpsellCardProps) => ( <_UpsellCard {...args} /> @@ -57,15 +42,20 @@ export const DefaultTemplate = (args) => ( ); -export const WithImage = DefaultTemplate.bind({}); - -export const WithoutImage = DefaultTemplate.bind({}); -WithoutImage.args = { ...args, illustrationSrc: null}; +export default { + title: "Upsells/Card", + component: _UpsellCard, + args, + argTypes, +}; - - {WithImage} - +export const WithImage = { + render: DefaultTemplate, + name: "With Image", +}; - - {WithoutImage} - +export const WithoutImage = { + render: DefaultTemplate, + name: "Without Image", + args: { ...args, illustrationSrc: null }, +}; diff --git a/frontend/src/metabase/admin/upsells/components/UpsellCard.tsx b/frontend/src/metabase/admin/upsells/components/UpsellCard.tsx index 4717eb93cb15a..4e97369f9d9e7 100644 --- a/frontend/src/metabase/admin/upsells/components/UpsellCard.tsx +++ b/frontend/src/metabase/admin/upsells/components/UpsellCard.tsx @@ -31,7 +31,7 @@ interface FixedWidthVariant { type Variants = FullWidthVariant | FixedWidthVariant; -type UpsellCardProps = OwnProps & Variants; +export type UpsellCardProps = OwnProps & Variants; export const _UpsellCard: React.FC = ({ title, diff --git a/frontend/src/metabase/admin/upsells/components/UpsellPill.mdx b/frontend/src/metabase/admin/upsells/components/UpsellPill.mdx new file mode 100644 index 0000000000000..fdaa3b8f814a1 --- /dev/null +++ b/frontend/src/metabase/admin/upsells/components/UpsellPill.mdx @@ -0,0 +1,19 @@ +import { Box } from "metabase/ui"; +import { Canvas, Story, Meta } from "@storybook/blocks"; +import { ReduxProvider } from "__support__/storybook"; +import { _UpsellPill } from "./UpsellPill"; +import * as UpsellPillStories from "./UpsellPill.stories"; + + + +# Upsell Pill + +## Examples + + + + + + + + diff --git a/frontend/src/metabase/admin/upsells/components/UpsellPill.stories.mdx b/frontend/src/metabase/admin/upsells/components/UpsellPill.stories.tsx similarity index 53% rename from frontend/src/metabase/admin/upsells/components/UpsellPill.stories.mdx rename to frontend/src/metabase/admin/upsells/components/UpsellPill.stories.tsx index 79d4cb9e888f5..efde7919e2c6f 100644 --- a/frontend/src/metabase/admin/upsells/components/UpsellPill.stories.mdx +++ b/frontend/src/metabase/admin/upsells/components/UpsellPill.stories.tsx @@ -1,16 +1,18 @@ -import { Box } from "metabase/ui"; -import { Canvas, Story, Meta } from "@storybook/addon-docs"; +import type { ComponentProps } from "react"; + import { ReduxProvider } from "__support__/storybook"; +import { Box } from "metabase/ui"; + import { _UpsellPill } from "./UpsellPill"; -export const args = { +const args = { children: "Metabase Enterprise is so great", link: "https://www.metabase.com", campaign: "enterprise", source: "enterprise-page-footer", }; -export const argTypes = { +const argTypes = { children: { control: { type: "text" }, }, @@ -25,19 +27,9 @@ export const argTypes = { }, }; - +type UpsellPillProps = ComponentProps; -# Upsell Pill - - -## Examples - -export const DefaultTemplate = (args) => ( +const DefaultTemplate = (args: UpsellPillProps) => ( <_UpsellPill {...args} /> @@ -45,26 +37,27 @@ export const DefaultTemplate = (args) => ( ); -export const Default = DefaultTemplate.bind({}); - - -export const NarrowTemplate = (args) => ( +const NarrowTemplate = (args: UpsellPillProps) => ( - + <_UpsellPill {...args} /> ); -export const Narrow = NarrowTemplate.bind({}); - - - {Default} - - - - - {Narrow} - +export default { + title: "Upsells/Pill", + component: _UpsellPill, + args, + argTypes, +}; +export const Default = { + render: DefaultTemplate, + name: "Default", +}; +export const Multiline = { + render: NarrowTemplate, + name: "Multiline", +}; diff --git a/frontend/src/metabase/collections/components/PinnedItemCard/PinnedItemCard.stories.tsx b/frontend/src/metabase/collections/components/PinnedItemCard/PinnedItemCard.stories.tsx index c8cb966b6a9d5..81563327e643f 100644 --- a/frontend/src/metabase/collections/components/PinnedItemCard/PinnedItemCard.stories.tsx +++ b/frontend/src/metabase/collections/components/PinnedItemCard/PinnedItemCard.stories.tsx @@ -1,7 +1,7 @@ import { action } from "@storybook/addon-actions"; -import type { ComponentStory } from "@storybook/react"; +import type { StoryFn } from "@storybook/react"; -import PinnedItemCard from "./PinnedItemCard"; +import PinnedItemCard, { type PinnedItemCardProps } from "./PinnedItemCard"; export default { title: "Collections/PinnedItemCard", @@ -22,70 +22,79 @@ const collection = { const onCopy = action("onCopy"); const onMove = action("onMove"); -const Template: ComponentStory = args => { +const Template: StoryFn = args => { return ; }; -export const Question = Template.bind({}); -Question.args = { - collection, - item: { - id: 1, - collection_position: 1, - collection_id: null, - model: "card", - name: "Question", - description: "This is a description of the question", - getIcon: () => ({ name: "question" }), - getUrl: () => "/question/1", - setArchived: action("setArchived"), - setPinned: action("setPinned"), - copy: true, - setCollection: action("setCollection"), - archived: false, +export const Question = { + render: Template, + + args: { + collection, + item: { + id: 1, + collection_position: 1, + collection_id: null, + model: "card", + name: "Question", + description: "This is a description of the question", + getIcon: () => ({ name: "question" }), + getUrl: () => "/question/1", + setArchived: action("setArchived"), + setPinned: action("setPinned"), + copy: true, + setCollection: action("setCollection"), + archived: false, + }, + onCopy, + onMove, }, - onCopy, - onMove, }; -export const Dashboard = Template.bind({}); -Dashboard.args = { - collection, - item: { - id: 1, - model: "dashboard", - collection_position: 1, - collection_id: null, - name: "Dashboard", - description: Array(20) - .fill("This is a description of the dashboard.") - .join(" "), - getIcon: () => ({ name: "dashboard" }), - getUrl: () => "/dashboard/1", - setArchived: action("setArchived"), - setPinned: action("setPinned"), - archived: false, +export const Dashboard = { + render: Template, + + args: { + collection, + item: { + id: 1, + model: "dashboard", + collection_position: 1, + collection_id: null, + name: "Dashboard", + description: Array(20) + .fill("This is a description of the dashboard.") + .join(" "), + getIcon: () => ({ name: "dashboard" }), + getUrl: () => "/dashboard/1", + setArchived: action("setArchived"), + setPinned: action("setPinned"), + archived: false, + }, + onCopy, + onMove, }, - onCopy, - onMove, }; -export const Model = Template.bind({}); -Model.args = { - collection, - item: { - id: 1, - model: "dataset", - collection_position: 1, - collection_id: null, - name: "Model", - description: "This is a description of the model", - getIcon: () => ({ name: "model" }), - getUrl: () => "/question/1", - setArchived: action("setArchived"), - setPinned: action("setPinned"), - archived: false, +export const Model = { + render: Template, + + args: { + collection, + item: { + id: 1, + model: "dataset", + collection_position: 1, + collection_id: null, + name: "Model", + description: "This is a description of the model", + getIcon: () => ({ name: "model" }), + getUrl: () => "/question/1", + setArchived: action("setArchived"), + setPinned: action("setPinned"), + archived: false, + }, + onCopy, + onMove, }, - onCopy, - onMove, }; diff --git a/frontend/src/metabase/collections/components/PinnedItemCard/PinnedItemCard.tsx b/frontend/src/metabase/collections/components/PinnedItemCard/PinnedItemCard.tsx index 66e6885e9a647..eca0615b06b28 100644 --- a/frontend/src/metabase/collections/components/PinnedItemCard/PinnedItemCard.tsx +++ b/frontend/src/metabase/collections/components/PinnedItemCard/PinnedItemCard.tsx @@ -45,7 +45,7 @@ type ItemOrSkeleton = iconForSkeleton: IconName; }; -type Props = { +export type PinnedItemCardProps = { databases?: Database[]; bookmarks?: Bookmark[]; createBookmark?: CreateBookmark; @@ -84,7 +84,7 @@ function PinnedItemCard({ onMove, onClick, iconForSkeleton, -}: Props) { +}: PinnedItemCardProps) { const [showTitleTooltip, setShowTitleTooltip] = useState(false); const icon = iconForSkeleton ?? diff --git a/frontend/src/metabase/common/components/Sidesheet/Sidesheet.mdx b/frontend/src/metabase/common/components/Sidesheet/Sidesheet.mdx new file mode 100644 index 0000000000000..ac08230a5b236 --- /dev/null +++ b/frontend/src/metabase/common/components/Sidesheet/Sidesheet.mdx @@ -0,0 +1,60 @@ +import { Canvas, Story, Meta } from "@storybook/blocks"; +import { Box, Flex } from "metabase/ui"; +import * as SidesheetStories from "./Sidesheet.stories"; + +import { Sidesheet } from "./Sidesheet"; +import { SidesheetCard } from "./SidesheetCard"; +import { SidesheetCardSection } from "./SidesheetCardSection"; +import { SidesheetButton, SidesheetButtonWithChevron } from "./SidesheetButton"; + +import { TestTabbedSidesheet, TestPagedSidesheet } from "./Sidesheet.samples"; + + + +# Sidesheet + +## When to use a Sidesheet + +## Docs + +## Caveats + +## Usage guidelines + +## Examples + +// TODO: figure out how to get CSS modules working with storybook 🔥 + + + + + +### With cards + + + + + +### With sectioned cards + + + + + +### With pages + + + + + +### With tabs + + + + + +### Sidesheet Buttons + + + + diff --git a/frontend/src/metabase/common/components/Sidesheet/Sidesheet.stories.mdx b/frontend/src/metabase/common/components/Sidesheet/Sidesheet.stories.mdx deleted file mode 100644 index 1a9348b3393e4..0000000000000 --- a/frontend/src/metabase/common/components/Sidesheet/Sidesheet.stories.mdx +++ /dev/null @@ -1,165 +0,0 @@ -import { Canvas, Story, Meta } from "@storybook/addon-docs"; -import { Box, Flex } from "metabase/ui"; - -import { Sidesheet } from "./Sidesheet"; -import { SidesheetCard } from "./SidesheetCard"; -import { SidesheetCardSection } from "./SidesheetCardSection"; -import { SidesheetButton, SidesheetButtonWithChevron } from "./SidesheetButton"; - -import { TestTabbedSidesheet, TestPagedSidesheet} from "./Sidesheet.samples"; - -export const args = { - size: "md", - title: "My Awesome Sidesheet", - onClose: () => {}, - isOpen: true, -}; - -export const argTypes = { - size: { - options: ["xs", "sm", "md", "lg", "xl", "auto"], - control: { type: "inline-radio" }, - }, - title: { - control: { type: "text" }, - }, - isOpen: { - control: { type: "boolean" }, - } -}; - - - -# Sidesheet - - -## When to use a Sidesheet - - - -## Docs - - -## Caveats - - - -## Usage guidelines - - - -## Examples - -// TODO: figure out how to get CSS modules working with storybook 🔥 - -export const DefaultTemplate = args => ( - - Call me Ishmael ... - -); - -export const WithCardsTemplate = args => ( - - - Here is even more cool information - - - titles are neat - - -); - -export const WithSectionedCardsTemplate = args => ( - - - - Some cards have so much information - - - that you need a bunch - - - of sections to display it all - - - -); - -export const PagedSidesheetTemplate = () => ( - -); - -export const TabbedSidesheetTemplate = () => ( - -); - - -export const SidesheetButtonTemplate = () => ( - - - - Do something fun - - - - - Favorite Pokemon - - Naclstack - - - - - - Configure favorite pokemon - - - -); - -export const Default = DefaultTemplate.bind({}); - - - {Default} - - -### With cards - -export const WithCards = WithCardsTemplate.bind({}); - - - {WithCards} - - - -### With sectioned cards - -export const WithSectionedCards = WithSectionedCardsTemplate.bind({}); - - - {WithSectionedCards} - - -### With pages - -export const WithPages = PagedSidesheetTemplate.bind({}); - - - {WithPages} - - -### With tabs - -export const WithTabs = TabbedSidesheetTemplate.bind({}); - - - {WithTabs} - - -### Sidesheet Buttons - -export const SidesheetButtonStory = SidesheetButtonTemplate.bind({}); - - - {SidesheetButtonStory} - diff --git a/frontend/src/metabase/common/components/Sidesheet/Sidesheet.stories.tsx b/frontend/src/metabase/common/components/Sidesheet/Sidesheet.stories.tsx new file mode 100644 index 0000000000000..4fe79f4d90dd3 --- /dev/null +++ b/frontend/src/metabase/common/components/Sidesheet/Sidesheet.stories.tsx @@ -0,0 +1,120 @@ +import type { ComponentProps } from "react"; + +import { Flex } from "metabase/ui"; + +import { Sidesheet } from "./Sidesheet"; +import { TestPagedSidesheet, TestTabbedSidesheet } from "./Sidesheet.samples"; +import { SidesheetButton, SidesheetButtonWithChevron } from "./SidesheetButton"; +import { SidesheetCard } from "./SidesheetCard"; +import { SidesheetCardSection } from "./SidesheetCardSection"; + +const args = { + size: "md", + title: "My Awesome Sidesheet", + onClose: () => {}, + isOpen: true, +}; + +const argTypes = { + size: { + options: ["xs", "sm", "md", "lg", "xl", "auto"], + control: { type: "inline-radio" }, + }, + title: { + control: { type: "text" }, + }, + isOpen: { + control: { type: "boolean" }, + }, +}; + +type SidesheetProps = ComponentProps; + +const DefaultTemplate = (args: SidesheetProps) => ( + Call me Ishmael ... +); + +const WithCardsTemplate = (args: SidesheetProps) => ( + + Here is even more cool information + + titles are neat + + +); + +const WithSectionedCardsTemplate = (args: SidesheetProps) => ( + + + + Some cards have so much information + + + that you need a bunch + + + of sections to display it all + + + +); + +const PagedSidesheetTemplate = () => ; + +const TabbedSidesheetTemplate = () => ; + +const SidesheetButtonTemplate = () => ( + + + Do something fun + + + + Favorite Pokemon + Naclstack + + + + + Configure favorite pokemon + + + +); + +export default { + title: "Components/Sidesheet", + component: Sidesheet, + args, + argTypes, +}; + +export const Default = { + render: DefaultTemplate, + name: "Default", +}; + +export const WithCards = { + render: WithCardsTemplate, + name: "With cards", +}; + +export const WithSectionedCards = { + render: WithSectionedCardsTemplate, + name: "With sectioned cards", +}; + +export const WithSubPages = { + render: PagedSidesheetTemplate, + name: "With sub pages", +}; + +export const WithTabs = { + render: TabbedSidesheetTemplate, + name: "With tabs", +}; + +export const SidesheetButtons = { + render: SidesheetButtonTemplate, + name: "Sidesheet buttons", +}; diff --git a/frontend/src/metabase/components/Calendar/Calendar.stories.tsx b/frontend/src/metabase/components/Calendar/Calendar.stories.tsx index 9ca7f37f9ace5..543f3cd28e7d1 100644 --- a/frontend/src/metabase/components/Calendar/Calendar.stories.tsx +++ b/frontend/src/metabase/components/Calendar/Calendar.stories.tsx @@ -1,4 +1,4 @@ -import type { ComponentStory } from "@storybook/react"; +import type { StoryFn } from "@storybook/react"; import Calendar from "./Calendar"; @@ -7,9 +7,11 @@ export default { component: Calendar, }; -const Template: ComponentStory = args => { +const Template: StoryFn = args => { return ; }; -export const Default = Template.bind({}); -Default.args = {}; +export const Default = { + render: Template, + args: {}, +}; diff --git a/frontend/src/metabase/components/DateMonthYearWidget/DateMonthYearWidget.stories.tsx b/frontend/src/metabase/components/DateMonthYearWidget/DateMonthYearWidget.stories.tsx index 3560f1a04c778..b373787865b7e 100644 --- a/frontend/src/metabase/components/DateMonthYearWidget/DateMonthYearWidget.stories.tsx +++ b/frontend/src/metabase/components/DateMonthYearWidget/DateMonthYearWidget.stories.tsx @@ -1,5 +1,5 @@ -import { useArgs } from "@storybook/client-api"; -import type { ComponentStory } from "@storybook/react"; +import { useArgs } from "@storybook/preview-api"; +import type { StoryFn } from "@storybook/react"; import { DateMonthYearWidget } from "./DateMonthYearWidget"; @@ -8,7 +8,7 @@ export default { component: DateMonthYearWidget, }; -const Template: ComponentStory = args => { +const Template: StoryFn = args => { const [{ value }, updateArgs] = useArgs(); const handleSetValue = (v: string) => { @@ -29,17 +29,26 @@ const Template: ComponentStory = args => { ); }; -export const Default = Template.bind({}); -Default.args = { - value: "", +export const Default = { + render: Template, + + args: { + value: "", + }, }; -export const ThisYear = Template.bind({}); -ThisYear.args = { - value: "2022", +export const ThisYear = { + render: Template, + + args: { + value: "2022", + }, }; -export const LastYear = Template.bind({}); -LastYear.args = { - value: "2021-07", +export const LastYear = { + render: Template, + + args: { + value: "2021-07", + }, }; diff --git a/frontend/src/metabase/components/DateQuarterYearWidget/DateQuarterYearWidget.stories.tsx b/frontend/src/metabase/components/DateQuarterYearWidget/DateQuarterYearWidget.stories.tsx index 035c0e1a622c3..b0270e56135bf 100644 --- a/frontend/src/metabase/components/DateQuarterYearWidget/DateQuarterYearWidget.stories.tsx +++ b/frontend/src/metabase/components/DateQuarterYearWidget/DateQuarterYearWidget.stories.tsx @@ -1,5 +1,5 @@ -import { useArgs } from "@storybook/addons"; -import type { ComponentStory } from "@storybook/react"; +import { useArgs } from "@storybook/preview-api"; +import type { StoryFn } from "@storybook/react"; import { DateQuarterYearWidget } from "./DateQuarterYearWidget"; @@ -8,7 +8,7 @@ export default { component: DateQuarterYearWidget, }; -const Template: ComponentStory = args => { +const Template: StoryFn = args => { const [{ value }, updateArgs] = useArgs(); const handleSetValue = (v: string) => { @@ -29,17 +29,26 @@ const Template: ComponentStory = args => { ); }; -export const Default = Template.bind({}); -Default.args = { - value: "", +export const Default = { + render: Template, + + args: { + value: "", + }, }; -export const SomeTimeLastYear = Template.bind({}); -SomeTimeLastYear.args = { - value: "4-2021", +export const SomeTimeLastYear = { + render: Template, + + args: { + value: "4-2021", + }, }; -export const SomeTimeAgo = Template.bind({}); -SomeTimeAgo.args = { - value: "2-1981", +export const SomeTimeAgo = { + render: Template, + + args: { + value: "2-1981", + }, }; diff --git a/frontend/src/metabase/components/DateRelativeWidget/DateRelativeWidget.stories.tsx b/frontend/src/metabase/components/DateRelativeWidget/DateRelativeWidget.stories.tsx index 6c7eadbc8097a..7d25bdd57455b 100644 --- a/frontend/src/metabase/components/DateRelativeWidget/DateRelativeWidget.stories.tsx +++ b/frontend/src/metabase/components/DateRelativeWidget/DateRelativeWidget.stories.tsx @@ -1,5 +1,5 @@ -import { useArgs } from "@storybook/addons"; -import type { ComponentStory } from "@storybook/react"; +import { useArgs } from "@storybook/preview-api"; +import type { StoryFn } from "@storybook/react"; import { DateRelativeWidget } from "./DateRelativeWidget"; @@ -8,7 +8,7 @@ export default { component: DateRelativeWidget, }; -const Template: ComponentStory = args => { +const Template: StoryFn = args => { const [{ value }, updateArgs] = useArgs(); const handleSetValue = (v?: string) => { @@ -29,22 +29,34 @@ const Template: ComponentStory = args => { ); }; -export const Default = Template.bind({}); -Default.args = { - value: "", +export const Default = { + render: Template, + + args: { + value: "", + }, }; -export const Yesterday = Template.bind({}); -Yesterday.args = { - value: "yesterday", +export const Yesterday = { + render: Template, + + args: { + value: "yesterday", + }, }; -export const LastMonth = Template.bind({}); -LastMonth.args = { - value: "lastmonth", +export const LastMonth = { + render: Template, + + args: { + value: "lastmonth", + }, }; -export const ThisWeek = Template.bind({}); -ThisWeek.args = { - value: "thisweek", +export const ThisWeek = { + render: Template, + + args: { + value: "thisweek", + }, }; diff --git a/frontend/src/metabase/components/EntityMenu.stories.tsx b/frontend/src/metabase/components/EntityMenu.stories.tsx index db3bcef951d45..2e554217aa99e 100644 --- a/frontend/src/metabase/components/EntityMenu.stories.tsx +++ b/frontend/src/metabase/components/EntityMenu.stories.tsx @@ -1,4 +1,4 @@ -import type { ComponentStory } from "@storybook/react"; +import type { StoryFn } from "@storybook/react"; import EntityMenu from "./EntityMenu"; @@ -7,7 +7,7 @@ export default { component: EntityMenu, }; -const Template: ComponentStory = args => { +const Template: StoryFn = args => { return ; }; @@ -30,8 +30,11 @@ const items = [ }, ]; -export const Default = Template.bind({}); -Default.args = { - items, - trigger: Click Me, +export const Default = { + render: Template, + + args: { + items, + trigger: Click Me, + }, }; diff --git a/frontend/src/metabase/components/HelpCard/HelpCard.stories.tsx b/frontend/src/metabase/components/HelpCard/HelpCard.stories.tsx index 9e014945d2c21..25c02f9ffb3c0 100644 --- a/frontend/src/metabase/components/HelpCard/HelpCard.stories.tsx +++ b/frontend/src/metabase/components/HelpCard/HelpCard.stories.tsx @@ -1,20 +1,23 @@ -import type { ComponentStory } from "@storybook/react"; +import type { StoryFn } from "@storybook/react"; -import HelpCard from "./HelpCard"; +import HelpCard, { type HelpCardProps } from "./HelpCard"; export default { title: "Components/HelpCard", component: HelpCard, }; -const Template: ComponentStory = args => { +const Template: StoryFn = args => { return ; }; -export const Default = Template.bind({}); -Default.args = { - title: "Need help with anything?", - helpUrl: "https://metabase.com", - children: - "See our docs for step-by-step directions on how to do what you need.", +export const Default = { + render: Template, + + args: { + title: "Need help with anything?", + helpUrl: "https://metabase.com", + children: + "See our docs for step-by-step directions on how to do what you need.", + }, }; diff --git a/frontend/src/metabase/components/ModalContent/ModalContent.stories.tsx b/frontend/src/metabase/components/ModalContent/ModalContent.stories.tsx index f03e257be8c2b..c2281e4c723b9 100644 --- a/frontend/src/metabase/components/ModalContent/ModalContent.stories.tsx +++ b/frontend/src/metabase/components/ModalContent/ModalContent.stories.tsx @@ -1,9 +1,12 @@ import { action } from "@storybook/addon-actions"; -import type { ComponentStory } from "@storybook/react"; +import type { StoryFn } from "@storybook/react"; import { color } from "metabase/lib/colors"; -import ModalContent, { ModalContentActionIcon } from "./index"; +import ModalContent, { + ModalContentActionIcon, + type ModalContentProps, +} from "./index"; export default { title: "Components/ModalContent", @@ -24,7 +27,7 @@ export default { }, }; -const Template: ComponentStory = args => { +const Template: StoryFn = args => { return (
- - - ), +export const WithHeaderActions = { + render: Template, + + args: { + ...args, + headerActions: ( + <> + + + ), + }, }; -export const WithBackButton = Template.bind({}); -WithBackButton.args = { - ...args, - onBack: action("onBack"), +export const WithBackButton = { + render: Template, + + args: { + ...args, + onBack: action("onBack"), + }, }; diff --git a/frontend/src/metabase/components/Schedule/Schedule.stories.tsx b/frontend/src/metabase/components/Schedule/Schedule.stories.tsx index fdce96762fc5b..e4b6a130926dd 100644 --- a/frontend/src/metabase/components/Schedule/Schedule.stories.tsx +++ b/frontend/src/metabase/components/Schedule/Schedule.stories.tsx @@ -1,5 +1,5 @@ -import { useArgs } from "@storybook/addons"; -import type { ComponentStory } from "@storybook/react"; +import { useArgs } from "@storybook/preview-api"; +import type { StoryFn } from "@storybook/react"; import { Schedule } from "./Schedule"; @@ -8,7 +8,7 @@ export default { component: Schedule, }; -const Template: ComponentStory = args => { +const Template: StoryFn = args => { const [ { schedule, @@ -29,13 +29,16 @@ const Template: ComponentStory = args => { ); }; -export const Default = Template.bind({}); -Default.args = { - schedule: { - schedule_day: "mon", - schedule_frame: null, - schedule_hour: 0, - schedule_type: "daily", +export const Default = { + render: Template, + + args: { + schedule: { + schedule_day: "mon", + schedule_frame: null, + schedule_hour: 0, + schedule_type: "daily", + }, + verb: "Deliver", }, - verb: "Deliver", }; diff --git a/frontend/src/metabase/components/SchedulePicker/SchedulePicker.stories.tsx b/frontend/src/metabase/components/SchedulePicker/SchedulePicker.stories.tsx index 57b89f1b8660f..0b85d9e0865fb 100644 --- a/frontend/src/metabase/components/SchedulePicker/SchedulePicker.stories.tsx +++ b/frontend/src/metabase/components/SchedulePicker/SchedulePicker.stories.tsx @@ -1,5 +1,5 @@ -import { useArgs } from "@storybook/addons"; -import type { ComponentStory } from "@storybook/react"; +import { useArgs } from "@storybook/preview-api"; +import type { StoryFn } from "@storybook/react"; import SchedulePicker from "./SchedulePicker"; @@ -8,7 +8,7 @@ export default { component: SchedulePicker, }; -const Template: ComponentStory = args => { +const Template: StoryFn = args => { const [ { schedule, @@ -29,13 +29,16 @@ const Template: ComponentStory = args => { ); }; -export const Default = Template.bind({}); -Default.args = { - schedule: { - schedule_day: "mon", - schedule_frame: null, - schedule_hour: 0, - schedule_type: "daily", +export const Default = { + render: Template, + + args: { + schedule: { + schedule_day: "mon", + schedule_frame: null, + schedule_hour: 0, + schedule_type: "daily", + }, + textBeforeInterval: "Deliver", }, - textBeforeInterval: "Deliver", }; diff --git a/frontend/src/metabase/components/SegmentedControl/SegmentedControl.stories.tsx b/frontend/src/metabase/components/SegmentedControl/SegmentedControl.stories.tsx index 379b035ea14f8..c791573561c6d 100644 --- a/frontend/src/metabase/components/SegmentedControl/SegmentedControl.stories.tsx +++ b/frontend/src/metabase/components/SegmentedControl/SegmentedControl.stories.tsx @@ -1,14 +1,17 @@ -import { useArgs } from "@storybook/addons"; -import type { ComponentStory } from "@storybook/react"; +import { useArgs } from "@storybook/preview-api"; +import type { StoryFn } from "@storybook/react"; -import { SegmentedControl } from "./SegmentedControl"; +import { + SegmentedControl, + type SegmentedControlProps, +} from "./SegmentedControl"; export default { title: "Components/SegmentedControl", component: SegmentedControl, }; -const Template: ComponentStory = args => { +const Template: StoryFn> = args => { const [{ value }, updateArgs] = useArgs(); const handleChange = (value: unknown) => updateArgs({ value }); @@ -19,42 +22,54 @@ Template.args = { value: 0, }; -export const Default = Template.bind({}); -Default.args = { - options: [ - { name: "Gadget", value: 0 }, - { name: "Gizmo", value: 1 }, - ], +export const Default = { + render: Template, + + args: { + options: [ + { name: "Gadget", value: 0 }, + { name: "Gizmo", value: 1 }, + ], + }, }; -export const WithIcons = Template.bind({}); -WithIcons.args = { - options: [ - { name: "Gadget", value: 0, icon: "lightbulb" }, - { name: "Gizmo", value: 1, icon: "folder" }, - { name: "Doohickey", value: 2, icon: "insight" }, - ], +export const WithIcons = { + render: Template, + + args: { + options: [ + { name: "Gadget", value: 0, icon: "lightbulb" }, + { name: "Gizmo", value: 1, icon: "folder" }, + { name: "Doohickey", value: 2, icon: "insight" }, + ], + }, }; -export const OnlyIcons = Template.bind({}); -OnlyIcons.args = { - options: [ - { value: 0, icon: "lightbulb" }, - { value: 1, icon: "folder" }, - { value: 2, icon: "insight" }, - ], +export const OnlyIcons = { + render: Template, + + args: { + options: [ + { value: 0, icon: "lightbulb" }, + { value: 1, icon: "folder" }, + { value: 2, icon: "insight" }, + ], + }, }; -export const WithColors = Template.bind({}); -WithColors.args = { - options: [ - { - name: "Gadget", - value: 0, - icon: "lightbulb", - selectedColor: "accent1", - }, - { name: "Gizmo", value: 1, icon: "folder", selectedColor: "accent2" }, - { name: "Doohickey", value: 2, icon: "insight" }, - ], +export const WithColors = { + render: Template, + + args: { + options: [ + { + name: "Gadget", + value: 0, + icon: "lightbulb", + selectedColor: "accent1", + }, + { name: "Gizmo", value: 1, icon: "folder", selectedColor: "accent2" }, + { name: "Doohickey", value: 2, icon: "insight" }, + ], + }, }; diff --git a/frontend/src/metabase/components/SegmentedControl/SegmentedControl.tsx b/frontend/src/metabase/components/SegmentedControl/SegmentedControl.tsx index c2864fc65946f..07777f5f3311b 100644 --- a/frontend/src/metabase/components/SegmentedControl/SegmentedControl.tsx +++ b/frontend/src/metabase/components/SegmentedControl/SegmentedControl.tsx @@ -26,7 +26,7 @@ export type SegmentedControlOption = { selectedColor?: string; }; -interface Props { +export interface SegmentedControlProps { name?: string; value?: Value; options: SegmentedControlOption[]; @@ -47,7 +47,7 @@ export function SegmentedControl({ inactiveColor = "text-dark", variant = "fill-background", ...props -}: Props) { +}: SegmentedControlProps) { const id = useMemo(() => _.uniqueId("radio-"), []); const name = nameProp || id; const selectedOptionIndex = options.findIndex( diff --git a/frontend/src/metabase/components/SelectList/SelectListItem.stories.tsx b/frontend/src/metabase/components/SelectList/SelectListItem.stories.tsx index ee3c6766a1445..fb3817e3714ef 100644 --- a/frontend/src/metabase/components/SelectList/SelectListItem.stories.tsx +++ b/frontend/src/metabase/components/SelectList/SelectListItem.stories.tsx @@ -1,4 +1,4 @@ -import type { ComponentStory } from "@storybook/react"; +import type { StoryFn } from "@storybook/react"; import { useState } from "react"; import SelectList from "./SelectList"; @@ -10,7 +10,7 @@ export default { const items = ["alert", "all", "archive", "dyno", "history"]; -const Template: ComponentStory = args => { +const Template: StoryFn = args => { const [value, setValue] = useState("dyno"); return ( @@ -30,9 +30,11 @@ const Template: ComponentStory = args => { ); }; -export const Default = Template.bind({}); +export const Default = { + render: Template, -Default.args = { - items: items, - rightIcon: "check", + args: { + items: items, + rightIcon: "check", + }, }; diff --git a/frontend/src/metabase/components/TextWidget/TextWidget.stories.tsx b/frontend/src/metabase/components/TextWidget/TextWidget.stories.tsx index 615b7fe16c279..30e07da6a7465 100644 --- a/frontend/src/metabase/components/TextWidget/TextWidget.stories.tsx +++ b/frontend/src/metabase/components/TextWidget/TextWidget.stories.tsx @@ -1,14 +1,14 @@ -import { useArgs } from "@storybook/addons"; -import type { ComponentStory } from "@storybook/react"; +import { useArgs } from "@storybook/preview-api"; +import type { StoryFn } from "@storybook/react"; -import { TextWidget } from "./TextWidget"; +import { TextWidget, type TextWidgetProps } from "./TextWidget"; export default { title: "Parameters/TextWidget", component: TextWidget, }; -const Template: ComponentStory = args => { +const Template: StoryFn = args => { const [{ value }, updateArgs] = useArgs(); const setValue = (value: string | number | null) => { @@ -18,18 +18,27 @@ const Template: ComponentStory = args => { return ; }; -export const Default = Template.bind({}); -Default.args = { - value: "", +export const Default = { + render: Template, + + args: { + value: "", + }, }; -export const InitialValue = Template.bind({}); -InitialValue.args = { - value: "Toucan McBird", +export const InitialValue = { + render: Template, + + args: { + value: "Toucan McBird", + }, }; -export const Placeholder = Template.bind({}); -Placeholder.args = { - value: "", - placeholder: "What's your wish?", +export const Placeholder = { + render: Template, + + args: { + value: "", + placeholder: "What's your wish?", + }, }; diff --git a/frontend/src/metabase/components/TextWidget/TextWidget.tsx b/frontend/src/metabase/components/TextWidget/TextWidget.tsx index 51a71ccaa9277..17f71f3e62242 100644 --- a/frontend/src/metabase/components/TextWidget/TextWidget.tsx +++ b/frontend/src/metabase/components/TextWidget/TextWidget.tsx @@ -4,7 +4,7 @@ import { t } from "ttag"; import { forceRedraw } from "metabase/lib/dom"; -type Props = { +export type TextWidgetProps = { value: string | number; setValue: (v: string | number | null) => void; className?: string; @@ -20,14 +20,14 @@ type State = { isFocused: boolean; }; -export class TextWidget extends Component { +export class TextWidget extends Component { static defaultProps = { isEditing: false, commitImmediately: false, disabled: false, }; - constructor(props: Props) { + constructor(props: TextWidgetProps) { super(props); this.state = { @@ -40,7 +40,7 @@ export class TextWidget extends Component { this.UNSAFE_componentWillReceiveProps(this.props); } - UNSAFE_componentWillReceiveProps(nextProps: Props) { + UNSAFE_componentWillReceiveProps(nextProps: TextWidgetProps) { if (nextProps.value !== this.props.value) { this.setState({ value: nextProps.value }, () => { // HACK: Address Safari rendering bug which causes https://github.com/metabase/metabase/issues/5335 diff --git a/frontend/src/metabase/components/Toaster/Toaster.stories.tsx b/frontend/src/metabase/components/Toaster/Toaster.stories.tsx index 185d9f0996b2e..f2e25a9435e10 100644 --- a/frontend/src/metabase/components/Toaster/Toaster.stories.tsx +++ b/frontend/src/metabase/components/Toaster/Toaster.stories.tsx @@ -1,24 +1,28 @@ -import type { ComponentStory } from "@storybook/react"; +import type { StoryFn } from "@storybook/react"; -import Toaster from "./Toaster"; +import Toaster, { type ToasterProps } from "./Toaster"; export default { title: "Dashboard/Toaster", component: Toaster, }; -const Template: ComponentStory = args => { +const Template: StoryFn = args => { return ; }; -export const Default = Template.bind({}); -Default.args = { - message: "Would you like to be notified when this dashboard is done loading?", - isShown: true, - onConfirm: () => { - alert("Confirmed"); - }, - onDismiss: () => { - alert("Dismissed"); +export const Default = { + render: Template, + + args: { + message: + "Would you like to be notified when this dashboard is done loading?", + isShown: true, + onConfirm: () => { + alert("Confirmed"); + }, + onDismiss: () => { + alert("Dismissed"); + }, }, }; diff --git a/frontend/src/metabase/components/TokenFieldItem/TokenFieldItem.stories.tsx b/frontend/src/metabase/components/TokenFieldItem/TokenFieldItem.stories.tsx index a142191978c70..d629ef9d466c1 100644 --- a/frontend/src/metabase/components/TokenFieldItem/TokenFieldItem.stories.tsx +++ b/frontend/src/metabase/components/TokenFieldItem/TokenFieldItem.stories.tsx @@ -1,5 +1,6 @@ -import type { ComponentStory } from "@storybook/react"; +import type { StoryFn } from "@storybook/react"; import cx from "classnames"; +import type { ComponentProps } from "react"; import CS from "metabase/css/core/index.css"; import { Icon } from "metabase/ui"; @@ -17,7 +18,7 @@ const Wrapper = ({ children }: { children: JSX.Element | JSX.Element[] }) => (
); -const Template: ComponentStory = args => { +const Template: StoryFn> = args => { return ( @@ -25,7 +26,7 @@ const Template: ComponentStory = args => { ); }; -const ManyTemplate: ComponentStory = args => { +const ManyTemplate: StoryFn> = args => { return ( {`${args.children} 1`} @@ -37,7 +38,7 @@ const ManyTemplate: ComponentStory = args => { ); }; -const AddonTemplate: ComponentStory = args => { +const AddonTemplate: StoryFn> = args => { return ( @@ -54,21 +55,29 @@ const AddonTemplate: ComponentStory = args => { ); }; -export const Default = Template.bind({}); -export const Many = ManyTemplate.bind({}); -export const WithAddon = AddonTemplate.bind({}); +export const Default = { + render: Template, -Default.args = { - isValid: true, - children: "Token Item Value", + args: { + isValid: true, + children: "Token Item Value", + }, }; -Many.args = { - isValid: true, - children: "Token Item Value", +export const Many = { + render: ManyTemplate, + + args: { + isValid: true, + children: "Token Item Value", + }, }; -WithAddon.args = { - isValid: true, - children: "Token Item Value", +export const WithAddon = { + render: AddonTemplate, + + args: { + isValid: true, + children: "Token Item Value", + }, }; diff --git a/frontend/src/metabase/components/UserAvatar/UserAvartar.stories.tsx b/frontend/src/metabase/components/UserAvatar/UserAvartar.stories.tsx index dc775bda6de5a..f126f0e0122ea 100644 --- a/frontend/src/metabase/components/UserAvatar/UserAvartar.stories.tsx +++ b/frontend/src/metabase/components/UserAvatar/UserAvartar.stories.tsx @@ -1,5 +1,3 @@ -import type { ComponentStory } from "@storybook/react"; - import UserAvatar from "./UserAvatar"; export default { @@ -7,46 +5,46 @@ export default { component: UserAvatar, }; -const Template: ComponentStory = args => ( - -); - -export const Default = Template.bind({}); -Default.args = { - user: { - first_name: "Testy", - last_name: "Tableton", - email: "user@metabase.test", - common_name: "Testy Tableton", +export const Default = { + args: { + user: { + first_name: "Testy", + last_name: "Tableton", + email: "user@metabase.test", + common_name: "Testy Tableton", + }, }, }; -export const SingleName = Template.bind({}); -SingleName.args = { - user: { - first_name: "Testy", - last_name: null, - email: "user@metabase.test", - common_name: "Testy", +export const SingleName = { + args: { + user: { + first_name: "Testy", + last_name: null, + email: "user@metabase.test", + common_name: "Testy", + }, }, }; -export const OnlyEmail = Template.bind({}); -OnlyEmail.args = { - user: { - first_name: null, - last_name: null, - email: "user@metabase.test", - common_name: "user@metabase.test", +export const OnlyEmail = { + args: { + user: { + first_name: null, + last_name: null, + email: "user@metabase.test", + common_name: "user@metabase.test", + }, }, }; -export const ShortEmail = Template.bind({}); -ShortEmail.args = { - user: { - first_name: null, - last_name: null, - email: "u@metabase.test", - common_name: "u@metabase.test", +export const ShortEmail = { + args: { + user: { + first_name: null, + last_name: null, + email: "u@metabase.test", + common_name: "u@metabase.test", + }, }, }; diff --git a/frontend/src/metabase/components/YearPicker/YearPicker.stories.tsx b/frontend/src/metabase/components/YearPicker/YearPicker.stories.tsx index 27c34981a5dae..52ab023e939e1 100644 --- a/frontend/src/metabase/components/YearPicker/YearPicker.stories.tsx +++ b/frontend/src/metabase/components/YearPicker/YearPicker.stories.tsx @@ -1,5 +1,5 @@ -import { useArgs } from "@storybook/addons"; -import type { ComponentStory } from "@storybook/react"; +import { useArgs } from "@storybook/preview-api"; +import type { StoryFn } from "@storybook/react"; import YearPicker from "./YearPicker"; @@ -8,7 +8,7 @@ export default { component: YearPicker, }; -const Template: ComponentStory = args => { +const Template: StoryFn = args => { const [{ value }, updateArgs] = useArgs(); const handleChange = (year: number) => { @@ -18,7 +18,10 @@ const Template: ComponentStory = args => { return ; }; -export const Default = Template.bind({}); -Default.args = { - value: 2022, +export const Default = { + render: Template, + + args: { + value: 2022, + }, }; diff --git a/frontend/src/metabase/core/components/AccordionList/AccordionList.stories.js b/frontend/src/metabase/core/components/AccordionList/AccordionList.stories.tsx similarity index 65% rename from frontend/src/metabase/core/components/AccordionList/AccordionList.stories.js rename to frontend/src/metabase/core/components/AccordionList/AccordionList.stories.tsx index e2af0a3360004..dc5f342a1baee 100644 --- a/frontend/src/metabase/core/components/AccordionList/AccordionList.stories.js +++ b/frontend/src/metabase/core/components/AccordionList/AccordionList.stories.tsx @@ -16,12 +16,8 @@ export default { component: AccordionList, }; -const Template = args => { - return ; -}; - -export const Default = Template.bind({}); - -Default.args = { - sections: SECTIONS, +export const Default = { + args: { + sections: SECTIONS, + }, }; diff --git a/frontend/src/metabase/core/components/Alert/Alert.stories.tsx b/frontend/src/metabase/core/components/Alert/Alert.stories.tsx index f24b01906bdce..3cbf1324cdce1 100644 --- a/frontend/src/metabase/core/components/Alert/Alert.stories.tsx +++ b/frontend/src/metabase/core/components/Alert/Alert.stories.tsx @@ -1,32 +1,41 @@ -import type { ComponentStory } from "@storybook/react"; +import type { StoryFn } from "@storybook/react"; -import Alert from "./Alert"; +import Alert, { type AlertProps } from "./Alert"; export default { title: "Core/Alert", component: Alert, }; -const Template: ComponentStory = args => { +const Template: StoryFn = args => { return ; }; -export const Default = Template.bind({}); -Default.args = { - children: "Info alert", - icon: "info", +export const Default = { + render: Template, + + args: { + children: "Info alert", + icon: "info", + }, }; -export const Warning = Template.bind({}); -Warning.args = { - children: "Warning alert", - variant: "warning", - icon: "warning", +export const Warning = { + render: Template, + + args: { + children: "Warning alert", + variant: "warning", + icon: "warning", + }, }; -export const Error = Template.bind({}); -Error.args = { - children: "Error alert", - variant: "error", - icon: "warning", +export const Error = { + render: Template, + + args: { + children: "Error alert", + variant: "error", + icon: "warning", + }, }; diff --git a/frontend/src/metabase/core/components/Alert/Alert.tsx b/frontend/src/metabase/core/components/Alert/Alert.tsx index d19f2c25eac0c..b521c34d659de 100644 --- a/frontend/src/metabase/core/components/Alert/Alert.tsx +++ b/frontend/src/metabase/core/components/Alert/Alert.tsx @@ -6,7 +6,7 @@ import { AlertIcon, AlertRoot } from "./Alert.styled"; export type AlertVariant = "info" | "warning" | "error"; -interface AlertProps { +export interface AlertProps { children: ReactNode; icon?: IconName; hasBorder?: boolean; diff --git a/frontend/src/metabase/core/components/AutocompleteInput/AutocompleteInput.stories.tsx b/frontend/src/metabase/core/components/AutocompleteInput/AutocompleteInput.stories.tsx index 93d7e8b311f8d..034278d5ecf81 100644 --- a/frontend/src/metabase/core/components/AutocompleteInput/AutocompleteInput.stories.tsx +++ b/frontend/src/metabase/core/components/AutocompleteInput/AutocompleteInput.stories.tsx @@ -1,4 +1,4 @@ -import type { ComponentStory } from "@storybook/react"; +import type { StoryFn } from "@storybook/react"; import { useState } from "react"; import AutocompleteInput from "./AutocompleteInput"; @@ -8,7 +8,7 @@ export default { component: AutocompleteInput, }; -const Template: ComponentStory = args => { +const Template: StoryFn = args => { const [value, setValue] = useState(""); return ( = args => { ); }; -export const Default = Template.bind({}); +export const Default = { + render: Template, +}; + +export const CustomFilter = { + render: Template, -export const CustomFilter = Template.bind({}); -CustomFilter.args = { - filterOptions: (value: string | undefined, options: string[]) => { - if (!value) { - return []; - } else { - return options.filter(o => o.includes(value[0])); - } + args: { + filterOptions: (value: string | undefined, options: string[]) => { + if (!value) { + return []; + } else { + return options.filter(o => o.includes(value[0])); + } + }, }, }; -const CustomOptionClickTemplate: ComponentStory< - typeof AutocompleteInput -> = args => { +const CustomOptionClickTemplate: StoryFn = args => { const [value, setValue] = useState(""); const handleOptionSelect = (option: string) => { @@ -72,4 +75,7 @@ const CustomOptionClickTemplate: ComponentStory< /> ); }; -export const CustomOptionClick = CustomOptionClickTemplate.bind({}); + +export const CustomOptionClick = { + render: CustomOptionClickTemplate, +}; diff --git a/frontend/src/metabase/core/components/BookmarkToggle/BookmarkToggle.stories.tsx b/frontend/src/metabase/core/components/BookmarkToggle/BookmarkToggle.stories.tsx index a630afd9e2a40..ba6d6007fd358 100644 --- a/frontend/src/metabase/core/components/BookmarkToggle/BookmarkToggle.stories.tsx +++ b/frontend/src/metabase/core/components/BookmarkToggle/BookmarkToggle.stories.tsx @@ -1,5 +1,5 @@ -import { useArgs } from "@storybook/addons"; -import type { ComponentStory } from "@storybook/react"; +import { useArgs } from "@storybook/preview-api"; +import type { StoryFn } from "@storybook/react"; import BookmarkToggle from "./BookmarkToggle"; @@ -8,7 +8,7 @@ export default { component: BookmarkToggle, }; -const Template: ComponentStory = args => { +const Template: StoryFn = args => { const [{ isBookmarked }, updateArgs] = useArgs(); const handleCreateBookmark = () => updateArgs({ isBookmarked: true }); const handleDeleteBookmark = () => updateArgs({ isBookmarked: false }); @@ -23,7 +23,10 @@ const Template: ComponentStory = args => { ); }; -export const Default = Template.bind({}); -Default.args = { - isBookmarked: false, +export const Default = { + render: Template, + + args: { + isBookmarked: false, + }, }; diff --git a/frontend/src/metabase/core/components/Button/Button.stories.tsx b/frontend/src/metabase/core/components/Button/Button.stories.tsx index 781c47237f001..5037f2837761b 100644 --- a/frontend/src/metabase/core/components/Button/Button.stories.tsx +++ b/frontend/src/metabase/core/components/Button/Button.stories.tsx @@ -1,34 +1,46 @@ -import type { ComponentStory } from "@storybook/react"; +import type { StoryFn } from "@storybook/react"; -import Button from "./Button"; +import Button, { type ButtonProps } from "./Button"; export default { title: "Core/Button", component: Button, }; -const Template: ComponentStory = args => { +const Template: StoryFn = args => { return @@ -19,4 +19,6 @@ const Template: ComponentStory = args => { ); }; -export const Default = Template.bind({}); +export const Default = { + render: Template, +}; diff --git a/frontend/src/metabase/core/components/CheckBox/CheckBox.stories.tsx b/frontend/src/metabase/core/components/CheckBox/CheckBox.stories.tsx index 8b7565f882a2d..3009f29b54204 100644 --- a/frontend/src/metabase/core/components/CheckBox/CheckBox.stories.tsx +++ b/frontend/src/metabase/core/components/CheckBox/CheckBox.stories.tsx @@ -1,5 +1,5 @@ -import { useArgs } from "@storybook/addons"; -import type { ComponentStory } from "@storybook/react"; +import { useArgs } from "@storybook/preview-api"; +import type { StoryFn } from "@storybook/react"; import type { ChangeEvent } from "react"; import CheckBox from "./CheckBox"; @@ -9,7 +9,7 @@ export default { component: CheckBox, }; -const Template: ComponentStory = args => { +const Template: StoryFn = args => { const [{ checked }, updateArgs] = useArgs(); const handleChange = (event: ChangeEvent) => { @@ -19,19 +19,28 @@ const Template: ComponentStory = args => { return ; }; -export const Default = Template.bind({}); -Default.args = { - checked: false, +export const Default = { + render: Template, + + args: { + checked: false, + }, }; -export const WithLabel = Template.bind({}); -WithLabel.args = { - checked: false, - label: "Label", +export const WithLabel = { + render: Template, + + args: { + checked: false, + label: "Label", + }, }; -export const WithCustomLabel = Template.bind({}); -WithCustomLabel.args = { - checked: false, - label: Label, +export const WithCustomLabel = { + render: Template, + + args: { + checked: false, + label: Label, + }, }; diff --git a/frontend/src/metabase/core/components/ColorInput/ColorInput.stories.tsx b/frontend/src/metabase/core/components/ColorInput/ColorInput.stories.tsx index bcba2aed52234..e8bf0f8c63ad6 100644 --- a/frontend/src/metabase/core/components/ColorInput/ColorInput.stories.tsx +++ b/frontend/src/metabase/core/components/ColorInput/ColorInput.stories.tsx @@ -1,5 +1,5 @@ -import { useArgs } from "@storybook/addons"; -import type { ComponentStory } from "@storybook/react"; +import { useArgs } from "@storybook/preview-api"; +import type { StoryFn } from "@storybook/react"; import ColorInput from "./ColorInput"; @@ -8,7 +8,7 @@ export default { component: ColorInput, }; -const Template: ComponentStory = args => { +const Template: StoryFn = args => { const [{ value }, updateArgs] = useArgs(); const handleChange = (value?: string) => { @@ -18,4 +18,6 @@ const Template: ComponentStory = args => { return ; }; -export const Default = Template.bind({}); +export const Default = { + render: Template, +}; diff --git a/frontend/src/metabase/core/components/ColorPicker/ColorPicker.stories.tsx b/frontend/src/metabase/core/components/ColorPicker/ColorPicker.stories.tsx index ed9c9fe17fd96..b563f8ca8ae2a 100644 --- a/frontend/src/metabase/core/components/ColorPicker/ColorPicker.stories.tsx +++ b/frontend/src/metabase/core/components/ColorPicker/ColorPicker.stories.tsx @@ -1,5 +1,5 @@ -import { useArgs } from "@storybook/addons"; -import type { ComponentStory } from "@storybook/react"; +import { useArgs } from "@storybook/preview-api"; +import type { StoryFn } from "@storybook/react"; import { color } from "metabase/lib/colors"; @@ -10,7 +10,7 @@ export default { component: ColorPicker, }; -const Template: ComponentStory = args => { +const Template: StoryFn = args => { const [{ value }, updateArgs] = useArgs(); const handleChange = (value?: string) => { @@ -20,8 +20,11 @@ const Template: ComponentStory = args => { return ; }; -export const Default = Template.bind({}); -Default.args = { - value: color("brand"), - placeholder: color("brand"), +export const Default = { + render: Template, + + args: { + value: color("brand"), + placeholder: color("brand"), + }, }; diff --git a/frontend/src/metabase/core/components/ColorPill/ColorPill.stories.tsx b/frontend/src/metabase/core/components/ColorPill/ColorPill.stories.tsx index 2977679b22d83..e10a7c405c316 100644 --- a/frontend/src/metabase/core/components/ColorPill/ColorPill.stories.tsx +++ b/frontend/src/metabase/core/components/ColorPill/ColorPill.stories.tsx @@ -1,25 +1,31 @@ -import type { ComponentStory } from "@storybook/react"; +import type { StoryFn } from "@storybook/react"; import { color } from "metabase/lib/colors"; -import ColorPill from "./ColorPill"; +import ColorPill, { type ColorPillProps } from "./ColorPill"; export default { title: "Core/ColorPill", component: ColorPill, }; -const Template: ComponentStory = args => { +const Template: StoryFn = args => { return ; }; -export const Default = Template.bind({}); -Default.args = { - color: color("brand"), +export const Default = { + render: Template, + + args: { + color: color("brand"), + }, }; -export const Auto = Template.bind({}); -Auto.args = { - color: color("brand"), - isAuto: true, +export const Auto = { + render: Template, + + args: { + color: color("brand"), + isAuto: true, + }, }; diff --git a/frontend/src/metabase/core/components/ColorRange/ColorRange.stories.tsx b/frontend/src/metabase/core/components/ColorRange/ColorRange.stories.tsx index ed1b097cb3584..c2c4b1c548957 100644 --- a/frontend/src/metabase/core/components/ColorRange/ColorRange.stories.tsx +++ b/frontend/src/metabase/core/components/ColorRange/ColorRange.stories.tsx @@ -1,29 +1,38 @@ -import type { ComponentStory } from "@storybook/react"; +import type { StoryFn } from "@storybook/react"; import { color } from "metabase/lib/colors"; -import ColorRange from "./ColorRange"; +import ColorRange, { type ColorRangeProps } from "./ColorRange"; export default { title: "Core/ColorRange", component: ColorRange, }; -const Template: ComponentStory = args => { +const Template: StoryFn = args => { return ; }; -export const Default = Template.bind({}); -Default.args = { - colors: [color("white"), color("brand")], +export const Default = { + render: Template, + + args: { + colors: [color("white"), color("brand")], + }, }; -export const Inverted = Template.bind({}); -Inverted.args = { - colors: [color("brand"), color("white")], +export const Inverted = { + render: Template, + + args: { + colors: [color("brand"), color("white")], + }, }; -export const ThreeColors = Template.bind({}); -ThreeColors.args = { - colors: [color("error"), color("white"), color("success")], +export const ThreeColors = { + render: Template, + + args: { + colors: [color("error"), color("white"), color("success")], + }, }; diff --git a/frontend/src/metabase/core/components/ColorRangeSelector/ColorRangeSelector.stories.tsx b/frontend/src/metabase/core/components/ColorRangeSelector/ColorRangeSelector.stories.tsx index bd5b4085409ef..5faa1b52d26b6 100644 --- a/frontend/src/metabase/core/components/ColorRangeSelector/ColorRangeSelector.stories.tsx +++ b/frontend/src/metabase/core/components/ColorRangeSelector/ColorRangeSelector.stories.tsx @@ -1,16 +1,18 @@ -import { useArgs } from "@storybook/addons"; -import type { ComponentStory } from "@storybook/react"; +import { useArgs } from "@storybook/preview-api"; +import type { StoryFn } from "@storybook/react"; import { color } from "metabase/lib/colors"; -import ColorRangeSelector from "./ColorRangeSelector"; +import ColorRangeSelector, { + type ColorRangeSelectorProps, +} from "./ColorRangeSelector"; export default { title: "Core/ColorRangeSelector", component: ColorRangeSelector, }; -const Template: ComponentStory = args => { +const Template: StoryFn = args => { const [{ value }, updateArgs] = useArgs(); const handleChange = (value: string[]) => { @@ -20,29 +22,42 @@ const Template: ComponentStory = args => { return ; }; -export const Default = Template.bind({}); -Default.args = { - value: [color("white"), color("brand")], - colors: [color("brand"), color("summarize"), color("filter")], +export const Default = { + render: Template, + + args: { + value: [color("white"), color("brand")], + colors: [color("brand"), color("summarize"), color("filter")], + }, }; -export const WithColorRanges = Template.bind({}); -WithColorRanges.args = { - value: [color("white"), color("brand")], - colors: [color("brand"), color("summarize"), color("filter")], - colorRanges: [ - [color("error"), color("white"), color("success")], - [color("error"), color("warning"), color("success")], - ], +export const WithColorRanges = { + render: Template, + + args: { + value: [color("white"), color("brand")], + colors: [color("brand"), color("summarize"), color("filter")], + colorRanges: [ + [color("error"), color("white"), color("success")], + [color("error"), color("warning"), color("success")], + ], + }, }; -export const WithColorMapping = Template.bind({}); -WithColorMapping.args = { - value: [color("white"), color("brand")], - colors: [color("brand"), color("summarize"), color("filter")], - colorMapping: { - [color("brand")]: [color("brand"), color("white"), color("brand")], - [color("summarize")]: [color("summarize"), color("white"), color("error")], - [color("filter")]: [color("filter"), color("white"), color("filter")], +export const WithColorMapping = { + render: Template, + + args: { + value: [color("white"), color("brand")], + colors: [color("brand"), color("summarize"), color("filter")], + colorMapping: { + [color("brand")]: [color("brand"), color("white"), color("brand")], + [color("summarize")]: [ + color("summarize"), + color("white"), + color("error"), + ], + [color("filter")]: [color("filter"), color("white"), color("filter")], + }, }, }; diff --git a/frontend/src/metabase/core/components/ColorSelector/ColorSelector.stories.tsx b/frontend/src/metabase/core/components/ColorSelector/ColorSelector.stories.tsx index 47368eb4f8323..909ce20462c52 100644 --- a/frontend/src/metabase/core/components/ColorSelector/ColorSelector.stories.tsx +++ b/frontend/src/metabase/core/components/ColorSelector/ColorSelector.stories.tsx @@ -1,16 +1,16 @@ -import { useArgs } from "@storybook/addons"; -import type { ComponentStory } from "@storybook/react"; +import { useArgs } from "@storybook/preview-api"; +import type { StoryFn } from "@storybook/react"; import { color } from "metabase/lib/colors"; -import ColorSelector from "./ColorSelector"; +import ColorSelector, { type ColorSelectorProps } from "./ColorSelector"; export default { title: "Core/ColorSelector", component: ColorSelector, }; -const Template: ComponentStory = args => { +const Template: StoryFn = args => { const [{ value }, updateArgs] = useArgs(); const handleChange = (value: string) => { @@ -20,8 +20,11 @@ const Template: ComponentStory = args => { return ; }; -export const Default = Template.bind({}); -Default.args = { - value: color("brand"), - colors: [color("brand"), color("summarize"), color("filter")], +export const Default = { + render: Template, + + args: { + value: color("brand"), + colors: [color("brand"), color("summarize"), color("filter")], + }, }; diff --git a/frontend/src/metabase/core/components/DateInput/DateInput.stories.tsx b/frontend/src/metabase/core/components/DateInput/DateInput.stories.tsx index 1ab46b2a47c42..3dcda8c2f8c50 100644 --- a/frontend/src/metabase/core/components/DateInput/DateInput.stories.tsx +++ b/frontend/src/metabase/core/components/DateInput/DateInput.stories.tsx @@ -1,4 +1,4 @@ -import type { ComponentStory } from "@storybook/react"; +import type { StoryFn } from "@storybook/react"; import type { Moment } from "moment-timezone"; import { useState } from "react"; @@ -9,14 +9,19 @@ export default { component: DateInput, }; -const Template: ComponentStory = args => { +const Template: StoryFn = args => { const [value, setValue] = useState(); return ; }; -export const Default = Template.bind({}); +export const Default = { + render: Template, +}; + +export const WithTime = { + render: Template, -export const WithTime = Template.bind({}); -WithTime.args = { - hasTime: true, + args: { + hasTime: true, + }, }; diff --git a/frontend/src/metabase/core/components/DateSelector/DateSelector.stories.tsx b/frontend/src/metabase/core/components/DateSelector/DateSelector.stories.tsx index f235584e07bc6..697e64f5b8556 100644 --- a/frontend/src/metabase/core/components/DateSelector/DateSelector.stories.tsx +++ b/frontend/src/metabase/core/components/DateSelector/DateSelector.stories.tsx @@ -1,23 +1,28 @@ -import type { ComponentStory } from "@storybook/react"; +import type { StoryFn } from "@storybook/react"; import moment from "moment-timezone"; import { useState } from "react"; -import DateSelector from "./DateSelector"; +import DateSelector, { type DateSelectorProps } from "./DateSelector"; export default { title: "Core/DateSelector", component: DateSelector, }; -const Template: ComponentStory = args => { +const Template: StoryFn = args => { const [value, setValue] = useState(args.value); return ; }; -export const Default = Template.bind({}); +export const Default = { + render: Template, +}; + +export const WithTime = { + render: Template, -export const WithTime = Template.bind({}); -WithTime.args = { - value: moment("2015-01-01"), - hasTime: true, + args: { + value: moment("2015-01-01"), + hasTime: true, + }, }; diff --git a/frontend/src/metabase/core/components/DateWidget/DateWidget.stories.tsx b/frontend/src/metabase/core/components/DateWidget/DateWidget.stories.tsx index 9701bff1aca5e..7e7a92bb5966b 100644 --- a/frontend/src/metabase/core/components/DateWidget/DateWidget.stories.tsx +++ b/frontend/src/metabase/core/components/DateWidget/DateWidget.stories.tsx @@ -1,4 +1,4 @@ -import type { ComponentStory } from "@storybook/react"; +import type { StoryFn } from "@storybook/react"; import type { Moment } from "moment-timezone"; import { useState } from "react"; @@ -9,14 +9,19 @@ export default { component: DateWidget, }; -const Template: ComponentStory = args => { +const Template: StoryFn = args => { const [value, setValue] = useState(); return ; }; -export const Default = Template.bind({}); +export const Default = { + render: Template, +}; + +export const WithTime = { + render: Template, -export const WithTime = Template.bind({}); -WithTime.args = { - hasTime: true, + args: { + hasTime: true, + }, }; diff --git a/frontend/src/metabase/core/components/EditableText/EditableText.stories.tsx b/frontend/src/metabase/core/components/EditableText/EditableText.stories.tsx index 7accb1a8f0d14..d9695da17564d 100644 --- a/frontend/src/metabase/core/components/EditableText/EditableText.stories.tsx +++ b/frontend/src/metabase/core/components/EditableText/EditableText.stories.tsx @@ -1,44 +1,56 @@ -import type { ComponentStory } from "@storybook/react"; +import type { StoryFn } from "@storybook/react"; -import EditableText from "./EditableText"; +import EditableText, { type EditableTextProps } from "./EditableText"; export default { title: "Core/EditableText", component: EditableText, }; -const Template: ComponentStory = args => { +const Template: StoryFn = args => { return ; }; -export const Default = Template.bind({}); -Default.args = { - initialValue: "Question", - placeholder: "Enter title", +export const Default = { + render: Template, + + args: { + initialValue: "Question", + placeholder: "Enter title", + }, }; -export const Multiline = Template.bind({}); -Multiline.args = { - initialValue: "Question", - placeholder: "Enter title", - isMultiline: true, +export const Multiline = { + render: Template, + + args: { + initialValue: "Question", + placeholder: "Enter title", + isMultiline: true, + }, }; -export const WithMaxWidth = Template.bind({}); -WithMaxWidth.args = { - initialValue: "Question", - placeholder: "Enter title", - style: { maxWidth: 500 }, +export const WithMaxWidth = { + render: Template, + + args: { + initialValue: "Question", + placeholder: "Enter title", + style: { maxWidth: 500 }, + }, }; -export const WithMarkdown = Template.bind({}); -WithMarkdown.args = { - initialValue: `**bold** text +export const WithMarkdown = { + render: Template, + + args: { + initialValue: `**bold** text - *multiline* + *multiline* - and [link](https://metabase.com)`, - placeholder: "Enter description", - isMultiline: true, - isMarkdown: true, + and [link](https://metabase.com)`, + placeholder: "Enter description", + isMultiline: true, + isMarkdown: true, + }, }; diff --git a/frontend/src/metabase/core/components/Ellipsified/Ellipsified.stories.tsx b/frontend/src/metabase/core/components/Ellipsified/Ellipsified.stories.tsx index 5d3f140691c7f..6455788de0497 100644 --- a/frontend/src/metabase/core/components/Ellipsified/Ellipsified.stories.tsx +++ b/frontend/src/metabase/core/components/Ellipsified/Ellipsified.stories.tsx @@ -1,4 +1,4 @@ -import type { ComponentStory } from "@storybook/react"; +import type { StoryFn } from "@storybook/react"; import { Ellipsified } from "./Ellipsified"; @@ -16,7 +16,7 @@ export default { component: Ellipsified, }; -const Template: ComponentStory = args => ( +const Template: StoryFn = args => (
    {testLabels.map((label: string) => (
  • @@ -26,8 +26,12 @@ const Template: ComponentStory = args => (
); -export const SingleLineEllipsify = Template.bind({}); -SingleLineEllipsify.args = { lines: 1 }; +export const SingleLineEllipsify = { + render: Template, + args: { lines: 1 }, +}; -export const MultiLineClamp = Template.bind({}); -MultiLineClamp.args = { lines: 8 }; +export const MultiLineClamp = { + render: Template, + args: { lines: 8 }, +}; diff --git a/frontend/src/metabase/core/components/ExternalLink/ExternalLink.stories.tsx b/frontend/src/metabase/core/components/ExternalLink/ExternalLink.stories.tsx index 5d1820255add0..73a9d106dc0f9 100644 --- a/frontend/src/metabase/core/components/ExternalLink/ExternalLink.stories.tsx +++ b/frontend/src/metabase/core/components/ExternalLink/ExternalLink.stories.tsx @@ -1,4 +1,4 @@ -import type { ComponentStory } from "@storybook/react"; +import type { StoryFn } from "@storybook/react"; import ExternalLink from "./ExternalLink"; @@ -7,12 +7,15 @@ export default { component: ExternalLink, }; -const Template: ComponentStory = args => { +const Template: StoryFn = args => { return ; }; -export const Default = Template.bind({}); -Default.args = { - href: "/", - children: "Link", +export const Default = { + render: Template, + + args: { + href: "/", + children: "Link", + }, }; diff --git a/frontend/src/metabase/core/components/FileInput/FileInput.stories.tsx b/frontend/src/metabase/core/components/FileInput/FileInput.stories.tsx index 0efcf1592b130..8cc4cd502f892 100644 --- a/frontend/src/metabase/core/components/FileInput/FileInput.stories.tsx +++ b/frontend/src/metabase/core/components/FileInput/FileInput.stories.tsx @@ -1,4 +1,4 @@ -import type { ComponentStory } from "@storybook/react"; +import type { StoryFn } from "@storybook/react"; import FileInput from "./FileInput"; @@ -7,11 +7,14 @@ export default { component: FileInput, }; -const Template: ComponentStory = args => { +const Template: StoryFn = args => { return ; }; -export const Default = Template.bind({}); -Default.args = { - name: "file", +export const Default = { + render: Template, + + args: { + name: "file", + }, }; diff --git a/frontend/src/metabase/core/components/FormCheckBox/FormCheckBox.stories.tsx b/frontend/src/metabase/core/components/FormCheckBox/FormCheckBox.stories.tsx index 1f02415f000d3..4da80c2e8480a 100644 --- a/frontend/src/metabase/core/components/FormCheckBox/FormCheckBox.stories.tsx +++ b/frontend/src/metabase/core/components/FormCheckBox/FormCheckBox.stories.tsx @@ -1,4 +1,4 @@ -import type { ComponentStory } from "@storybook/react"; +import type { StoryFn } from "@storybook/react"; import { Form, FormProvider } from "metabase/forms"; @@ -9,7 +9,7 @@ export default { component: FormCheckBox, }; -const Template: ComponentStory = args => { +const Template: StoryFn = args => { const initialValues = { value: false }; const handleSubmit = () => undefined; @@ -22,13 +22,19 @@ const Template: ComponentStory = args => { ); }; -export const Default = Template.bind({}); -Default.args = { - title: "Title", +export const Default = { + render: Template, + + args: { + title: "Title", + }, }; -export const WithDescription = Template.bind({}); -WithDescription.args = { - title: "Title", - description: "Description", +export const WithDescription = { + render: Template, + + args: { + title: "Title", + description: "Description", + }, }; diff --git a/frontend/src/metabase/core/components/FormDateInput/FormDateInput.stories.tsx b/frontend/src/metabase/core/components/FormDateInput/FormDateInput.stories.tsx index 77ae6c0a6944d..73d0167922d20 100644 --- a/frontend/src/metabase/core/components/FormDateInput/FormDateInput.stories.tsx +++ b/frontend/src/metabase/core/components/FormDateInput/FormDateInput.stories.tsx @@ -1,4 +1,4 @@ -import type { ComponentStory } from "@storybook/react"; +import type { StoryFn } from "@storybook/react"; import { Form, FormProvider } from "metabase/forms"; @@ -9,7 +9,7 @@ export default { component: FormDateInput, }; -const Template: ComponentStory = args => { +const Template: StoryFn = args => { const initialValues = { value: undefined }; const handleSubmit = () => undefined; @@ -22,13 +22,19 @@ const Template: ComponentStory = args => { ); }; -export const Default = Template.bind({}); -Default.args = { - title: "Title", +export const Default = { + render: Template, + + args: { + title: "Title", + }, }; -export const WithDescription = Template.bind({}); -WithDescription.args = { - title: "Title", - description: "Description", +export const WithDescription = { + render: Template, + + args: { + title: "Title", + description: "Description", + }, }; diff --git a/frontend/src/metabase/core/components/FormField/FormField.stories.tsx b/frontend/src/metabase/core/components/FormField/FormField.stories.tsx index d9ca6952e1a3c..fb2172d268a0d 100644 --- a/frontend/src/metabase/core/components/FormField/FormField.stories.tsx +++ b/frontend/src/metabase/core/components/FormField/FormField.stories.tsx @@ -1,5 +1,5 @@ -import { useArgs } from "@storybook/addons"; -import type { ComponentStory } from "@storybook/react"; +import { useArgs } from "@storybook/preview-api"; +import type { StoryFn } from "@storybook/react"; import type { ComponentProps } from "react"; import { cloneElement, isValidElement } from "react"; @@ -17,7 +17,7 @@ type inputProps = { onChange: (value: unknown) => void; }; -const Template: ComponentStory = ({ +const Template: StoryFn = ({ children, ...args }: ComponentProps) => { @@ -37,23 +37,32 @@ const Template: ComponentStory = ({ ); }; -export const ToggleStory = Template.bind({}); -ToggleStory.storyName = "Toggle"; -ToggleStory.args = { - children: , +export const ToggleStory = { + render: Template, + name: "Toggle", + + args: { + children: , + }, }; -export const ToggleWithTitle = Template.bind({}); -ToggleWithTitle.args = { - children: , - title: "Toggle this value?", - infoTooltip: "Info tooltip", +export const ToggleWithTitle = { + render: Template, + + args: { + children: , + title: "Toggle this value?", + infoTooltip: "Info tooltip", + }, }; -export const ToggleWithInlineTitle = Template.bind({}); -ToggleWithInlineTitle.args = { - children: , - title: "Toggle this value?", - orientation: "horizontal", - infoTooltip: "Info tooltip", +export const ToggleWithInlineTitle = { + render: Template, + + args: { + children: , + title: "Toggle this value?", + orientation: "horizontal", + infoTooltip: "Info tooltip", + }, }; diff --git a/frontend/src/metabase/core/components/FormFileInput/FormFileInput.stories.tsx b/frontend/src/metabase/core/components/FormFileInput/FormFileInput.stories.tsx index 61b7e7770360b..8b72d7ba9567b 100644 --- a/frontend/src/metabase/core/components/FormFileInput/FormFileInput.stories.tsx +++ b/frontend/src/metabase/core/components/FormFileInput/FormFileInput.stories.tsx @@ -1,4 +1,4 @@ -import type { ComponentStory } from "@storybook/react"; +import type { StoryFn } from "@storybook/react"; import { Form, FormProvider } from "metabase/forms"; @@ -9,7 +9,7 @@ export default { component: FormFileInput, }; -const Template: ComponentStory = args => { +const Template: StoryFn = args => { const initialValues = { value: undefined }; const handleSubmit = () => undefined; @@ -22,13 +22,19 @@ const Template: ComponentStory = args => { ); }; -export const Default = Template.bind({}); -Default.args = { - title: "Title", +export const Default = { + render: Template, + + args: { + title: "Title", + }, }; -export const WithDescription = Template.bind({}); -WithDescription.args = { - title: "Title", - description: "Description", +export const WithDescription = { + render: Template, + + args: { + title: "Title", + description: "Description", + }, }; diff --git a/frontend/src/metabase/core/components/FormInput/FormInput.stories.tsx b/frontend/src/metabase/core/components/FormInput/FormInput.stories.tsx index 49586aa96edd5..7f0b5defd3b42 100644 --- a/frontend/src/metabase/core/components/FormInput/FormInput.stories.tsx +++ b/frontend/src/metabase/core/components/FormInput/FormInput.stories.tsx @@ -1,4 +1,4 @@ -import type { ComponentStory } from "@storybook/react"; +import type { StoryFn } from "@storybook/react"; import { useState } from "react"; import { Form, FormProvider } from "metabase/forms"; @@ -30,7 +30,7 @@ export default { }, }; -const Template: ComponentStory = args => { +const Template: StoryFn = args => { const initialValues = { value: false }; const handleSubmit = () => undefined; @@ -43,21 +43,30 @@ const Template: ComponentStory = args => { ); }; -export const Default = Template.bind({}); -Default.args = { - title: "Title", +export const Default = { + render: Template, + + args: { + title: "Title", + }, }; -export const WithDescription = Template.bind({}); -WithDescription.args = { - title: "Title", - description: "Description", +export const WithDescription = { + render: Template, + + args: { + title: "Title", + description: "Description", + }, }; -export const WithTitleAndActions = Template.bind({}); -WithTitleAndActions.args = { - title: "Title", - description: "Description", - optional: true, - actions: "Default", +export const WithTitleAndActions = { + render: Template, + + args: { + title: "Title", + description: "Description", + optional: true, + actions: "Default", + }, }; diff --git a/frontend/src/metabase/core/components/FormNumericInput/FormNumericInput.stories.tsx b/frontend/src/metabase/core/components/FormNumericInput/FormNumericInput.stories.tsx index dfc32d4e02fb7..c99126d53cfc6 100644 --- a/frontend/src/metabase/core/components/FormNumericInput/FormNumericInput.stories.tsx +++ b/frontend/src/metabase/core/components/FormNumericInput/FormNumericInput.stories.tsx @@ -1,4 +1,4 @@ -import type { ComponentStory } from "@storybook/react"; +import type { StoryFn } from "@storybook/react"; import { Form, FormProvider } from "metabase/forms"; @@ -9,7 +9,7 @@ export default { component: FormNumericInput, }; -const Template: ComponentStory = args => { +const Template: StoryFn = args => { const initialValues = { value: undefined }; const handleSubmit = () => undefined; @@ -22,13 +22,19 @@ const Template: ComponentStory = args => { ); }; -export const Default = Template.bind({}); -Default.args = { - title: "Title", +export const Default = { + render: Template, + + args: { + title: "Title", + }, }; -export const WithDescription = Template.bind({}); -WithDescription.args = { - title: "Title", - description: "Description", +export const WithDescription = { + render: Template, + + args: { + title: "Title", + description: "Description", + }, }; diff --git a/frontend/src/metabase/core/components/FormRadio/FormRadio.stories.tsx b/frontend/src/metabase/core/components/FormRadio/FormRadio.stories.tsx index 0fbfb2bdb6bc8..5eac12865f3f0 100644 --- a/frontend/src/metabase/core/components/FormRadio/FormRadio.stories.tsx +++ b/frontend/src/metabase/core/components/FormRadio/FormRadio.stories.tsx @@ -1,4 +1,4 @@ -import type { ComponentStory } from "@storybook/react"; +import type { StoryFn } from "@storybook/react"; import { Form, FormProvider } from "metabase/forms"; @@ -15,7 +15,7 @@ export default { component: FormRadio, }; -const Template: ComponentStory = args => { +const Template: StoryFn = args => { const initialValues = { value: undefined }; const handleSubmit = () => undefined; @@ -28,13 +28,19 @@ const Template: ComponentStory = args => { ); }; -export const Default = Template.bind({}); -Default.args = { - title: "Title", +export const Default = { + render: Template, + + args: { + title: "Title", + }, }; -export const WithDescription = Template.bind({}); -WithDescription.args = { - title: "Title", - description: "Description", +export const WithDescription = { + render: Template, + + args: { + title: "Title", + description: "Description", + }, }; diff --git a/frontend/src/metabase/core/components/FormSelect/FormSelect.stories.tsx b/frontend/src/metabase/core/components/FormSelect/FormSelect.stories.tsx index c1f326248d7c2..2130ad8635473 100644 --- a/frontend/src/metabase/core/components/FormSelect/FormSelect.stories.tsx +++ b/frontend/src/metabase/core/components/FormSelect/FormSelect.stories.tsx @@ -1,4 +1,4 @@ -import type { ComponentStory } from "@storybook/react"; +import type { StoryFn } from "@storybook/react"; import { Form, FormProvider } from "metabase/forms"; @@ -15,7 +15,7 @@ export default { component: FormSelect, }; -const Template: ComponentStory = args => { +const Template: StoryFn = args => { const initialValues = { value: undefined }; const handleSubmit = () => undefined; @@ -28,15 +28,21 @@ const Template: ComponentStory = args => { ); }; -export const Default = Template.bind({}); -Default.args = { - title: "Title", - placeholder: "Use default", +export const Default = { + render: Template, + + args: { + title: "Title", + placeholder: "Use default", + }, }; -export const WithDescription = Template.bind({}); -WithDescription.args = { - title: "Title", - placeholder: "Use default", - description: "Description", +export const WithDescription = { + render: Template, + + args: { + title: "Title", + placeholder: "Use default", + description: "Description", + }, }; diff --git a/frontend/src/metabase/core/components/FormTextArea/FormTextArea.stories.tsx b/frontend/src/metabase/core/components/FormTextArea/FormTextArea.stories.tsx index 65fe4b49be76a..8cb7bff34d0e4 100644 --- a/frontend/src/metabase/core/components/FormTextArea/FormTextArea.stories.tsx +++ b/frontend/src/metabase/core/components/FormTextArea/FormTextArea.stories.tsx @@ -1,4 +1,4 @@ -import type { ComponentStory } from "@storybook/react"; +import type { StoryFn } from "@storybook/react"; import { Form, FormProvider } from "metabase/forms"; @@ -9,7 +9,7 @@ export default { component: FormTextArea, }; -const Template: ComponentStory = args => { +const Template: StoryFn = args => { const initialValues = { value: false }; const handleSubmit = () => undefined; @@ -22,13 +22,19 @@ const Template: ComponentStory = args => { ); }; -export const Default = Template.bind({}); -Default.args = { - title: "Title", +export const Default = { + render: Template, + + args: { + title: "Title", + }, }; -export const WithDescription = Template.bind({}); -WithDescription.args = { - title: "Title", - description: "Description", +export const WithDescription = { + render: Template, + + args: { + title: "Title", + description: "Description", + }, }; diff --git a/frontend/src/metabase/core/components/FormToggle/FormToggle.stories.tsx b/frontend/src/metabase/core/components/FormToggle/FormToggle.stories.tsx index ca0f7ef18a13c..c8007d2ea1c03 100644 --- a/frontend/src/metabase/core/components/FormToggle/FormToggle.stories.tsx +++ b/frontend/src/metabase/core/components/FormToggle/FormToggle.stories.tsx @@ -1,4 +1,4 @@ -import type { ComponentStory } from "@storybook/react"; +import type { StoryFn } from "@storybook/react"; import { Form, FormProvider } from "metabase/forms"; @@ -9,7 +9,7 @@ export default { component: FormToggle, }; -const Template: ComponentStory = args => { +const Template: StoryFn = args => { const initialValues = { value: false }; const handleSubmit = () => undefined; @@ -22,13 +22,19 @@ const Template: ComponentStory = args => { ); }; -export const Default = Template.bind({}); -Default.args = { - title: "Title", +export const Default = { + render: Template, + + args: { + title: "Title", + }, }; -export const WithDescription = Template.bind({}); -WithDescription.args = { - title: "Title", - description: "Description", +export const WithDescription = { + render: Template, + + args: { + title: "Title", + description: "Description", + }, }; diff --git a/frontend/src/metabase/core/components/Input/Input.stories.tsx b/frontend/src/metabase/core/components/Input/Input.stories.tsx index d5d4ada42bd63..7f610b4da1428 100644 --- a/frontend/src/metabase/core/components/Input/Input.stories.tsx +++ b/frontend/src/metabase/core/components/Input/Input.stories.tsx @@ -1,18 +1,18 @@ -import type { ComponentStory } from "@storybook/react"; +import type { StoryFn } from "@storybook/react"; import { useState } from "react"; -import Input from "./Input"; +import Input, { type InputProps } from "./Input"; export default { title: "Core/Input", component: Input, }; -const UncontrolledTemplate: ComponentStory = args => { +const UncontrolledTemplate: StoryFn = args => { return ; }; -const ControlledTemplate: ComponentStory = args => { +const ControlledTemplate: StoryFn = args => { const [value, setValue] = useState(""); return ( = args => { ); }; -export const Default = UncontrolledTemplate.bind({}); +export const Default = { + render: UncontrolledTemplate, +}; + +export const WithError = { + render: UncontrolledTemplate, -export const WithError = UncontrolledTemplate.bind({}); -WithError.args = { - error: true, + args: { + error: true, + }, }; -export const WithRightIcon = UncontrolledTemplate.bind({}); -WithRightIcon.args = { - rightIcon: "info", - rightIconTooltip: "Useful tips", +export const WithRightIcon = { + render: UncontrolledTemplate, + + args: { + rightIcon: "info", + rightIconTooltip: "Useful tips", + }, }; -export const Controlled = ControlledTemplate.bind({}); +export const Controlled = { + render: ControlledTemplate, +}; diff --git a/frontend/src/metabase/core/components/Link/Link.stories.tsx b/frontend/src/metabase/core/components/Link/Link.stories.tsx index d50b7ea43fe39..21e2ace4561f0 100644 --- a/frontend/src/metabase/core/components/Link/Link.stories.tsx +++ b/frontend/src/metabase/core/components/Link/Link.stories.tsx @@ -1,6 +1,6 @@ -import type { ComponentStory } from "@storybook/react"; +import type { StoryFn } from "@storybook/react"; -import Link from "./"; +import Link, { type LinkProps } from "./"; export default { title: "Core/Link", @@ -13,7 +13,7 @@ const sampleStyle = { gap: "2rem", }; -const Template: ComponentStory = args => { +const Template: StoryFn = args => { return (
Click Me @@ -21,9 +21,11 @@ const Template: ComponentStory = args => { ); }; -export const Default = Template.bind({}); +export const Default = { + render: Template, -Default.args = { - to: "/foo/bar", - variant: "default", + args: { + to: "/foo/bar", + variant: "default", + }, }; diff --git a/frontend/src/metabase/core/components/Markdown/Markdown.stories.tsx b/frontend/src/metabase/core/components/Markdown/Markdown.stories.tsx index ec922015c8640..a34bb2867c55c 100644 --- a/frontend/src/metabase/core/components/Markdown/Markdown.stories.tsx +++ b/frontend/src/metabase/core/components/Markdown/Markdown.stories.tsx @@ -1,22 +1,25 @@ -import type { ComponentStory } from "@storybook/react"; +import type { StoryFn } from "@storybook/react"; -import Markdown from "./Markdown"; +import Markdown, { type MarkdownProps } from "./Markdown"; export default { title: "Core/Markdown", component: Markdown, }; -const Template: ComponentStory = args => { +const Template: StoryFn = args => { return ; }; -export const Default = Template.bind({}); -Default.args = { - children: ` -Our first email blast to the mailing list not directly linked to the release -of a new version. We wanted to see if this would effect visits to landing pages -for the features in 0.41. +export const Default = { + render: Template, -Here’s a [doc](https://metabase.test) with the findings.`, + args: { + children: ` + Our first email blast to the mailing list not directly linked to the release + of a new version. We wanted to see if this would effect visits to landing pages + for the features in 0.41. + + Here’s a [doc](https://metabase.test) with the findings.`, + }, }; diff --git a/frontend/src/metabase/core/components/MarkdownPreview/MarkdownPreview.stories.tsx b/frontend/src/metabase/core/components/MarkdownPreview/MarkdownPreview.stories.tsx index cd71a8526e91b..7bbdb66cb10a0 100644 --- a/frontend/src/metabase/core/components/MarkdownPreview/MarkdownPreview.stories.tsx +++ b/frontend/src/metabase/core/components/MarkdownPreview/MarkdownPreview.stories.tsx @@ -1,14 +1,14 @@ import styled from "@emotion/styled"; -import type { ComponentStory } from "@storybook/react"; +import type { StoryFn } from "@storybook/react"; -import { MarkdownPreview } from "./MarkdownPreview"; +import { MarkdownPreview, type MarkdownPreviewProps } from "./MarkdownPreview"; export default { title: "Core/MarkdownPreview", component: MarkdownPreview, }; -const Template: ComponentStory = args => { +const Template: StoryFn = args => { return ( @@ -20,22 +20,28 @@ const Container = styled.div` width: 200px; `; -export const PlainText = Template.bind({}); -PlainText.args = { - children: `Our first email blast to the mailing list not directly linked to the release of a new version. We wanted to see if this would effect visits to landing pages for the features in 0.41.`, +export const PlainText = { + render: Template, + + args: { + children: `Our first email blast to the mailing list not directly linked to the release of a new version. We wanted to see if this would effect visits to landing pages for the features in 0.41.`, + }, }; -export const Markdown = Template.bind({}); -Markdown.args = { - children: `![Metabase logo](https://www.metabase.com/images/logo.svg) +export const Markdown = { + render: Template, + + args: { + children: `![Metabase logo](https://www.metabase.com/images/logo.svg) -# New version + # New version -Our first email blast to the mailing list not directly linked to the release -of a new version. We wanted to see if this would effect visits to landing pages -for the features in 0.41. + Our first email blast to the mailing list not directly linked to the release + of a new version. We wanted to see if this would effect visits to landing pages + for the features in 0.41. ----- + ---- -Here’s a [doc](https://metabase.test) with the findings.`, + Here’s a [doc](https://metabase.test) with the findings.`, + }, }; diff --git a/frontend/src/metabase/core/components/MarkdownPreview/MarkdownPreview.tsx b/frontend/src/metabase/core/components/MarkdownPreview/MarkdownPreview.tsx index 36762dd2f73f1..5a35a2de26805 100644 --- a/frontend/src/metabase/core/components/MarkdownPreview/MarkdownPreview.tsx +++ b/frontend/src/metabase/core/components/MarkdownPreview/MarkdownPreview.tsx @@ -8,7 +8,7 @@ import Tooltip from "../Tooltip"; import C from "./MarkdownPreview.module.css"; -interface Props { +export interface MarkdownPreviewProps { children: string; className?: string; tooltipMaxWidth?: ComponentProps["maxWidth"]; @@ -26,7 +26,7 @@ export const MarkdownPreview = ({ lineClamp, allowedElements = DEFAULT_ALLOWED_ELEMENTS, oneLine, -}: Props) => { +}: MarkdownPreviewProps) => { const { isTruncated, ref } = useIsTruncated(); const setReactMarkdownRef: LegacyRef = div => { diff --git a/frontend/src/metabase/core/components/NumericInput/NumericInput.stories.tsx b/frontend/src/metabase/core/components/NumericInput/NumericInput.stories.tsx index dfac3bd278e8e..ee3c4e3bf0e73 100644 --- a/frontend/src/metabase/core/components/NumericInput/NumericInput.stories.tsx +++ b/frontend/src/metabase/core/components/NumericInput/NumericInput.stories.tsx @@ -1,4 +1,4 @@ -import type { ComponentStory } from "@storybook/react"; +import type { StoryFn } from "@storybook/react"; import { useState } from "react"; import NumericInput from "./NumericInput"; @@ -8,9 +8,11 @@ export default { component: NumericInput, }; -const Template: ComponentStory = args => { +const Template: StoryFn = args => { const [value, setValue] = useState(); return ; }; -export const Default = Template.bind({}); +export const Default = { + render: Template, +}; diff --git a/frontend/src/metabase/core/components/Radio/Radio.stories.tsx b/frontend/src/metabase/core/components/Radio/Radio.stories.tsx index 16842711d817e..b4cfc8d04ab9b 100644 --- a/frontend/src/metabase/core/components/Radio/Radio.stories.tsx +++ b/frontend/src/metabase/core/components/Radio/Radio.stories.tsx @@ -1,14 +1,14 @@ -import { useArgs } from "@storybook/addons"; -import type { ComponentStory } from "@storybook/react"; +import { useArgs } from "@storybook/preview-api"; +import type { StoryFn } from "@storybook/react"; -import Radio from "./Radio"; +import Radio, { type RadioProps } from "./Radio"; export default { title: "Deprecated/Radio", component: Radio, }; -const Template: ComponentStory = args => { +const Template: StoryFn> = args => { const [{ value }, updateArgs] = useArgs(); const handleChange = (value: unknown) => updateArgs({ value }); @@ -23,20 +23,29 @@ Template.args = { ], }; -export const Default = Template.bind({}); -Default.args = { - ...Template.args, - variant: "normal", +export const Default = { + render: Template, + + args: { + ...Template.args, + variant: "normal", + }, }; -export const Underlined = Template.bind({}); -Underlined.args = { - ...Template.args, - variant: "underlined", +export const Underlined = { + render: Template, + + args: { + ...Template.args, + variant: "underlined", + }, }; -export const Bubble = Template.bind({}); -Bubble.args = { - ...Template.args, - variant: "bubble", +export const Bubble = { + render: Template, + + args: { + ...Template.args, + variant: "bubble", + }, }; diff --git a/frontend/src/metabase/core/components/Select/Select.stories.tsx b/frontend/src/metabase/core/components/Select/Select.stories.tsx index 11c018a85a4ac..fff58b33ce6f7 100644 --- a/frontend/src/metabase/core/components/Select/Select.stories.tsx +++ b/frontend/src/metabase/core/components/Select/Select.stories.tsx @@ -1,4 +1,4 @@ -import type { ComponentStory } from "@storybook/react"; +import type { StoryFn } from "@storybook/react"; import Select from "./Select"; @@ -7,90 +7,93 @@ export default { component: Select, }; -const Template: ComponentStory = args => { +const Template: StoryFn = args => { return