From a8f453c75916be39846f0e4fd6deff0bec68bd22 Mon Sep 17 00:00:00 2001 From: Jamie Henson Date: Fri, 13 Sep 2024 16:49:17 +0100 Subject: [PATCH 1/7] fix: load icons on story render, add missing colors, reserve text/bg classes --- .storybook/preview.tsx | 8 +++++++- tailwind.config.js | 3 +++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx index 40c359bfd..888b6f220 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -29,6 +29,12 @@ initialize({ }); const preview: Preview = { + decorators: [ + (Story) => { + loadIcons(); + return ; + }, + ], parameters: { controls: { matchers: { @@ -43,7 +49,7 @@ const preview: Preview = { options: { storySort: { method: "alphabetical", - order: ["CSS", "JS Components", "Brand"], + order: ["CSS", "Features", "JS Components", "Brand"], }, }, darkMode: { diff --git a/tailwind.config.js b/tailwind.config.js index c8483fe4c..a7ba8e1f0 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -8,6 +8,8 @@ module.exports = { "w-1/6", { pattern: /^hljs.*/ }, { pattern: /^ui-.*/ }, + { pattern: /^text-.*/ }, + { pattern: /^bg-.*/ }, ], theme: { screens: { @@ -52,6 +54,7 @@ module.exports = { code2: ["var(--fs-code2)", "var(--lh-dense)"], }, colors: { + "neutral-000": "var(--color-neutral-000)", "neutral-100": "var(--color-neutral-100)", "neutral-200": "var(--color-neutral-200)", "neutral-300": "var(--color-neutral-300)", From c7d0e61521651e7f92d497d24a826f7f7fb43859 Mon Sep 17 00:00:00 2001 From: Jamie Henson Date: Fri, 13 Sep 2024 16:50:26 +0100 Subject: [PATCH 2/7] feat: dynamic theming calculations --- src/core/Icon.tsx | 4 +-- src/core/styles/colors/types.ts | 2 ++ src/core/styles/colors/utils.ts | 49 +++++++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 3 deletions(-) create mode 100644 src/core/styles/colors/utils.ts diff --git a/src/core/Icon.tsx b/src/core/Icon.tsx index c97ec61c3..740a5ae55 100644 --- a/src/core/Icon.tsx +++ b/src/core/Icon.tsx @@ -2,6 +2,7 @@ import React, { CSSProperties } from "react"; import { defaultIconSecondaryColor } from "./Icon/secondary-colors"; import { IconName } from "./Icon/types"; import { ColorClass } from "./styles/colors/types"; +import { convertTailwindClassToVar } from "./styles/colors/utils"; type IconProps = { name: IconName; @@ -11,9 +12,6 @@ type IconProps = { additionalCSS?: string; }; -const convertTailwindClassToVar = (className: string) => - className.replace(/(text|bg)-([a-z0-9-]+)/gi, "var(--color-$2)"); - const Icon = ({ name, size = "0.75rem", diff --git a/src/core/styles/colors/types.ts b/src/core/styles/colors/types.ts index b994c5302..b563ecd37 100644 --- a/src/core/styles/colors/types.ts +++ b/src/core/styles/colors/types.ts @@ -7,6 +7,8 @@ export type ColorName = type ColorClassPrefixes = "bg" | "text"; +export type Theme = "light" | "dark"; + export type ColorClass = `${ColorClassPrefixes}-${ColorName}`; const neutralColors = [ diff --git a/src/core/styles/colors/utils.ts b/src/core/styles/colors/utils.ts new file mode 100644 index 000000000..aaaca61b9 --- /dev/null +++ b/src/core/styles/colors/utils.ts @@ -0,0 +1,49 @@ +import { ColorClass, colorNames, Theme } from "./types"; + +export const convertTailwindClassToVar = (className: string) => + className.replace(/(text|bg)-([a-z0-9-]+)/gi, "var(--color-$2)"); + +const calculatePaletteRange = ( + acc: { max: number; min: number }, + color: string, +) => { + const splitColor = color.split("-"); + const numericalColor = Number(splitColor[splitColor.length - 1]); + + return { + min: acc.min === -1 || numericalColor < acc.min ? numericalColor : acc.min, + max: acc.max === -1 || numericalColor > acc.max ? numericalColor : acc.max, + }; +}; + +export const determineThemeColor = ( + baseTheme: Theme, + currentTheme: Theme, + color: ColorClass, +) => { + if (baseTheme === currentTheme) { + return color; + } else { + const splitColor = color.split("-"); + + if (splitColor.length === 3) { + const [property, palette, variant] = splitColor; + + const paletteColors = Object.keys(colorNames).includes(palette) + ? colorNames[palette as keyof typeof colorNames] + : colorNames.secondary; + + const { min, max } = paletteColors.reduce( + (acc, color) => calculatePaletteRange(acc, color), + { + min: -1, + max: -1, + }, + ); + + return `${property}-${palette}-${(max + min - Number(variant)).toString().padStart(3, "0")}` as ColorClass; + } else { + return color; + } + } +}; From 677e133d5831d01530634289798e96b3d4d9c251 Mon Sep 17 00:00:00 2001 From: Jamie Henson Date: Wed, 18 Sep 2024 13:57:12 +0100 Subject: [PATCH 3/7] chore: add TW container queries plugin --- package.json | 1 + tailwind.config.js | 2 +- yarn.lock | 5 +++++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 482d21428..b5fa0fa2d 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "@storybook/test-runner": "^0.19.1", "@swc/cli": "^0.4.0", "@swc/core": "^1.4.11", + "@tailwindcss/container-queries": "^0.1.1", "@types/dompurify": "^3.0.5", "@types/js-cookie": "^3.0.6", "@types/lodash.throttle": "^4.1.9", diff --git a/tailwind.config.js b/tailwind.config.js index a7ba8e1f0..a887e77d2 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -352,5 +352,5 @@ module.exports = { corePlugins: { preflight: false, }, - plugins: [], + plugins: [require("@tailwindcss/container-queries")], }; diff --git a/yarn.lock b/yarn.lock index eeffecd28..da996daa8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2064,6 +2064,11 @@ dependencies: defer-to-connect "^2.0.0" +"@tailwindcss/container-queries@^0.1.1": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@tailwindcss/container-queries/-/container-queries-0.1.1.tgz#9a759ce2cb8736a4c6a0cb93aeb740573a731974" + integrity sha512-p18dswChx6WnTSaJCSGx6lTmrGzNNvm2FtXmiO6AuA1V4U5REyoqwmT6kgAsIMdjo07QdAfYXHJ4hnMtfHzWgA== + "@testing-library/dom@10.4.0": version "10.4.0" resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-10.4.0.tgz#82a9d9462f11d240ecadbf406607c6ceeeff43a8" From f1029530750744458d2416bcc1736f0b053a96a4 Mon Sep 17 00:00:00 2001 From: Jamie Henson Date: Tue, 17 Sep 2024 11:35:51 +0100 Subject: [PATCH 4/7] feat: add PricingCards component for pricing sections --- src/core/FeaturedLink.tsx | 6 +- .../FeaturedLink.stories.tsx.snap | 10 +- .../__snapshots__/Meganav.stories.tsx.snap | 14 +- src/core/Pricing/PricingCards.stories.tsx | 53 + src/core/Pricing/PricingCards.tsx | 163 ++ .../PricingCards.stories.tsx.snap | 1541 +++++++++++++++++ src/core/Pricing/data.tsx | 269 +++ src/core/Pricing/types.ts | 22 + .../ProductTile.stories.tsx.snap | 6 +- src/core/styles/colors/types.ts | 7 +- tailwind.config.js | 20 +- 11 files changed, 2080 insertions(+), 31 deletions(-) create mode 100644 src/core/Pricing/PricingCards.stories.tsx create mode 100644 src/core/Pricing/PricingCards.tsx create mode 100644 src/core/Pricing/__snapshots__/PricingCards.stories.tsx.snap create mode 100644 src/core/Pricing/data.tsx create mode 100644 src/core/Pricing/types.ts diff --git a/src/core/FeaturedLink.tsx b/src/core/FeaturedLink.tsx index d6664617d..26e1020db 100644 --- a/src/core/FeaturedLink.tsx +++ b/src/core/FeaturedLink.tsx @@ -39,7 +39,7 @@ const buildTargetAndRel = (url: string, newWindow: boolean) => { const FeaturedLink = ({ url, textSize = "text-p2", - iconColor = "text-cool-black", + iconColor, flush = false, reverse = false, additionalCSS = "", @@ -72,7 +72,7 @@ const FeaturedLink = ({ name="icon-gui-link-arrow" size={`calc(var(--featured-link-icon-size) * 1.25)`} color={iconColor} - additionalCSS="align-middle mr-8 relative -top-1 -right-4 transition-all group-hover:right-0 transform rotate-180" + additionalCSS="align-middle mr-8 relative -top-1 -right-4 transition-[right] group-hover:right-0 transform rotate-180" /> {children} @@ -83,7 +83,7 @@ const FeaturedLink = ({ name="icon-gui-link-arrow" size={`calc(var(--featured-link-icon-size) * 1.25)`} color={iconColor} - additionalCSS="align-middle ml-8 relative -top-1 -left-4 transition-all group-hover:left-0" + additionalCSS="align-middle ml-8 relative -top-1 -left-4 transition-[left] group-hover:left-0" /> )} diff --git a/src/core/FeaturedLink/__snapshots__/FeaturedLink.stories.tsx.snap b/src/core/FeaturedLink/__snapshots__/FeaturedLink.stories.tsx.snap index dc23694c2..b09459935 100644 --- a/src/core/FeaturedLink/__snapshots__/FeaturedLink.stories.tsx.snap +++ b/src/core/FeaturedLink/__snapshots__/FeaturedLink.stories.tsx.snap @@ -6,7 +6,7 @@ exports[`JS Components/Featured Link Default smoke-test 1`] = ` style="--featured-link-icon-size: var(--fs-p2);" > Featured link - @@ -21,7 +21,7 @@ exports[`JS Components/Featured Link Large smoke-test 1`] = ` style="--featured-link-icon-size: var(--fs-p1);" > Featured link - @@ -36,7 +36,7 @@ exports[`JS Components/Featured Link Pink smoke-test 1`] = ` style="--featured-link-icon-size: var(--fs-p2);" > Featured link - @@ -50,7 +50,7 @@ exports[`JS Components/Featured Link Reverse smoke-test 1`] = ` class="font-sans font-bold block text-gui-default hover:text-gui-hover focus:text-gui-focus focus:outline-gui-focus group ui-text-p2 py-8 " style="--featured-link-icon-size: var(--fs-p2);" > - @@ -66,7 +66,7 @@ exports[`JS Components/Featured Link Small smoke-test 1`] = ` style="--featured-link-icon-size: var(--fs-p3);" > Featured link - diff --git a/src/core/Meganav/__snapshots__/Meganav.stories.tsx.snap b/src/core/Meganav/__snapshots__/Meganav.stories.tsx.snap index d22e2eba0..e513d1c92 100644 --- a/src/core/Meganav/__snapshots__/Meganav.stories.tsx.snap +++ b/src/core/Meganav/__snapshots__/Meganav.stories.tsx.snap @@ -69,7 +69,7 @@ exports[`JS Components/Meganav Default smoke-test 1`] = ` style="--featured-link-icon-size: var(--fs-p2);" > Explore how it works - @@ -211,7 +211,7 @@ exports[`JS Components/Meganav Default smoke-test 1`] = ` style="--featured-link-icon-size: var(--fs-p3);" > Explore Four Pillars of Dependability - @@ -762,7 +762,7 @@ exports[`JS Components/Meganav Default smoke-test 1`] = ` style="--featured-link-icon-size: var(--fs-p3);" > More from our Blog - @@ -1174,7 +1174,7 @@ exports[`JS Components/Meganav Default smoke-test 1`] = ` style="--featured-link-icon-size: var(--fs-p2);" > Support - @@ -1375,7 +1375,7 @@ exports[`JS Components/Meganav Default smoke-test 1`] = ` style="--featured-link-icon-size: var(--fs-p2);" > Explore how it works - @@ -1517,7 +1517,7 @@ exports[`JS Components/Meganav Default smoke-test 1`] = ` style="--featured-link-icon-size: var(--fs-p3);" > Explore Four Pillars of Dependability - @@ -2093,7 +2093,7 @@ exports[`JS Components/Meganav Default smoke-test 1`] = ` style="--featured-link-icon-size: var(--fs-p3);" > More from our Blog - diff --git a/src/core/Pricing/PricingCards.stories.tsx b/src/core/Pricing/PricingCards.stories.tsx new file mode 100644 index 000000000..5529e11ff --- /dev/null +++ b/src/core/Pricing/PricingCards.stories.tsx @@ -0,0 +1,53 @@ +import React from "react"; +import PricingCards from "./PricingCards"; +import { consumptionData, planData } from "./data"; + +export default { + title: "Features/Pricing Cards", + component: PricingCards, + tags: ["autodocs"], + parameters: { + docs: { + description: { + component: + "A reusable component that forms the basis of two sections of the pricing page. It is used to display pricing plans and consumption pricing. The presence of a `delimiter` prop changes the UX to focus around intermediate icon columns.", + }, + }, + }, +}; + +export const PlansDarkMode = { + render: () => ( +
+ +
+ ), +}; + +export const PlansLightMode = { + render: () => ( +
+ +
+ ), +}; + +export const ConsumptionDarkMode = { + render: () => ( +
+ +
+ ), +}; + +export const ConsumptionLightMode = { + render: () => ( +
+ +
+ ), +}; diff --git a/src/core/Pricing/PricingCards.tsx b/src/core/Pricing/PricingCards.tsx new file mode 100644 index 000000000..c750f52ca --- /dev/null +++ b/src/core/Pricing/PricingCards.tsx @@ -0,0 +1,163 @@ +import React, { Fragment } from "react"; +import type { PricingDataFeature } from "./types"; +import { determineThemeColor } from "../styles/colors/utils"; +import { ColorClass, Theme } from "../styles/colors/types"; +import Icon from "../Icon"; +import FeaturedLink from "../FeaturedLink"; +import { IconName } from "../Icon/types"; + +type PricingCardsProps = { + data: PricingDataFeature[]; + theme?: Theme; + delimiter?: IconName; +}; + +const PricingCards = ({ + data, + theme = "dark", + delimiter, +}: PricingCardsProps) => { + // work out a dynamic theme colouring, using dark theme colouring as the base + const t = (color: ColorClass) => determineThemeColor("dark", theme, color); + + const delimiterColumn = (index: number) => + delimiter && index % 2 === 1 ? ( +
+ +
+ ) : null; + + const gridRules = { + nonDelimited: "grid @[552px]:grid-cols-2 @[1104px]:grid-cols-4", + delimited: "flex flex-col items-center @[920px]:flex-row", + }; + + return ( +
+
+ {data.map(({ title, description, price, cta, sections }, index) => ( + + {delimiterColumn(index)} +
+
+
+
+

+ {title.content} +

+

+ {description.content} +

+
+
+

+ {isNaN(Number(price.amount)) ? "" : "$"} + {price.amount} +

+
{price.content}
+
+ {cta ? ( +
+ + {cta.text} + +
+ ) : null} +
+
+ {sections.map(({ title, items, listItemColors, cta }) => ( +
+

+ {title} +

+
+ {items.map((item, index) => + Array.isArray(item) ? ( +
0 && index % 2 === 0 ? `${t("bg-blue-900")} rounded-md` : ""}`} + > + {item.map((subItem, subIndex) => ( + + {subItem} + + ))} +
+ ) : ( +
+ {listItemColors ? ( + + ) : null} +
+ {item} +
+
+ ), + )} +
+ {cta ? ( +
+
+ ••• +
+ + {cta.text} + +
+ ) : null} +
+ ))} +
+
+
+ {delimiterColumn(index)} +
+ ))} +
+
+ ); +}; + +export default PricingCards; diff --git a/src/core/Pricing/__snapshots__/PricingCards.stories.tsx.snap b/src/core/Pricing/__snapshots__/PricingCards.stories.tsx.snap new file mode 100644 index 000000000..854dd5c55 --- /dev/null +++ b/src/core/Pricing/__snapshots__/PricingCards.stories.tsx.snap @@ -0,0 +1,1541 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Features/Pricing Cards ConsumptionDarkMode smoke-test 1`] = ` +
+
+
+
+
+
+
+

+ Messages +

+

+ Messages contain the data that a client is communicating, such as the contents of a chat message. +

+
+
+

+ $2.5 +

+
+ per million +
+
+
+
+
+

+ Volume discounts +

+
+
+ + Consumption + + + $/million msgs + +
+
+ + First 50 million msgs + + + $2.50 + +
+
+ + Next 450 million msgs + + + $2.25 + +
+
+ + Next 4.5 billion msgs + + + $1.95 + +
+
+ + Next 15 billion msgs + + + $1.65 + +
+
+ + Next 30 billion msgs + + + $1.40 + +
+
+ + Over 50 billion msgs + + + $1.25 + +
+
+
+
+
+
+
+ + + + +
+
+
+
+
+

+ Channels +

+

+ Clients publish and receive messages on channels (also know as topics). We only charge for active channels. +

+
+
+

+ $1 +

+
+ per million mins +
+
+
+
+
+

+ Volume discounts +

+
+
+ + Consumption + + + $/million msgs + +
+
+ + First 10 million msgs + + + $1.00 + +
+
+ + Next 90 million msgs + + + $0.95 + +
+
+ + Next 900 million msgs + + + $0.85 + +
+
+ + Next 4 billion msgs + + + $0.75 + +
+
+ + Next 10 billion msgs + + + $0.65 + +
+
+ + Over 15 billion msgs + + + $0.60 + +
+
+
+
+
+
+
+ + + + +
+
+
+
+
+

+ Connections +

+

+ Clients establish and maintain a connection to the Ably service, typically over WebSockets. We only charge for active connections. +

+
+
+

+ $1 +

+
+ per million mins +
+
+
+
+
+

+ Volume discounts +

+
+
+ + Consumption + + + $/million msgs + +
+
+ + First 10 million msgs + + + $1.00 + +
+
+ + Next 90 million msgs + + + $0.95 + +
+
+ + Next 900 million msgs + + + $0.85 + +
+
+ + Next 4 billion msgs + + + $0.75 + +
+
+ + Next 10 billion msgs + + + $0.65 + +
+
+ + Over 15 billion msgs + + + $0.60 + +
+
+
+
+
+
+
+
+
+`; + +exports[`Features/Pricing Cards ConsumptionLightMode smoke-test 1`] = ` +
+
+
+
+
+
+
+

+ Messages +

+

+ Messages contain the data that a client is communicating, such as the contents of a chat message. +

+
+
+

+ $2.5 +

+
+ per million +
+
+
+
+
+

+ Volume discounts +

+
+
+ + Consumption + + + $/million msgs + +
+
+ + First 50 million msgs + + + $2.50 + +
+
+ + Next 450 million msgs + + + $2.25 + +
+
+ + Next 4.5 billion msgs + + + $1.95 + +
+
+ + Next 15 billion msgs + + + $1.65 + +
+
+ + Next 30 billion msgs + + + $1.40 + +
+
+ + Over 50 billion msgs + + + $1.25 + +
+
+
+
+
+
+
+ + + + +
+
+
+
+
+

+ Channels +

+

+ Clients publish and receive messages on channels (also know as topics). We only charge for active channels. +

+
+
+

+ $1 +

+
+ per million mins +
+
+
+
+
+

+ Volume discounts +

+
+
+ + Consumption + + + $/million msgs + +
+
+ + First 10 million msgs + + + $1.00 + +
+
+ + Next 90 million msgs + + + $0.95 + +
+
+ + Next 900 million msgs + + + $0.85 + +
+
+ + Next 4 billion msgs + + + $0.75 + +
+
+ + Next 10 billion msgs + + + $0.65 + +
+
+ + Over 15 billion msgs + + + $0.60 + +
+
+
+
+
+
+
+ + + + +
+
+
+
+
+

+ Connections +

+

+ Clients establish and maintain a connection to the Ably service, typically over WebSockets. We only charge for active connections. +

+
+
+

+ $1 +

+
+ per million mins +
+
+
+
+
+

+ Volume discounts +

+
+
+ + Consumption + + + $/million msgs + +
+
+ + First 10 million msgs + + + $1.00 + +
+
+ + Next 90 million msgs + + + $0.95 + +
+
+ + Next 900 million msgs + + + $0.85 + +
+
+ + Next 4 billion msgs + + + $0.75 + +
+
+ + Next 10 billion msgs + + + $0.65 + +
+
+ + Over 15 billion msgs + + + $0.60 + +
+
+
+
+
+
+
+
+
+`; + +exports[`Features/Pricing Cards PlansDarkMode smoke-test 1`] = ` +
+
+
+
+
+
+
+

+ Free +

+

+ Build a proof of concept. +

+
+
+

+ $0 +

+
+
+
+ +
+
+
+

+ Capacity +

+
+
+
+ 200 concurrent channels +
+
+
+
+ 200 concurrent connections +
+
+
+
+
+

+ Includes +

+
+
+ + + + +
+ Community & email support (best effort) +
+
+
+ + + + +
+ No commitment +
+
+
+
+ + + See all features + + + + + +
+
+
+
+
+
+
+
+
+

+ Standard +

+

+ Roll-out into production. +

+
+
+

+ $29 +

+
+

+ /month +

+

+ + consumption +

+
+
+ +
+
+
+

+ Capacity +

+
+
+
+ 10k concurrent channels +
+
+
+
+ 10k concurrent connections +
+
+
+
+ 2.5k messages/second +
+
+
+
+
+

+ Includes +

+
+
+ + + + +
+ 1 day email support SLA +
+
+
+ + + + +
+ Uptime SLO +
+
+
+
+ + + See all features + + + + + +
+
+
+
+
+
+
+
+
+

+ Pro +

+

+ Scale with confidence. +

+
+
+

+ $399 +

+
+

+ /month +

+

+ + consumption +

+
+
+ +
+
+
+

+ Capacity +

+
+
+
+ 50k concurrent channels +
+
+
+
+ 50k concurrent connections +
+
+
+
+ 10k messages/second +
+
+
+
+
+

+ Includes +

+
+
+ + + + +
+ 4 hour email support SLA +
+
+
+ + + + +
+ Datadog (lite) +
+
+
+ + + + +
+ Uptime SLO +
+
+
+
+ + + See all features + + + + + +
+
+
+
+
+
+
+
+
+

+ Enterprise +

+

+ Deliver without limits. +

+
+
+

+ Custom +

+
+
+
+ +
+
+
+

+ Capacity +

+
+
+
+ Unlimited concurrent channels +
+
+
+
+ Unlimited concurrent connections +
+
+
+
+ Unlimited messages/second +
+
+
+
+
+

+ Includes +

+
+
+ + + + +
+ 24/7 mission critical support +
+
+
+ + + + +
+ 99.999% uptime SLAs +
+
+
+ + + + +
+ Committed use discounts +
+
+
+ + + + +
+ Datadog +
+
+
+ + + + +
+ CNAME, SSO, & more +
+
+
+
+ + + See all features + + + + + +
+
+
+
+
+
+
+
+`; + +exports[`Features/Pricing Cards PlansLightMode smoke-test 1`] = ` +
+
+
+
+
+
+
+

+ Free +

+

+ Build a proof of concept. +

+
+
+

+ $0 +

+
+
+
+ +
+
+
+

+ Capacity +

+
+
+
+ 200 concurrent channels +
+
+
+
+ 200 concurrent connections +
+
+
+
+
+

+ Includes +

+
+
+ + + + +
+ Community & email support (best effort) +
+
+
+ + + + +
+ No commitment +
+
+
+
+ + + See all features + + + + + +
+
+
+
+
+
+
+
+
+

+ Standard +

+

+ Roll-out into production. +

+
+
+

+ $29 +

+
+

+ /month +

+

+ + consumption +

+
+
+ +
+
+
+

+ Capacity +

+
+
+
+ 10k concurrent channels +
+
+
+
+ 10k concurrent connections +
+
+
+
+ 2.5k messages/second +
+
+
+
+
+

+ Includes +

+
+
+ + + + +
+ 1 day email support SLA +
+
+
+ + + + +
+ Uptime SLO +
+
+
+
+ + + See all features + + + + + +
+
+
+
+
+
+
+
+
+

+ Pro +

+

+ Scale with confidence. +

+
+
+

+ $399 +

+
+

+ /month +

+

+ + consumption +

+
+
+ +
+
+
+

+ Capacity +

+
+
+
+ 50k concurrent channels +
+
+
+
+ 50k concurrent connections +
+
+
+
+ 10k messages/second +
+
+
+
+
+

+ Includes +

+
+
+ + + + +
+ 4 hour email support SLA +
+
+
+ + + + +
+ Datadog (lite) +
+
+
+ + + + +
+ Uptime SLO +
+
+
+
+ + + See all features + + + + + +
+
+
+
+
+
+
+
+
+

+ Enterprise +

+

+ Deliver without limits. +

+
+
+

+ Custom +

+
+
+
+ +
+
+
+

+ Capacity +

+
+
+
+ Unlimited concurrent channels +
+
+
+
+ Unlimited concurrent connections +
+
+
+
+ Unlimited messages/second +
+
+
+
+
+

+ Includes +

+
+
+ + + + +
+ 24/7 mission critical support +
+
+
+ + + + +
+ 99.999% uptime SLAs +
+
+
+ + + + +
+ Committed use discounts +
+
+
+ + + + +
+ Datadog +
+
+
+ + + + +
+ CNAME, SSO, & more +
+
+
+
+ + + See all features + + + + + +
+
+
+
+
+
+
+
+`; diff --git a/src/core/Pricing/data.tsx b/src/core/Pricing/data.tsx new file mode 100644 index 000000000..76ad1236f --- /dev/null +++ b/src/core/Pricing/data.tsx @@ -0,0 +1,269 @@ +import React from "react"; +import { PricingDataFeature } from "./types"; + +export const planData: PricingDataFeature[] = [ + { + title: { + content: "Free", + className: "font-mono text-p3 uppercase font-extrabold tracking-[0.16em]", + color: "text-neutral-700", + }, + description: { + content: "Build a proof of concept.", + className: "ui-text-p1", + color: "text-neutral-500", + }, + price: { amount: 0 }, + cta: { text: "Start for free", url: "#" }, + sections: [ + { + title: "Capacity", + items: ["200 concurrent channels", "200 concurrent connections"], + }, + { + title: "Includes", + items: ["Community & email support (best effort)", "No commitment"], + listItemColors: { + foreground: "text-neutral-600", + background: "text-neutral-1000", + }, + cta: { + text: "See all features", + url: "#", + }, + }, + ], + }, + { + title: { + content: "Standard", + className: "font-mono text-p3 uppercase font-extrabold tracking-[0.16em]", + color: "text-neutral-000", + }, + description: { + content: "Roll-out into production.", + className: "ui-text-p1", + color: "text-neutral-500", + }, + price: { + amount: 29, + content: ( + <> +

+ /month +

+

+ + consumption +

+ + ), + }, + cta: { text: "Get started", url: "#" }, + sections: [ + { + title: "Capacity", + items: [ + "10k concurrent channels", + "10k concurrent connections", + "2.5k messages/second", + ], + }, + { + title: "Includes", + items: ["1 day email support SLA", "Uptime SLO"], + listItemColors: { + foreground: "text-blue-400", + background: "text-blue-800", + }, + cta: { + text: "See all features", + url: "#", + }, + }, + ], + }, + { + title: { + content: "Pro", + className: "font-mono text-p3 uppercase font-extrabold tracking-[0.16em]", + color: "text-neutral-000", + }, + description: { + content: "Scale with confidence.", + className: "ui-text-p1", + color: "text-neutral-500", + }, + price: { + amount: 399, + content: ( + <> +

+ /month +

+

+ + consumption +

+ + ), + }, + cta: { text: "Get started", url: "#" }, + sections: [ + { + title: "Capacity", + items: [ + "50k concurrent channels", + "50k concurrent connections", + "10k messages/second", + ], + }, + { + title: "Includes", + items: ["4 hour email support SLA", "Datadog (lite)", "Uptime SLO"], + listItemColors: { + foreground: "text-blue-400", + background: "text-blue-800", + }, + cta: { + text: "See all features", + url: "#", + }, + }, + ], + }, + + { + title: { + content: "Enterprise", + className: "font-mono text-p3 uppercase font-extrabold tracking-[0.16em]", + color: "text-orange-600", + }, + description: { + content: "Deliver without limits.", + className: "ui-text-p1", + color: "text-neutral-500", + }, + price: { amount: "Custom" }, + cta: { text: "Contact us", url: "#", className: "ui-btn-alt text-white" }, + sections: [ + { + title: "Capacity", + items: [ + "Unlimited concurrent channels", + "Unlimited concurrent connections", + "Unlimited messages/second", + ], + }, + { + title: "Includes", + items: [ + "24/7 mission critical support", + "99.999% uptime SLAs", + "Committed use discounts", + "Datadog", + "CNAME, SSO, & more", + ], + listItemColors: { + foreground: "text-orange-600", + background: "text-orange-1000", + }, + cta: { + text: "See all features", + url: "#", + }, + }, + ], + }, +]; + +export const consumptionData: PricingDataFeature[] = [ + { + title: { + content: "Messages", + className: "ui-text-h3", + color: "text-neutral-000", + }, + description: { + content: + "Messages contain the data that a client is communicating, such as the contents of a chat message.", + className: "ui-text-p3", + color: "text-neutral-700", + }, + price: { amount: 2.5, content: "per million" }, + sections: [ + { + title: "Volume discounts", + items: [ + ["Consumption", "$/million msgs"], + ["First 50 million msgs", "$2.50"], + ["Next 450 million msgs", "$2.25"], + ["Next 4.5 billion msgs", "$1.95"], + ["Next 15 billion msgs", "$1.65"], + ["Next 30 billion msgs", "$1.40"], + ["Over 50 billion msgs", "$1.25"], + ], + }, + ], + }, + { + title: { + content: "Channels", + className: "ui-text-h3", + color: "text-neutral-000", + }, + description: { + content: + "Clients publish and receive messages on channels (also know as topics). We only charge for active channels.", + className: "ui-text-p3", + color: "text-neutral-700", + }, + price: { amount: 1, content: "per million mins" }, + sections: [ + { + title: "Volume discounts", + items: [ + ["Consumption", "$/million msgs"], + ["First 10 million msgs", "$1.00"], + ["Next 90 million msgs", "$0.95"], + ["Next 900 million msgs", "$0.85"], + ["Next 4 billion msgs", "$0.75"], + ["Next 10 billion msgs", "$0.65"], + ["Over 15 billion msgs", "$0.60"], + ], + }, + ], + }, + { + title: { + content: "Connections", + className: "ui-text-h3", + color: "text-neutral-000", + }, + description: { + content: + "Clients establish and maintain a connection to the Ably service, typically over WebSockets. We only charge for active connections.", + className: "ui-text-p3", + color: "text-neutral-700", + }, + price: { amount: 1, content: "per million mins" }, + sections: [ + { + title: "Volume discounts", + items: [ + ["Consumption", "$/million msgs"], + ["First 10 million msgs", "$1.00"], + ["Next 90 million msgs", "$0.95"], + ["Next 900 million msgs", "$0.85"], + ["Next 4 billion msgs", "$0.75"], + ["Next 10 billion msgs", "$0.65"], + ["Over 15 billion msgs", "$0.60"], + ], + }, + ], + }, +]; diff --git a/src/core/Pricing/types.ts b/src/core/Pricing/types.ts new file mode 100644 index 000000000..c0f44185d --- /dev/null +++ b/src/core/Pricing/types.ts @@ -0,0 +1,22 @@ +import { ReactNode } from "react"; +import { ColorClass } from "../styles/colors/types"; + +type PricingDataHeader = { + content: string; + className?: string; + color?: ColorClass; +}; +type PricingDataFeatureCta = { text: string; url: string; className?: string }; + +export type PricingDataFeature = { + title: PricingDataHeader; + description: PricingDataHeader; + price: { amount: number | string; content?: ReactNode }; + cta?: PricingDataFeatureCta; + sections: { + title: string; + items: string[] | string[][]; + listItemColors?: { foreground: ColorClass; background: ColorClass }; + cta?: PricingDataFeatureCta; + }[]; +}; diff --git a/src/core/ProductTile/__snapshots__/ProductTile.stories.tsx.snap b/src/core/ProductTile/__snapshots__/ProductTile.stories.tsx.snap index 0fc3156d3..24770ab7a 100644 --- a/src/core/ProductTile/__snapshots__/ProductTile.stories.tsx.snap +++ b/src/core/ProductTile/__snapshots__/ProductTile.stories.tsx.snap @@ -181,7 +181,7 @@ exports[`JS Components/Product Tile SelectedProductTiles smoke-test 1`] = ` style="--featured-link-icon-size: var(--fs-p2);" > View docs - @@ -235,7 +235,7 @@ exports[`JS Components/Product Tile SelectedProductTiles smoke-test 1`] = ` style="--featured-link-icon-size: var(--fs-p2);" > Explore - @@ -289,7 +289,7 @@ exports[`JS Components/Product Tile SelectedProductTiles smoke-test 1`] = ` style="--featured-link-icon-size: var(--fs-p2);" > Explore - diff --git a/src/core/styles/colors/types.ts b/src/core/styles/colors/types.ts index b563ecd37..29aa95c4f 100644 --- a/src/core/styles/colors/types.ts +++ b/src/core/styles/colors/types.ts @@ -5,11 +5,14 @@ export type ColorName = | (typeof guiColors)[number] | (typeof aliasedColors)[number]; -type ColorClassPrefixes = "bg" | "text"; +type ColorClassVariants = "" | "hover:" | "focus:"; + +type ColorClassPrefixes = "bg" | "text" | "from" | "to"; export type Theme = "light" | "dark"; -export type ColorClass = `${ColorClassPrefixes}-${ColorName}`; +export type ColorClass = + `${ColorClassVariants}${ColorClassPrefixes}-${ColorName}`; const neutralColors = [ "neutral-000", diff --git a/tailwind.config.js b/tailwind.config.js index a887e77d2..957698c1e 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -8,8 +8,11 @@ module.exports = { "w-1/6", { pattern: /^hljs.*/ }, { pattern: /^ui-.*/ }, - { pattern: /^text-.*/ }, - { pattern: /^bg-.*/ }, + { + pattern: + /^(text|bg|from|to)-(neutral|orange|yellow|green|blue|violet|pink)-[\d]{1,2}00.*/, + variants: ["hover", "focus"], + }, ], theme: { screens: { @@ -287,20 +290,15 @@ module.exports = { "widen-0.15": "var(--ls-widen-0_15)", "widen-0.16": "var(--ls-widen-0_16)", }, - borderRadius: { - none: "0", - sm: "0.125rem", - md: "0.1875rem", - lg: "0.5rem", - xl: "0.75rem", - full: "9999px", - DEFAULT: "0.375rem", - }, extend: { backgroundImage: { "gradient-active-orange": "var(--gradient-active-orange)", "gradient-hot-pink": "var(--gradient-hot-pink)", }, + borderRadius: { + md: "0.1875rem", + DEFAULT: "0.375rem", + }, transitionProperty: { input: "background-color, box-shadow", filter: "filter", From 82b80197f6dcc275e00c3ca7e5c0308ec42de8c6 Mon Sep 17 00:00:00 2001 From: Jamie Henson Date: Wed, 18 Sep 2024 15:01:31 +0100 Subject: [PATCH 5/7] fix: coderabbit suggested fixes Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- .storybook/preview.tsx | 11 ------ src/core/Pricing/PricingCards.stories.tsx | 34 ++++++------------- src/core/Pricing/PricingCards.tsx | 5 ++- .../PricingCards.stories.tsx.snap | 4 +-- src/core/styles/colors/types.ts | 7 +++- src/core/styles/colors/utils.ts | 2 +- 6 files changed, 22 insertions(+), 41 deletions(-) diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx index 888b6f220..e40889146 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -8,16 +8,6 @@ import theme, { brandImage, brandImageDark } from "./theme"; import loadIcons from "../src/core/icons"; import { Preview } from "@storybook/react"; -const docsContainer = ({ children, context, ...props }) => { - loadIcons(); - - return ( - - {children} - - ); -}; - initialize({ onUnhandledRequest: "bypass", serviceWorker: { @@ -44,7 +34,6 @@ const preview: Preview = { }, docs: { theme, - container: docsContainer, }, options: { storySort: { diff --git a/src/core/Pricing/PricingCards.stories.tsx b/src/core/Pricing/PricingCards.stories.tsx index 5529e11ff..51a5b3202 100644 --- a/src/core/Pricing/PricingCards.stories.tsx +++ b/src/core/Pricing/PricingCards.stories.tsx @@ -1,5 +1,5 @@ import React from "react"; -import PricingCards from "./PricingCards"; +import PricingCards, { PricingCardsProps } from "./PricingCards"; import { consumptionData, planData } from "./data"; export default { @@ -16,38 +16,26 @@ export default { }, }; +const Template = ({ data, theme = "dark", delimiter }: PricingCardsProps) => ( +
+ +
+); + export const PlansDarkMode = { - render: () => ( -
- -
- ), + render: () =>