diff --git a/.changeset/cuddly-berries-explain.md b/.changeset/cuddly-berries-explain.md new file mode 100644 index 0000000000..7fafb32120 --- /dev/null +++ b/.changeset/cuddly-berries-explain.md @@ -0,0 +1,6 @@ +--- +"@twilio-paste/core": patch +"@twilio-paste/data-visualization-library": patch +--- + +[Data Visualization Library] update Highcharts native tooltip styles to align more closely with Paste styles including correctly setting the font family diff --git a/.eslintignore b/.eslintignore index 009797d238..6aee0e1c31 100644 --- a/.eslintignore +++ b/.eslintignore @@ -19,3 +19,5 @@ packages/**/dist/* tsconfig.build.tsbuildinfo **/*.d.ts + +apps/backend/supabase/schema.gen.ts diff --git a/apps/backend/package.json b/apps/backend/package.json index ef7b193cbc..b6d95281e9 100644 --- a/apps/backend/package.json +++ b/apps/backend/package.json @@ -9,6 +9,6 @@ "db:reset": "yarn supabase db reset" }, "devDependencies": { - "supabase": "^2.6.8" + "supabase": "^2.8.1" } } diff --git a/apps/backend/supabase/schema.gen.ts b/apps/backend/supabase/schema.gen.ts index d7c0e7c109..b471f2dd96 100644 --- a/apps/backend/supabase/schema.gen.ts +++ b/apps/backend/supabase/schema.gen.ts @@ -225,23 +225,12 @@ export type Database = { [_ in never]: never } Functions: { - binary_quantize: - | { - Args: { - "": string - } - Returns: unknown - } - | { - Args: { - "": unknown - } - Returns: unknown - } + binary_quantize: { + Args: { "": string } | { "": unknown } + Returns: unknown + } get_page_parents: { - Args: { - page_id: number - } + Args: { page_id: number } Returns: { id: number parent_page_id: number @@ -250,103 +239,57 @@ export type Database = { }[] } halfvec_avg: { - Args: { - "": number[] - } + Args: { "": number[] } Returns: unknown } halfvec_out: { - Args: { - "": unknown - } + Args: { "": unknown } Returns: unknown } halfvec_send: { - Args: { - "": unknown - } + Args: { "": unknown } Returns: string } halfvec_typmod_in: { - Args: { - "": unknown[] - } + Args: { "": unknown[] } Returns: number } hnsw_bit_support: { - Args: { - "": unknown - } + Args: { "": unknown } Returns: unknown } hnsw_halfvec_support: { - Args: { - "": unknown - } + Args: { "": unknown } Returns: unknown } hnsw_sparsevec_support: { - Args: { - "": unknown - } + Args: { "": unknown } Returns: unknown } hnswhandler: { - Args: { - "": unknown - } + Args: { "": unknown } Returns: unknown } ivfflat_bit_support: { - Args: { - "": unknown - } + Args: { "": unknown } Returns: unknown } ivfflat_halfvec_support: { - Args: { - "": unknown - } + Args: { "": unknown } Returns: unknown } ivfflathandler: { - Args: { - "": unknown - } + Args: { "": unknown } + Returns: unknown + } + l2_norm: { + Args: { "": unknown } | { "": unknown } + Returns: number + } + l2_normalize: { + Args: { "": string } | { "": unknown } | { "": unknown } Returns: unknown } - l2_norm: - | { - Args: { - "": unknown - } - Returns: number - } - | { - Args: { - "": unknown - } - Returns: number - } - l2_normalize: - | { - Args: { - "": string - } - Returns: string - } - | { - Args: { - "": unknown - } - Returns: unknown - } - | { - Args: { - "": unknown - } - Returns: unknown - } match_discussions: { Args: { embedding: string @@ -420,21 +363,15 @@ export type Database = { }[] } sparsevec_out: { - Args: { - "": unknown - } + Args: { "": unknown } Returns: unknown } sparsevec_send: { - Args: { - "": unknown - } + Args: { "": unknown } Returns: string } sparsevec_typmod_in: { - Args: { - "": unknown[] - } + Args: { "": unknown[] } Returns: number } upsert_story_and_create_story_render: { @@ -450,46 +387,27 @@ export type Database = { Returns: undefined } vector_avg: { - Args: { - "": number[] - } + Args: { "": number[] } Returns: string } - vector_dims: - | { - Args: { - "": string - } - Returns: number - } - | { - Args: { - "": unknown - } - Returns: number - } + vector_dims: { + Args: { "": string } | { "": unknown } + Returns: number + } vector_norm: { - Args: { - "": string - } + Args: { "": string } Returns: number } vector_out: { - Args: { - "": string - } + Args: { "": string } Returns: unknown } vector_send: { - Args: { - "": string - } + Args: { "": string } Returns: string } vector_typmod_in: { - Args: { - "": unknown[] - } + Args: { "": unknown[] } Returns: number } } @@ -568,6 +486,7 @@ export type Database = { created_at: string | null id: string last_accessed_at: string | null + level: number | null metadata: Json | null name: string | null owner: string | null @@ -582,6 +501,7 @@ export type Database = { created_at?: string | null id?: string last_accessed_at?: string | null + level?: number | null metadata?: Json | null name?: string | null owner?: string | null @@ -596,6 +516,7 @@ export type Database = { created_at?: string | null id?: string last_accessed_at?: string | null + level?: number | null metadata?: Json | null name?: string | null owner?: string | null @@ -615,6 +536,38 @@ export type Database = { }, ] } + prefixes: { + Row: { + bucket_id: string + created_at: string | null + level: number + name: string + updated_at: string | null + } + Insert: { + bucket_id: string + created_at?: string | null + level?: number + name: string + updated_at?: string | null + } + Update: { + bucket_id?: string + created_at?: string | null + level?: number + name?: string + updated_at?: string | null + } + Relationships: [ + { + foreignKeyName: "prefixes_bucketId_fkey" + columns: ["bucket_id"] + isOneToOne: false + referencedRelation: "buckets" + referencedColumns: ["id"] + }, + ] + } s3_multipart_uploads: { Row: { bucket_id: string @@ -718,31 +671,40 @@ export type Database = { [_ in never]: never } Functions: { + add_prefixes: { + Args: { _bucket_id: string; _name: string } + Returns: undefined + } can_insert_object: { - Args: { - bucketid: string - name: string - owner: string - metadata: Json - } + Args: { bucketid: string; name: string; owner: string; metadata: Json } Returns: undefined } + delete_prefix: { + Args: { _bucket_id: string; _name: string } + Returns: boolean + } extension: { - Args: { - name: string - } + Args: { name: string } Returns: string } filename: { - Args: { - name: string - } + Args: { name: string } Returns: string } foldername: { - Args: { - name: string - } + Args: { name: string } + Returns: string[] + } + get_level: { + Args: { name: string } + Returns: number + } + get_prefix: { + Args: { name: string } + Returns: string + } + get_prefixes: { + Args: { name: string } Returns: string[] } get_size_by_bucket: { @@ -807,6 +769,63 @@ export type Database = { metadata: Json }[] } + search_legacy_v1: { + Args: { + prefix: string + bucketname: string + limits?: number + levels?: number + offsets?: number + search?: string + sortcolumn?: string + sortorder?: string + } + Returns: { + name: string + id: string + updated_at: string + created_at: string + last_accessed_at: string + metadata: Json + }[] + } + search_v1_optimised: { + Args: { + prefix: string + bucketname: string + limits?: number + levels?: number + offsets?: number + search?: string + sortcolumn?: string + sortorder?: string + } + Returns: { + name: string + id: string + updated_at: string + created_at: string + last_accessed_at: string + metadata: Json + }[] + } + search_v2: { + Args: { + prefix: string + bucket_name: string + limits?: number + levels?: number + start_after?: string + } + Returns: { + key: string + name: string + id: string + updated_at: string + created_at: string + metadata: Json + }[] + } } Enums: { [_ in never]: never @@ -817,27 +836,29 @@ export type Database = { } } -type PublicSchema = Database[Extract] +type DefaultSchema = Database[Extract] export type Tables< - PublicTableNameOrOptions extends - | keyof (PublicSchema["Tables"] & PublicSchema["Views"]) + DefaultSchemaTableNameOrOptions extends + | keyof (DefaultSchema["Tables"] & DefaultSchema["Views"]) | { schema: keyof Database }, - TableName extends PublicTableNameOrOptions extends { schema: keyof Database } - ? keyof (Database[PublicTableNameOrOptions["schema"]]["Tables"] & - Database[PublicTableNameOrOptions["schema"]]["Views"]) + TableName extends DefaultSchemaTableNameOrOptions extends { + schema: keyof Database + } + ? keyof (Database[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] & + Database[DefaultSchemaTableNameOrOptions["schema"]]["Views"]) : never = never, -> = PublicTableNameOrOptions extends { schema: keyof Database } - ? (Database[PublicTableNameOrOptions["schema"]]["Tables"] & - Database[PublicTableNameOrOptions["schema"]]["Views"])[TableName] extends { +> = DefaultSchemaTableNameOrOptions extends { schema: keyof Database } + ? (Database[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] & + Database[DefaultSchemaTableNameOrOptions["schema"]]["Views"])[TableName] extends { Row: infer R } ? R : never - : PublicTableNameOrOptions extends keyof (PublicSchema["Tables"] & - PublicSchema["Views"]) - ? (PublicSchema["Tables"] & - PublicSchema["Views"])[PublicTableNameOrOptions] extends { + : DefaultSchemaTableNameOrOptions extends keyof (DefaultSchema["Tables"] & + DefaultSchema["Views"]) + ? (DefaultSchema["Tables"] & + DefaultSchema["Views"])[DefaultSchemaTableNameOrOptions] extends { Row: infer R } ? R @@ -845,20 +866,22 @@ export type Tables< : never export type TablesInsert< - PublicTableNameOrOptions extends - | keyof PublicSchema["Tables"] + DefaultSchemaTableNameOrOptions extends + | keyof DefaultSchema["Tables"] | { schema: keyof Database }, - TableName extends PublicTableNameOrOptions extends { schema: keyof Database } - ? keyof Database[PublicTableNameOrOptions["schema"]]["Tables"] + TableName extends DefaultSchemaTableNameOrOptions extends { + schema: keyof Database + } + ? keyof Database[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] : never = never, -> = PublicTableNameOrOptions extends { schema: keyof Database } - ? Database[PublicTableNameOrOptions["schema"]]["Tables"][TableName] extends { +> = DefaultSchemaTableNameOrOptions extends { schema: keyof Database } + ? Database[DefaultSchemaTableNameOrOptions["schema"]]["Tables"][TableName] extends { Insert: infer I } ? I : never - : PublicTableNameOrOptions extends keyof PublicSchema["Tables"] - ? PublicSchema["Tables"][PublicTableNameOrOptions] extends { + : DefaultSchemaTableNameOrOptions extends keyof DefaultSchema["Tables"] + ? DefaultSchema["Tables"][DefaultSchemaTableNameOrOptions] extends { Insert: infer I } ? I @@ -866,20 +889,22 @@ export type TablesInsert< : never export type TablesUpdate< - PublicTableNameOrOptions extends - | keyof PublicSchema["Tables"] + DefaultSchemaTableNameOrOptions extends + | keyof DefaultSchema["Tables"] | { schema: keyof Database }, - TableName extends PublicTableNameOrOptions extends { schema: keyof Database } - ? keyof Database[PublicTableNameOrOptions["schema"]]["Tables"] + TableName extends DefaultSchemaTableNameOrOptions extends { + schema: keyof Database + } + ? keyof Database[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] : never = never, -> = PublicTableNameOrOptions extends { schema: keyof Database } - ? Database[PublicTableNameOrOptions["schema"]]["Tables"][TableName] extends { +> = DefaultSchemaTableNameOrOptions extends { schema: keyof Database } + ? Database[DefaultSchemaTableNameOrOptions["schema"]]["Tables"][TableName] extends { Update: infer U } ? U : never - : PublicTableNameOrOptions extends keyof PublicSchema["Tables"] - ? PublicSchema["Tables"][PublicTableNameOrOptions] extends { + : DefaultSchemaTableNameOrOptions extends keyof DefaultSchema["Tables"] + ? DefaultSchema["Tables"][DefaultSchemaTableNameOrOptions] extends { Update: infer U } ? U @@ -887,21 +912,23 @@ export type TablesUpdate< : never export type Enums< - PublicEnumNameOrOptions extends - | keyof PublicSchema["Enums"] + DefaultSchemaEnumNameOrOptions extends + | keyof DefaultSchema["Enums"] | { schema: keyof Database }, - EnumName extends PublicEnumNameOrOptions extends { schema: keyof Database } - ? keyof Database[PublicEnumNameOrOptions["schema"]]["Enums"] + EnumName extends DefaultSchemaEnumNameOrOptions extends { + schema: keyof Database + } + ? keyof Database[DefaultSchemaEnumNameOrOptions["schema"]]["Enums"] : never = never, -> = PublicEnumNameOrOptions extends { schema: keyof Database } - ? Database[PublicEnumNameOrOptions["schema"]]["Enums"][EnumName] - : PublicEnumNameOrOptions extends keyof PublicSchema["Enums"] - ? PublicSchema["Enums"][PublicEnumNameOrOptions] +> = DefaultSchemaEnumNameOrOptions extends { schema: keyof Database } + ? Database[DefaultSchemaEnumNameOrOptions["schema"]]["Enums"][EnumName] + : DefaultSchemaEnumNameOrOptions extends keyof DefaultSchema["Enums"] + ? DefaultSchema["Enums"][DefaultSchemaEnumNameOrOptions] : never export type CompositeTypes< PublicCompositeTypeNameOrOptions extends - | keyof PublicSchema["CompositeTypes"] + | keyof DefaultSchema["CompositeTypes"] | { schema: keyof Database }, CompositeTypeName extends PublicCompositeTypeNameOrOptions extends { schema: keyof Database @@ -910,7 +937,19 @@ export type CompositeTypes< : never = never, > = PublicCompositeTypeNameOrOptions extends { schema: keyof Database } ? Database[PublicCompositeTypeNameOrOptions["schema"]]["CompositeTypes"][CompositeTypeName] - : PublicCompositeTypeNameOrOptions extends keyof PublicSchema["CompositeTypes"] - ? PublicSchema["CompositeTypes"][PublicCompositeTypeNameOrOptions] + : PublicCompositeTypeNameOrOptions extends keyof DefaultSchema["CompositeTypes"] + ? DefaultSchema["CompositeTypes"][PublicCompositeTypeNameOrOptions] : never +export const Constants = { + graphql_public: { + Enums: {}, + }, + public: { + Enums: {}, + }, + storage: { + Enums: {}, + }, +} as const + diff --git a/cypress/integration/sidebar-navigation/index.spec.ts b/cypress/integration/sidebar-navigation/index.spec.ts index 55f3d365d9..610a1e4c50 100644 --- a/cypress/integration/sidebar-navigation/index.spec.ts +++ b/cypress/integration/sidebar-navigation/index.spec.ts @@ -1,9 +1,10 @@ const sidebarNavigationDisclosures = [ "introduction", + "foundations", + "data-visualization", "for-designers", "for-engineers", "contributing", - "foundations", "content", "patterns", "components", @@ -34,9 +35,10 @@ describe("Sidebar navigation", () => { cy.get(`[data-cy="${contentSelector}"]`).as("currentContent"); cy.get("@currentContent").should("have.css", "display", "none"); cy.get("@currentContent").should("have.attr", "hidden", "hidden"); - - cy.get(`[data-cy="${buttonSelector}"]`).click().should("have.attr", "aria-expanded", "true"); - cy.get("@currentContent").scrollIntoView().should("have.css", "display", "block"); + cy.get(`[data-cy="${buttonSelector}"]`).click({ multiple: true }).should("have.attr", "aria-expanded", "true"); + cy.get("@currentContent").each(($el) => { + cy.wrap($el).scrollIntoView().should("have.css", "display", "block"); + }); }); }); @@ -44,7 +46,7 @@ describe("Sidebar navigation", () => { const buttonSelector = `${BASE}-button-${disclosureName}`; it(`should close the the "${disclosureName}" sidebar disclosure`, () => { - cy.get(`[data-cy="${buttonSelector}"]`).click().should("have.attr", "aria-expanded", "false"); + cy.get(`[data-cy="${buttonSelector}"]`).click({ multiple: true }).should("have.attr", "aria-expanded", "false"); }); }); }); diff --git a/cypress/integration/sitemap-vrt/constants.ts b/cypress/integration/sitemap-vrt/constants.ts index adfb59814e..503efdb488 100644 --- a/cypress/integration/sitemap-vrt/constants.ts +++ b/cypress/integration/sitemap-vrt/constants.ts @@ -62,6 +62,9 @@ export const SITEMAP = [ "/components/card/", "/components/card/api", "/components/card/changelog", + "/components/chart-provider/", + "/components/chart-provider/api", + "/components/chart-provider/changelog", "/components/chat-composer/", "/components/chat-composer/api", "/components/chat-composer/changelog", @@ -285,6 +288,8 @@ export const SITEMAP = [ "/foundations/content/word-list/", "/foundations/illustrations/", "/foundations/data-visualization/", + "/foundations/data-visualization/for-engineers/", + "/foundations/data-visualization/for-engineers/chart-types/", "/foundations/spacing-and-layout/", "/foundations/typography/", "/inclusive-design/", diff --git a/packages/paste-libraries/data-visualization/__test__/__snapshots__/index.spec.tsx.snap b/packages/paste-libraries/data-visualization/__test__/__snapshots__/index.spec.tsx.snap index 1e7b20ed73..f8bb776329 100644 --- a/packages/paste-libraries/data-visualization/__test__/__snapshots__/index.spec.tsx.snap +++ b/packages/paste-libraries/data-visualization/__test__/__snapshots__/index.spec.tsx.snap @@ -131,9 +131,15 @@ Object { "text": "Solar Employment Growth by Sector, 2010-2016", }, "tooltip": Object { - "backgroundColor": "rgb(244, 244, 246)", + "backgroundColor": "rgb(18, 28, 45)", + "borderColor": "rgb(136, 145, 170)", + "borderRadius": "8", + "borderWidth": "1px", + "padding": 12, "style": Object { "color": "rgb(18, 28, 45)", + "fontFamily": "'Inter var experimental', 'Inter var', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif", + "fontSize": "0.875rem", }, }, "xAxis": Object { @@ -228,9 +234,15 @@ Object { }, }, "tooltip": Object { - "backgroundColor": "rgb(244, 244, 246)", + "backgroundColor": "rgb(18, 28, 45)", + "borderColor": "rgb(136, 145, 170)", + "borderRadius": "8", + "borderWidth": "1px", + "padding": 12, "style": Object { "color": "rgb(18, 28, 45)", + "fontFamily": "'Inter var experimental', 'Inter var', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif", + "fontSize": "0.875rem", }, }, "xAxis": Object { diff --git a/packages/paste-libraries/data-visualization/src/usePasteHighchartsTheme.tsx b/packages/paste-libraries/data-visualization/src/usePasteHighchartsTheme.tsx index 23e81c5079..c671813e3a 100644 --- a/packages/paste-libraries/data-visualization/src/usePasteHighchartsTheme.tsx +++ b/packages/paste-libraries/data-visualization/src/usePasteHighchartsTheme.tsx @@ -105,9 +105,15 @@ export const usePasteHighchartsTheme = (options: Highcharts.Options): Highcharts }, }, tooltip: { - backgroundColor: context.backgroundColors.colorBackground, + backgroundColor: context.backgroundColors.colorBackgroundBodyInverse, + borderColor: context.borderColors.colorBorderInverse, + borderWidth: context.borderWidths.borderWidth10, + borderRadius: context.radii.borderRadius30.replace("px", ""), + padding: 12, style: { + fontFamily: context.fonts.fontFamilyText, color: context.textColors.colorText, + fontSize: context.fontSizes.fontSize30, }, }, credits: { diff --git a/packages/paste-libraries/data-visualization/stories/base-chart.stories.tsx b/packages/paste-libraries/data-visualization/stories/base-chart.stories.tsx new file mode 100644 index 0000000000..da83445912 --- /dev/null +++ b/packages/paste-libraries/data-visualization/stories/base-chart.stories.tsx @@ -0,0 +1,29 @@ +import type { Meta, StoryFn } from "@storybook/react"; +import { ChartProvider } from "@twilio-paste/chart-provider"; +import { Stack } from "@twilio-paste/stack"; +import * as React from "react"; + +import { BaseChart } from "./components/BaseChart"; +import { lineChartOptions } from "./options/lineChartOptions"; + +// eslint-disable-next-line import/no-default-export +export default { + title: "Libraries/data-visualization/base-chart", + parameters: { + chromatic: { disableSnapshot: true }, + a11y: { + // no need to a11y check composition of a11y checked components + disable: true, + }, + }, +} as Meta; + +export const HighchartsOptions: StoryFn = () => { + return ( + + + + + + ); +}; diff --git a/packages/paste-libraries/data-visualization/stories/components/BaseChart.tsx b/packages/paste-libraries/data-visualization/stories/components/BaseChart.tsx new file mode 100644 index 0000000000..137003f155 --- /dev/null +++ b/packages/paste-libraries/data-visualization/stories/components/BaseChart.tsx @@ -0,0 +1,48 @@ +import { Box } from "@twilio-paste/box"; +import { ChartContext } from "@twilio-paste/chart-provider"; +import * as Highcharts from "highcharts"; +import HighchartsReact from "highcharts-react-official"; +import HighchartsAccessibilityModule from "highcharts/modules/accessibility"; +import * as React from "react"; + +import { applyPasteHighchartsModules, usePasteHighchartsTheme } from "../../src"; + +const Chart: React.FC = () => { + applyPasteHighchartsModules(Highcharts, HighchartsAccessibilityModule); + const chartRef = React.useRef(null); + const { options, setChart, setChartRef } = React.useContext(ChartContext); + const [chartOptions, setChartOptions] = React.useState( + // disabling animation for stories only. Not included in our docs examples + usePasteHighchartsTheme({ ...options, plotOptions: { series: { animation: false } } }), + ); + + React.useLayoutEffect(() => { + setChartOptions(Highcharts.merge(chartOptions, options)); + }, [options]); + + React.useEffect(() => { + if (chartRef.current) { + setChartRef(chartRef.current); + } + }, [chartRef.current]); + + const callback = (chart: Highcharts.Chart): void => { + if (chart?.series?.length > 0) { + setChart(chart); + } + }; + + return ( + + + + ); +}; + +export const BaseChart = React.memo(Chart); diff --git a/packages/paste-website/package.json b/packages/paste-website/package.json index 122ebf5c10..9eefcbc269 100644 --- a/packages/paste-website/package.json +++ b/packages/paste-website/package.json @@ -50,6 +50,7 @@ "@twilio-paste/button-group": "^5.0.1", "@twilio-paste/callout": "^5.0.1", "@twilio-paste/card": "^10.1.0", + "@twilio-paste/chart-provider": "^2.0.1", "@twilio-paste/chat-composer": "^6.0.1", "@twilio-paste/chat-log": "^6.0.1", "@twilio-paste/checkbox": "^14.0.1", diff --git a/packages/paste-website/src/component-examples/data-visualization/BaseChart.tsx b/packages/paste-website/src/component-examples/data-visualization/BaseChart.tsx new file mode 100644 index 0000000000..b9ad272c36 --- /dev/null +++ b/packages/paste-website/src/component-examples/data-visualization/BaseChart.tsx @@ -0,0 +1,44 @@ +import { Box } from "@twilio-paste/box"; +import { ChartContext } from "@twilio-paste/chart-provider"; +import { applyPasteHighchartsModules, usePasteHighchartsTheme } from "@twilio-paste/data-visualization-library"; +import * as Highcharts from "highcharts"; +import HighchartsReact from "highcharts-react-official"; +import HighchartsAccessibilityModule from "highcharts/modules/accessibility"; +import * as React from "react"; + +const Chart: React.FC = () => { + applyPasteHighchartsModules(Highcharts, HighchartsAccessibilityModule); + const chartRef = React.useRef(null); + const { options, setChart, setChartRef } = React.useContext(ChartContext); + const [chartOptions, setChartOptions] = React.useState(usePasteHighchartsTheme(options)); + + React.useLayoutEffect(() => { + setChartOptions(Highcharts.merge(chartOptions, options)); + }, [options]); + + React.useEffect(() => { + if (chartRef.current) { + setChartRef(chartRef.current); + } + }, [chartRef.current]); + + const callback = (chart: Highcharts.Chart): void => { + if (chart?.series?.length > 0) { + setChart(chart); + } + }; + + return ( + + + + ); +}; + +export const ExamplesDataVizBaseChart = React.memo(Chart); diff --git a/packages/paste-website/src/component-examples/data-visualization/ChartProviderExamples.ts b/packages/paste-website/src/component-examples/data-visualization/ChartProviderExamples.ts new file mode 100644 index 0000000000..461f90b637 --- /dev/null +++ b/packages/paste-website/src/component-examples/data-visualization/ChartProviderExamples.ts @@ -0,0 +1,57 @@ +export const SimpleChartProviderExample = ` +const ChartProviderExample = () => { + const lineSeriesData = [ + { + name: "Installation", + data: [43934, 52503, 57177, 69658, 97031, 119931, 137133, 154175], + }, + { + name: "Manufacturing", + data: [24916, 24064, 29742, 29851, 32490, 30282, 38121, 40434], + }, + ] + + return ( + + + + ); +}; + +render(); +`.trim(); + +export const CustomChartProviderExample = ` +const ChartProviderExample = () => { + const lineSeriesData = [ + { + name: "Installation", + data: [43934, 52503, 57177, 69658, 97031, 119931, 137133, 154175], + type: "line", + }, + { + name: "Manufacturing", + data: [24916, 24064, 29742, 29851, 32490, 30282, 38121, 40434], + type: "line", + }, + { + name: "Sales & Distribution", + data: [11744, 17722, 16005, 19771, 20185, 24377, 32147, 39387], + type: "column", + }, + { + name: "Project Development", + data: [null, null, 7988, 12169, 15112, 22452, 34400, 34227], + type: "column", + }, + ] + + return ( + + + + ); +}; + +render(); +`.trim(); diff --git a/packages/paste-website/src/components/site-wrapper/sidebar/SidebarNavigation.tsx b/packages/paste-website/src/components/site-wrapper/sidebar/SidebarNavigation.tsx index 3c5dc6565d..7b7ce2a235 100644 --- a/packages/paste-website/src/components/site-wrapper/sidebar/SidebarNavigation.tsx +++ b/packages/paste-website/src/components/site-wrapper/sidebar/SidebarNavigation.tsx @@ -135,9 +135,20 @@ const SiteSidebarNavigation = (): JSX.Element => { Word list - - Data visualization - + + Overview + + + Getting started + + + Chart types + + + @@ -363,6 +374,32 @@ const SiteSidebarNavigation = (): JSX.Element => { if (name === "Sidebar Navigation") { return null; } + if (["Data Visualization"].includes(name)) { + return null; + } + if (name === "Chart Provider") { + return ( + + event({ + category: "Left Navigation", + action: `click-${name}`, + label: "Data Visualization", + }) + } + > + + Chart Provider + + + ); + } return ( {name} diff --git a/packages/paste-website/src/constants.ts b/packages/paste-website/src/constants.ts index faf02fb74f..96caa18a22 100644 --- a/packages/paste-website/src/constants.ts +++ b/packages/paste-website/src/constants.ts @@ -48,6 +48,7 @@ export const SidebarCategoryRoutes = { FOUNDATIONS: "/foundations", CONTENT: "/foundations/content", FOUNDATIONS_LOCALIZATION: "/foundations/localization", + DATA_VISUALIZATION: "/foundations/data-visualization", PATTERNS: "/patterns", EXPERIENCES: "/experiences", COMPONENTS: "/components", diff --git a/packages/paste-website/src/pages/components/chart-provider/api.mdx b/packages/paste-website/src/pages/components/chart-provider/api.mdx new file mode 100644 index 0000000000..68a507997a --- /dev/null +++ b/packages/paste-website/src/pages/components/chart-provider/api.mdx @@ -0,0 +1,61 @@ +import Changelog from '@twilio-paste/chart-provider/CHANGELOG.md'; // I don't know why this is needed but if you remove it the page fails to render +import packageJson from '@twilio-paste/chart-provider/package.json'; + +import {SidebarCategoryRoutes} from '../../../constants'; +import ComponentPageLayout from '../../../layouts/ComponentPageLayout'; +import {getFeature, getNavigationData, getComponentApi} from '../../../utils/api'; + +export const meta = { + title: 'ChartProvider', + package: '@twilio-paste/chart-provider', + description: packageJson.description, + slug: '/components/chart-provider/api/', +}; + +export default ComponentPageLayout; + +export const getStaticProps = async () => { + const navigationData = await getNavigationData(); + const feature = await getFeature('Chart Provider'); + const {componentApi, componentApiTocData} = getComponentApi('@twilio-paste/chart-provider'); + return { + props: { + data: { + ...packageJson, + ...feature, + }, + componentApi, + mdxHeadings: [...mdxHeadings, ...componentApiTocData], + navigationData, + pageHeaderData: { + categoryRoute: SidebarCategoryRoutes.COMPONENTS, + githubUrl: 'https://github.com/twilio-labs/paste/tree/main/packages/paste-core/components/chart-provider', + storybookUrl: '/?path=/story/components-chartprovider', + }, + }, + }; +}; + +## Installation + +```bash +yarn add @twilio-paste/chart-provider - or - yarn add @twilio-paste/core +``` + +## Usage + +```jsx +import { ChartProvider } from '@twilio-paste/core/chart-provider'; + +const ChartProviderExample = () => { + return ( + + + + ); +}; +``` + +## Props + + diff --git a/packages/paste-website/src/pages/components/chart-provider/changelog.mdx b/packages/paste-website/src/pages/components/chart-provider/changelog.mdx new file mode 100644 index 0000000000..019de7af08 --- /dev/null +++ b/packages/paste-website/src/pages/components/chart-provider/changelog.mdx @@ -0,0 +1,36 @@ +import {SidebarCategoryRoutes} from '../../../constants'; +import Changelog from '@twilio-paste/chart-provider/CHANGELOG.md'; +import packageJson from '@twilio-paste/chart-provider/package.json'; +import ComponentPageLayout from '../../../layouts/ComponentPageLayout'; +import {getFeature, getNavigationData} from '../../../utils/api'; + +export const meta = { + title: 'Chart Provider', + package: '@twilio-paste/chart-provider', + description: packageJson.description, + slug: '/components/chart-provider/changelog/', +}; + +export default ComponentPageLayout; + +export const getStaticProps = async () => { + const navigationData = await getNavigationData(); + const feature = await getFeature('Chart Provider'); + return { + props: { + data: { + ...packageJson, + ...feature, + }, + navigationData, + mdxHeadings, + pageHeaderData: { + categoryRoute: SidebarCategoryRoutes.COMPONENTS, + githubUrl: 'https://github.com/twilio-labs/paste/tree/main/packages/paste-core/components/chart-provider', + storybookUrl: '/?path=/story/components-chartprovider', + }, + }, + }; +}; + + diff --git a/packages/paste-website/src/pages/components/chart-provider/index.mdx b/packages/paste-website/src/pages/components/chart-provider/index.mdx new file mode 100644 index 0000000000..ade9cc520e --- /dev/null +++ b/packages/paste-website/src/pages/components/chart-provider/index.mdx @@ -0,0 +1,77 @@ +import { ChartProvider } from '@twilio-paste/chart-provider'; +import { Box } from '@twilio-paste/box'; +import { Callout, CalloutHeading, CalloutText } from '@twilio-paste/callout'; +import packageJson from '@twilio-paste/chart-provider/package.json'; + +import {SidebarCategoryRoutes} from '../../../constants'; +import ComponentPageLayout from '../../../layouts/ComponentPageLayout'; +import {getFeature, getNavigationData} from '../../../utils/api'; +import {ExamplesDataVizBaseChart as BaseChart} from '../../../component-examples/data-visualization/BaseChart'; +import {CustomChartProviderExample, SimpleChartProviderExample} from '../../../component-examples/data-visualization/ChartProviderExamples'; + +export const meta = { + title: 'Chart Provider', + package: '@twilio-paste/chart-provider', + description: packageJson.description, + slug: '/components/chart-provider/', +}; + +export default ComponentPageLayout; + +export const getStaticProps = async () => { + const navigationData = await getNavigationData(); + const feature = await getFeature('Chart Provider'); + return { + props: { + data: { + ...packageJson, + ...feature, + }, + navigationData, + mdxHeadings, + pageHeaderData: { + categoryRoute: SidebarCategoryRoutes.COMPONENTS, + githubUrl: 'https://github.com/twilio-labs/paste/tree/main/packages/paste-core/components/chart-provider', + storybookUrl: '/?path=/story/components-chartprovider', //TODO: Update this to the correct storybook URL + }, + }, + }; +}; + + +{SimpleChartProviderExample} + + +## Guidelines + +## About Chart Provider + +Chart Provider is a wrapper around Highcharts that provides a consistent API for configuring an individual chart instance. This component has no visible elements and is an engineering asset only. It acts as a store for chart options and provides a context for managing chart state. + +We highly recommend using our [BaseChart](/foundations/data-visualization/for-engineers#basechart) code inside the Chart Provider to ensure that the chart is rendered correctly and state is correctly stored. + +## Examples + +### Custom charts + +You can use the `highchartsOptions` prop to create any currently unsupported charts by passing the Highcharts config directly to the base chart without modification. We recommend using our wrappers for easier migration and a simpler way of accessing the rendered chart object for building custom interactions. + + + + Compatibility + If you build charts using the Highcharts API directly it will be unlikely that our Paste data visualization components will function correctly as they depend on helper functions and event tracking that we enrich the default options with. + + + + +{CustomChartProviderExample} + + diff --git a/packages/paste-website/src/pages/foundations/data-visualization/for-engineers/chart-types.mdx b/packages/paste-website/src/pages/foundations/data-visualization/for-engineers/chart-types.mdx new file mode 100644 index 0000000000..f6cb8046f3 --- /dev/null +++ b/packages/paste-website/src/pages/foundations/data-visualization/for-engineers/chart-types.mdx @@ -0,0 +1,71 @@ +export const meta = { + title: "Chart types", + description: "Display the supported chart types for the Paste Data Visualization library with coded examples.", + slug: "/foundations/data-visualization/engineering/chart-types", +}; + +import Image from "next/image"; +import Highcharts from "highcharts"; +import HighchartsAccessibilityModule from "highcharts/modules/accessibility"; +import HighchartsReact from "highcharts-react-official"; +import { applyPasteHighchartsModules, usePasteHighchartsTheme } from "@twilio-paste/data-visualization-library"; + +import { Box } from "@twilio-paste/box"; +import { Heading } from "@twilio-paste/heading"; +import { Text } from "@twilio-paste/text"; +import { Callout, CalloutHeading, CalloutText } from "@twilio-paste/callout"; +import { ChartProvider } from "@twilio-paste/chart-provider"; +import { PageHeaderSeparator } from "@twilio-paste/page-header"; +import { Separator } from "@twilio-paste/separator"; +import { InlineCode } from "@twilio-paste/inline-code"; +import { Anchor } from "@twilio-paste/anchor"; + +import { SidebarCategoryRoutes } from "../../../../constants"; +import DefaultLayout from "../../../../layouts/DefaultLayout"; +import { getNavigationData } from "../../../../utils/api"; +import { CustomChartProviderExample } from "../../../../component-examples/data-visualization/ChartProviderExamples"; +import { ExamplesDataVizBaseChart as BaseChart } from "../../../../component-examples/data-visualization/BaseChart"; + +export default DefaultLayout; + +export const getStaticProps = async () => { + const navigationData = await getNavigationData(); + return { + props: { + navigationData, + }, + }; +}; + + + + + + + + + + + + + + + + In progress + As our current data visualization offerings are in progress, these guidelines may change. We will update these pages when new features are supported. Please raise a GitHub discussion for any feedback or requests. + + + +## Custom + + +{CustomChartProviderExample} + + + + + diff --git a/packages/paste-website/src/pages/foundations/data-visualization/for-engineers/index.mdx b/packages/paste-website/src/pages/foundations/data-visualization/for-engineers/index.mdx new file mode 100644 index 0000000000..5c880a6849 --- /dev/null +++ b/packages/paste-website/src/pages/foundations/data-visualization/for-engineers/index.mdx @@ -0,0 +1,157 @@ +export const meta = { + title: "Getting started", + description: "An overview of integrating Highcharts with Paste's data visualization components, including setup, licensing, and chart context.", + slug: "/foundations/data-visualization/for-engineers/", +}; + +import Image from "next/image"; +import Highcharts from "highcharts"; +import HighchartsAccessibilityModule from "highcharts/modules/accessibility"; +import HighchartsReact from "highcharts-react-official"; +import { applyPasteHighchartsModules, usePasteHighchartsTheme } from "@twilio-paste/data-visualization-library"; + +import { Box } from "@twilio-paste/box"; +import { Heading } from "@twilio-paste/heading"; +import { Text } from "@twilio-paste/text"; +import { Callout, CalloutHeading, CalloutText } from "@twilio-paste/callout"; +import { PageHeaderSeparator } from "@twilio-paste/page-header"; +import { Separator } from "@twilio-paste/separator"; +import { InlineCode } from "@twilio-paste/inline-code"; +import { Anchor} from "@twilio-paste/anchor"; + +import { SidebarCategoryRoutes } from "../../../../constants"; +import DefaultLayout from "../../../../layouts/DefaultLayout"; +import { getNavigationData } from "../../../../utils/api"; + +export default DefaultLayout; + +export const getStaticProps = async () => { + const navigationData = await getNavigationData(); + return { + props: { + navigationData, + }, + }; +}; + + + + + + + + + + + + + +## Introduction + + + + In progress + As our current data visualization offerings are in progress, these guidelines may change. We will update these pages when new features are supported. Please raise a GitHub discussion for any feedback or requests. + + + +Our charting components are designed to work seamlessly with Highcharts. However, due to licensing restrictions, we cannot include Highcharts directly in the Paste library. + +To address this, we have created components and wrappers that simplify the Highcharts API. These tools expose various props, allowing you to configure charts through a streamlined and user-friendly interface. The props are transformed into objects that Highcharts can interpret, and our components automatically apply consistent styles to the charts. Global styles will be set in the `BaseChart` using our existing hook. + +## Licensing + +Paste does not provide a license for Twilio usage. Licenses are acquired on an application level. For 2025, licenses have already been purchased for these applications: + +- Twilio Admin +- Twilio Console +- Twilio Flex +- Twilio Segment +- Twilio SendGrid + +If you want to use the Paste charting components in another application, you'll need to acquire a new license. If you're a Twilio employee and need further information, you can reach out to us via [GitHub discussions](https://github.com/twilio-labs/paste/discussions/new?category=q-a) or the Twilio Procurement team. + +## Setup + +To ensure our components function correctly, some initial configuration is required in your project. This seciton will cover: +- Adding any additional modules required for additional functionality such as gauges, exporting etc. +- Storing and retrieving rendered chart objects. + +You will need to include a component that retrieves the chart configuration from a [Chart Provider](/components/chart-provider)'s context and passes it to Highcharts. This component must also capture the rendered chart and store it in the Chart Provider context. + +Storing the rendered chart is essential for several reasons. It allows us to determine the positioning of elements relative to the screen, enabling the placement of components like Tooltips. Additionally, it facilitates triggering update functions on the chart for interactions such as zooming or toggling the visibility of series through a legend component. + +### BaseChart + +We recommend copying the below code and creating an instance of it in your application to use with our components. This component is designed to be reused across all charts. You do not need a new instance of this component for each chart. + + + + Modules are subject to change + As we expand our supported charts you will need to maintain this file to include any required modules for the new charts or functionality of our components to work correctly. For example, you might change this line of code from this: + applyPasteHighchartsModules(Highcharts, HighchartsAccessibilityModule); + To this: + applyPasteHighchartsModules(Highcharts, HighchartsAccessibilityModule, HighchartsSankeyModule, ...); + + + +```jsx +import { ChartContext } from "@twilio-paste/core/chart-provider"; +import { Box } from "@twilio-paste/core/box"; +import { applyPasteHighchartsModules, usePasteHighchartsTheme } from "@twilio-paste/core/data-visualization-library"; +import * as Highcharts from "highcharts"; +import HighchartsReact from "highcharts-react-official"; +import HighchartsAccessibilityModule from "highcharts/modules/accessibility"; +import * as React from "react"; + +const Chart: React.FC = () => { + // Load the accessibility module and any other modules you need. + applyPasteHighchartsModules(Highcharts, HighchartsAccessibilityModule); + const chartRef = React.useRef(null); + const { options, setChart, setChartRef } = React.useContext(ChartContext); + const [chartOptions, setChartOptions] = React.useState(usePasteHighchartsTheme(options)); + + React.useLayoutEffect(() => { + setChartOptions(Highcharts.merge(chartOptions, options)); + }, [options]); + + React.useEffect(() => { + if (chartRef.current) { + setChartRef(chartRef.current); + } + }, [chartRef.current]); + + const callback = (chart: Highcharts.Chart) => { + // Ensure the chart has been rendered before setting it. This will cause issues in our components if the series is empty. + if (chart?.series?.length > 0) { + setChart(chart); + } + }; + + return ( + + + + ); +}; + +export const BaseChart = React.memo(Chart); +``` + +### Chart context + +We use React Context to store the rendered chart object to use in our components. When talking about the chart context we do not only mean the rendered object but also the initial configuration. You will need to pass data to the context for the `BaseChart` to read and use. + +Each individual chart instance must be wrapped in a [Chart Provider](/components/chart-provider) which sets the initial configuration and applies chart-specific styles. + +An individual chart instance doesn't only contain a chart. It also contains chart titles, legends, tooltips, and any other component that's not part of the chart canvas. In simpler terms, it is a container that wraps not only the Highcharts elements, but also any of our Paste components that interact with that chart and canvas. + + + + diff --git a/packages/paste-website/src/pages/foundations/data-visualization/index.mdx b/packages/paste-website/src/pages/foundations/data-visualization/index.mdx index bb542d3552..f81fd69f82 100644 --- a/packages/paste-website/src/pages/foundations/data-visualization/index.mdx +++ b/packages/paste-website/src/pages/foundations/data-visualization/index.mdx @@ -16,6 +16,7 @@ import { Text } from "@twilio-paste/text"; import { Callout, CalloutHeading, CalloutText } from "@twilio-paste/callout"; import { PageHeaderSeparator } from "@twilio-paste/page-header"; import { Separator } from "@twilio-paste/separator"; +import {Anchor} from "@twilio-paste/anchor"; import { ResponsiveImage } from "../../../components/ResponsiveImage"; import { SidebarCategoryRoutes } from "../../../constants"; @@ -62,6 +63,15 @@ This foundation page was created to help establish a familiar and accessible use ### For engineers + + + Paste chart support + + We're actively working on full charting support through a suite of Paste components designed to make it easy to build pre-styled, interactive charts. As we expand these capabilities, we will continue adding more examples and documentation in the For engineers section. We plan to roll out support for additional chart types over time and recommend using our components if they fit your current needs. + + + + The easiest way to use the data visualization tokens in your charts is to use `usePasteHighchartsTheme` from the [data visualization library package](/core/libraries/data-visualization) with [Highcharts](https://www.highcharts.com/). The `usePasteHighchartsTheme` hook takes an object of Highchart configurations and returns a new object with Paste colors and fonts. Be sure to include the [Highcharts Accessibility module](/foundations/data-visualization#adding-highcharts-accessibility-module) to ensure that your charts are accessible to all users. diff --git a/yarn.lock b/yarn.lock index c6b43a1128..62fc01cb97 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11099,7 +11099,7 @@ __metadata: version: 0.0.0-use.local resolution: "@twilio-paste/backend@workspace:apps/backend" dependencies: - supabase: ^2.6.8 + supabase: ^2.8.1 languageName: unknown linkType: soft @@ -15770,6 +15770,7 @@ __metadata: "@twilio-paste/button-group": ^5.0.1 "@twilio-paste/callout": ^5.0.1 "@twilio-paste/card": ^10.1.0 + "@twilio-paste/chart-provider": ^2.0.1 "@twilio-paste/chat-composer": ^6.0.1 "@twilio-paste/chat-log": ^6.0.1 "@twilio-paste/checkbox": ^14.0.1 @@ -43343,9 +43344,9 @@ resolve@^2.0.0-next.3: languageName: node linkType: hard -"supabase@npm:^2.6.8": - version: 2.8.1 - resolution: "supabase@npm:2.8.1" +"supabase@npm:^2.8.1": + version: 2.22.1 + resolution: "supabase@npm:2.22.1" dependencies: bin-links: ^5.0.0 https-proxy-agent: ^7.0.2 @@ -43353,7 +43354,7 @@ resolve@^2.0.0-next.3: tar: 7.4.3 bin: supabase: bin/supabase - checksum: 10b240963da4263ad8d03e84b97939504f5690eb894a9ee8ae225d69c0b8f31a890e9e24e9d4b2757c714eb38addb6878f6e531f9f7279c22de50a991b8e7cd4 + checksum: 2b12441bc6754b6b845e91765b2b7585aae7af944fb77d20f52e4894e70761650dfb148453c83a6eaee426bba5e2f28da2200e8f4f86a31fbc34dc70f35f4c84 languageName: node linkType: hard