From 2282ad199e9ca0f9f83548f99f0a681b218b166d Mon Sep 17 00:00:00 2001 From: Jamie Henson Date: Wed, 13 Mar 2024 11:57:51 +0000 Subject: [PATCH] feat: port existing ably-ui content to SB stories, strip out TW layers where they conflict --- .prettierignore | 2 +- .storybook/application.css | 28 + .storybook/{main.js => main.ts} | 24 +- .storybook/manager.ts | 1 + .storybook/preview.ts | 17 - .storybook/preview.tsx | 48 + .storybook/styles.css | 4 + .storybook/theme.ts | 2 +- global.d.ts | 5 +- package.json | 11 +- postcss.config.js | 13 +- public/mockServiceWorker.js | 303 +++ src/core/Code/Code.stories.tsx | 75 + .../ContactFooter/ContactFooter.stories.tsx | 11 + src/core/ContactFooter/component.css | 16 +- .../CookieMessage/CookieMessage.stories.tsx | 12 + .../CustomerLogos/CustomerLogos.stories.tsx | 43 + .../DropdownMenu/DropdownMenu.stories.tsx | 50 + .../FeaturedLink/FeaturedLink.stories.tsx | 43 + src/core/Flash/Flash.stories.tsx | 37 + src/core/Flash/component.css | 40 +- src/core/Flash/component.jsx | 3 +- src/core/Footer/Footer.stories.tsx | 26 + src/core/Footer/component.css | 46 +- src/core/Footer/component.jsx | 3 +- src/core/Icon/Icon.stories.tsx | 150 ++ src/core/Loader/Loader.stories.tsx | 21 + src/core/Logo/Logo.stories.tsx | 12 + src/core/Meganav/Meganav.stories.tsx | 111 + src/core/Meganav/component.css | 208 +- src/core/Notice/component.css | 10 +- src/core/Slider/Slider.stories.tsx | 45 + src/core/Slider/component.css | 10 +- src/core/Slider/component.jsx | 2 +- src/core/icons.js | 7 + src/core/styles/buttons.css | 244 +- src/core/styles/layout.css | 30 +- src/core/styles/properties.css | 2 + src/core/utils/syntax-highlighter.css | 114 +- src/pages/Buttons.mdx | 121 + src/pages/Chips.mdx | 62 + src/pages/Colour.mdx | 23 + src/pages/Forms.mdx | 173 ++ src/pages/Layout.mdx | 58 + src/{stories => }/pages/Typography.mdx | 20 +- src/pages/utils.ts | 70 + src/stories/pages/Colour.mdx | 156 -- src/stories/pages/Forms.mdx | 145 -- src/stories/pages/Layout.mdx | 60 - tailwind.config.js | 1 - tsconfig.json | 2 +- yarn.lock | 1996 +++++++++++------ 52 files changed, 3293 insertions(+), 1423 deletions(-) create mode 100644 .storybook/application.css rename .storybook/{main.js => main.ts} (97%) delete mode 100644 .storybook/preview.ts create mode 100644 .storybook/preview.tsx create mode 100644 public/mockServiceWorker.js create mode 100644 src/core/Code/Code.stories.tsx create mode 100644 src/core/ContactFooter/ContactFooter.stories.tsx create mode 100644 src/core/CookieMessage/CookieMessage.stories.tsx create mode 100644 src/core/CustomerLogos/CustomerLogos.stories.tsx create mode 100644 src/core/DropdownMenu/DropdownMenu.stories.tsx create mode 100644 src/core/FeaturedLink/FeaturedLink.stories.tsx create mode 100644 src/core/Flash/Flash.stories.tsx create mode 100644 src/core/Footer/Footer.stories.tsx create mode 100644 src/core/Icon/Icon.stories.tsx create mode 100644 src/core/Loader/Loader.stories.tsx create mode 100644 src/core/Logo/Logo.stories.tsx create mode 100644 src/core/Meganav/Meganav.stories.tsx create mode 100644 src/core/Slider/Slider.stories.tsx create mode 100644 src/core/icons.js create mode 100644 src/pages/Buttons.mdx create mode 100644 src/pages/Chips.mdx create mode 100644 src/pages/Colour.mdx create mode 100644 src/pages/Forms.mdx create mode 100644 src/pages/Layout.mdx rename src/{stories => }/pages/Typography.mdx (94%) create mode 100644 src/pages/utils.ts delete mode 100644 src/stories/pages/Colour.mdx delete mode 100644 src/stories/pages/Forms.mdx delete mode 100644 src/stories/pages/Layout.mdx diff --git a/.prettierignore b/.prettierignore index 809ae3cf3..5863c3042 100644 --- a/.prettierignore +++ b/.prettierignore @@ -3,4 +3,4 @@ vendor ./reset ./preview/public ./preview/tmp -node_modules +node_modules \ No newline at end of file diff --git a/.storybook/application.css b/.storybook/application.css new file mode 100644 index 000000000..c0171b89e --- /dev/null +++ b/.storybook/application.css @@ -0,0 +1,28 @@ +@import url("https://fonts.googleapis.com/css2?family=Manrope:wght@200;300;400;500;600;700;800&display=swap"); + +@layer components { + .pi-checkered-bg { + --size: 0.25rem; + --double: calc(var(--size) * 2); + --bg: #eee; + --fg: #ddd; + background: linear-gradient( + 45deg, + var(--fg) 25%, + transparent 25.1%, + transparent 74.9%, + var(--fg) 75% + ), + linear-gradient( + 45deg, + var(--fg) 25%, + transparent 25.1%, + transparent 74.9%, + var(--fg) 75% + ), + var(--bg); + background-repeat: repeat, repeat; + background-position: 0 0, var(--size) var(--size); + background-size: var(--double) var(--double), var(--double) var(--double); + } +} diff --git a/.storybook/main.js b/.storybook/main.ts similarity index 97% rename from .storybook/main.js rename to .storybook/main.ts index 8d3979fd4..06e73c92d 100644 --- a/.storybook/main.js +++ b/.storybook/main.ts @@ -3,10 +3,21 @@ const path = require("path"); /** @type { import('@storybook/react-webpack5').StorybookConfig } */ const config = { stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"], + staticDirs: ["../public"], + framework: { + name: "@storybook/react-webpack5", + options: { + builder: { + useSWC: true, + }, + }, + }, + core: { + disableTelemetry: true, + }, addons: [ "@storybook/addon-links", "@storybook/addon-essentials", - "@storybook/addon-onboarding", "@storybook/addon-interactions", "@storybook/addon-styling-webpack", { @@ -42,19 +53,8 @@ const config = { }, }, ], - framework: { - name: "@storybook/react-webpack5", - options: { - builder: { - useSWC: true, - }, - }, - }, docs: { autodocs: "tag", }, - core: { - disableTelemetry: true, - }, }; export default config; diff --git a/.storybook/manager.ts b/.storybook/manager.ts index 8020f2454..822a0514e 100644 --- a/.storybook/manager.ts +++ b/.storybook/manager.ts @@ -1,5 +1,6 @@ import { addons } from "@storybook/manager-api"; import theme from "./theme"; +import "./application.css"; addons.setConfig({ theme, diff --git a/.storybook/preview.ts b/.storybook/preview.ts deleted file mode 100644 index 162031454..000000000 --- a/.storybook/preview.ts +++ /dev/null @@ -1,17 +0,0 @@ -import "./styles.css"; - -/** @type { import('@storybook/react').Preview } */ - -const preview = { - parameters: { - actions: { argTypesRegex: "^on[A-Z].*" }, - controls: { - matchers: { - color: /(background|color)$/i, - date: /Date$/i, - }, - }, - }, -}; - -export default preview; diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx new file mode 100644 index 000000000..1be4fe723 --- /dev/null +++ b/.storybook/preview.tsx @@ -0,0 +1,48 @@ +import React from "react"; +import { DocsContainer } from "@storybook/blocks"; +import { rest } from "msw"; +import { initialize, mswDecorator } from "msw-storybook-addon"; + +import "./styles.css"; +import theme from "./theme"; +import "../src/core/utils/syntax-highlighter"; +import loadIcons from "../src/core/icons"; + +const docsContainer = ({ children, context, ...props }) => { + loadIcons(); + + return ( + + {children} + + ); +}; + +initialize({ + onUnhandledRequest: "bypass", +}); + +const preview = { + parameters: { + actions: { argTypesRegex: "^on[A-Z].*" }, + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/i, + }, + }, + docs: { + theme, + container: docsContainer, + }, + }, + decorators: [ + mswDecorator, + (Story) => { + loadIcons(); + return Story(); + }, + ], +}; + +export default preview; diff --git a/.storybook/styles.css b/.storybook/styles.css index 54c2dfe4b..eae27a75d 100644 --- a/.storybook/styles.css +++ b/.storybook/styles.css @@ -7,3 +7,7 @@ @import "../reset/styles.css"; @import "../core/styles.css"; + +@import "./application.css"; + +@import "../src/core/utils/syntax-highlighter.css"; diff --git a/.storybook/theme.ts b/.storybook/theme.ts index 5e0a0b1e9..b745e56db 100644 --- a/.storybook/theme.ts +++ b/.storybook/theme.ts @@ -6,7 +6,7 @@ const brandImage = export default create({ base: "light", brandTitle: "Ably UI", - brandUrl: "https://ably.com", brandImage, brandTarget: "_self", + fontBase: '"Manrope", "Open Sans", sans-serif', }); diff --git a/global.d.ts b/global.d.ts index 08f5f4f61..b24459644 100644 --- a/global.d.ts +++ b/global.d.ts @@ -1,2 +1,5 @@ declare module "*.png"; -declare module "*.svg"; +declare module "*.svg" { + const content: string; + export default content; +} diff --git a/package.json b/package.json index de935ab89..99af82f60 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,6 @@ "@storybook/addon-essentials": "^7.6.4", "@storybook/addon-interactions": "^7.6.4", "@storybook/addon-links": "^7.6.4", - "@storybook/addon-onboarding": "^1.0.10", "@storybook/addon-styling-webpack": "^0.0.5", "@storybook/blocks": "^7.6.4", "@storybook/react": "^7.6.4", @@ -44,6 +43,8 @@ "extra-watch-webpack-plugin": "^1.0.3", "find-imports": "^1.1.0", "mini-css-extract-plugin": "^1.2.1", + "msw": "1.3.2", + "msw-storybook-addon": "^1.10.0", "null-loader": "^4.0.1", "postcss": "^8.1.10", "postcss-calc": "^7.0.5", @@ -55,6 +56,7 @@ "style-loader": "^3.3.3", "svg-spritemap-webpack-plugin": "^3.7.1", "tailwindcss": "^3.3.6", + "vite": "^4.5.2", "webpack": "^5.3.2", "webpack-cli": "^4.2.0", "yargs": "^16.2.0" @@ -102,5 +104,8 @@ "react", "view-components" ], - "author": "Ably Real-time Ltd " -} + "author": "Ably Real-time Ltd ", + "msw": { + "workerDirectory": "public" + } +} \ No newline at end of file diff --git a/postcss.config.js b/postcss.config.js index dd8ad4d54..4e85a41ea 100644 --- a/postcss.config.js +++ b/postcss.config.js @@ -1,8 +1,7 @@ -module.exports = { - plugins: [ - "postcss-import", - "postcss-custom-properties", - "postcss-calc", - "autoprefixer", - ], +export default { + plugins: { + "postcss-import": {}, + tailwindcss: {}, + autoprefixer: {}, + }, }; diff --git a/public/mockServiceWorker.js b/public/mockServiceWorker.js new file mode 100644 index 000000000..51d85eeeb --- /dev/null +++ b/public/mockServiceWorker.js @@ -0,0 +1,303 @@ +/* eslint-disable */ +/* tslint:disable */ + +/** + * Mock Service Worker (1.3.2). + * @see https://github.com/mswjs/msw + * - Please do NOT modify this file. + * - Please do NOT serve this file on production. + */ + +const INTEGRITY_CHECKSUM = '3d6b9f06410d179a7f7404d4bf4c3c70' +const activeClientIds = new Set() + +self.addEventListener('install', function () { + self.skipWaiting() +}) + +self.addEventListener('activate', function (event) { + event.waitUntil(self.clients.claim()) +}) + +self.addEventListener('message', async function (event) { + const clientId = event.source.id + + if (!clientId || !self.clients) { + return + } + + const client = await self.clients.get(clientId) + + if (!client) { + return + } + + const allClients = await self.clients.matchAll({ + type: 'window', + }) + + switch (event.data) { + case 'KEEPALIVE_REQUEST': { + sendToClient(client, { + type: 'KEEPALIVE_RESPONSE', + }) + break + } + + case 'INTEGRITY_CHECK_REQUEST': { + sendToClient(client, { + type: 'INTEGRITY_CHECK_RESPONSE', + payload: INTEGRITY_CHECKSUM, + }) + break + } + + case 'MOCK_ACTIVATE': { + activeClientIds.add(clientId) + + sendToClient(client, { + type: 'MOCKING_ENABLED', + payload: true, + }) + break + } + + case 'MOCK_DEACTIVATE': { + activeClientIds.delete(clientId) + break + } + + case 'CLIENT_CLOSED': { + activeClientIds.delete(clientId) + + const remainingClients = allClients.filter((client) => { + return client.id !== clientId + }) + + // Unregister itself when there are no more clients + if (remainingClients.length === 0) { + self.registration.unregister() + } + + break + } + } +}) + +self.addEventListener('fetch', function (event) { + const { request } = event + const accept = request.headers.get('accept') || '' + + // Bypass server-sent events. + if (accept.includes('text/event-stream')) { + return + } + + // Bypass navigation requests. + if (request.mode === 'navigate') { + return + } + + // Opening the DevTools triggers the "only-if-cached" request + // that cannot be handled by the worker. Bypass such requests. + if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') { + return + } + + // Bypass all requests when there are no active clients. + // Prevents the self-unregistered worked from handling requests + // after it's been deleted (still remains active until the next reload). + if (activeClientIds.size === 0) { + return + } + + // Generate unique request ID. + const requestId = Math.random().toString(16).slice(2) + + event.respondWith( + handleRequest(event, requestId).catch((error) => { + if (error.name === 'NetworkError') { + console.warn( + '[MSW] Successfully emulated a network error for the "%s %s" request.', + request.method, + request.url, + ) + return + } + + // At this point, any exception indicates an issue with the original request/response. + console.error( + `\ +[MSW] Caught an exception from the "%s %s" request (%s). This is probably not a problem with Mock Service Worker. There is likely an additional logging output above.`, + request.method, + request.url, + `${error.name}: ${error.message}`, + ) + }), + ) +}) + +async function handleRequest(event, requestId) { + const client = await resolveMainClient(event) + const response = await getResponse(event, client, requestId) + + // Send back the response clone for the "response:*" life-cycle events. + // Ensure MSW is active and ready to handle the message, otherwise + // this message will pend indefinitely. + if (client && activeClientIds.has(client.id)) { + ;(async function () { + const clonedResponse = response.clone() + sendToClient(client, { + type: 'RESPONSE', + payload: { + requestId, + type: clonedResponse.type, + ok: clonedResponse.ok, + status: clonedResponse.status, + statusText: clonedResponse.statusText, + body: + clonedResponse.body === null ? null : await clonedResponse.text(), + headers: Object.fromEntries(clonedResponse.headers.entries()), + redirected: clonedResponse.redirected, + }, + }) + })() + } + + return response +} + +// Resolve the main client for the given event. +// Client that issues a request doesn't necessarily equal the client +// that registered the worker. It's with the latter the worker should +// communicate with during the response resolving phase. +async function resolveMainClient(event) { + const client = await self.clients.get(event.clientId) + + if (client?.frameType === 'top-level') { + return client + } + + const allClients = await self.clients.matchAll({ + type: 'window', + }) + + return allClients + .filter((client) => { + // Get only those clients that are currently visible. + return client.visibilityState === 'visible' + }) + .find((client) => { + // Find the client ID that's recorded in the + // set of clients that have registered the worker. + return activeClientIds.has(client.id) + }) +} + +async function getResponse(event, client, requestId) { + const { request } = event + const clonedRequest = request.clone() + + function passthrough() { + // Clone the request because it might've been already used + // (i.e. its body has been read and sent to the client). + const headers = Object.fromEntries(clonedRequest.headers.entries()) + + // Remove MSW-specific request headers so the bypassed requests + // comply with the server's CORS preflight check. + // Operate with the headers as an object because request "Headers" + // are immutable. + delete headers['x-msw-bypass'] + + return fetch(clonedRequest, { headers }) + } + + // Bypass mocking when the client is not active. + if (!client) { + return passthrough() + } + + // Bypass initial page load requests (i.e. static assets). + // The absence of the immediate/parent client in the map of the active clients + // means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet + // and is not ready to handle requests. + if (!activeClientIds.has(client.id)) { + return passthrough() + } + + // Bypass requests with the explicit bypass header. + // Such requests can be issued by "ctx.fetch()". + if (request.headers.get('x-msw-bypass') === 'true') { + return passthrough() + } + + // Notify the client that a request has been intercepted. + const clientMessage = await sendToClient(client, { + type: 'REQUEST', + payload: { + id: requestId, + url: request.url, + method: request.method, + headers: Object.fromEntries(request.headers.entries()), + cache: request.cache, + mode: request.mode, + credentials: request.credentials, + destination: request.destination, + integrity: request.integrity, + redirect: request.redirect, + referrer: request.referrer, + referrerPolicy: request.referrerPolicy, + body: await request.text(), + bodyUsed: request.bodyUsed, + keepalive: request.keepalive, + }, + }) + + switch (clientMessage.type) { + case 'MOCK_RESPONSE': { + return respondWithMock(clientMessage.data) + } + + case 'MOCK_NOT_FOUND': { + return passthrough() + } + + case 'NETWORK_ERROR': { + const { name, message } = clientMessage.data + const networkError = new Error(message) + networkError.name = name + + // Rejecting a "respondWith" promise emulates a network error. + throw networkError + } + } + + return passthrough() +} + +function sendToClient(client, message) { + return new Promise((resolve, reject) => { + const channel = new MessageChannel() + + channel.port1.onmessage = (event) => { + if (event.data && event.data.error) { + return reject(event.data.error) + } + + resolve(event.data) + } + + client.postMessage(message, [channel.port2]) + }) +} + +function sleep(timeMs) { + return new Promise((resolve) => { + setTimeout(resolve, timeMs) + }) +} + +async function respondWithMock(response) { + await sleep(response.delay) + return new Response(response.body, response) +} diff --git a/src/core/Code/Code.stories.tsx b/src/core/Code/Code.stories.tsx new file mode 100644 index 000000000..e23c2b948 --- /dev/null +++ b/src/core/Code/Code.stories.tsx @@ -0,0 +1,75 @@ +import type { StoryObj } from "@storybook/react"; + +import Code from "./component.jsx"; + +type Story = StoryObj; + +export default { + title: "Components/Code", + component: Code, + tags: ["autodocs"], +}; + +export const Javascript: Story = { + args: { + language: "javascript", + snippet: `var ably = new Ably.Realtime('1WChTA.mc0Biw:kNfiYG4KiPgmHHgH'); +var channel = ably.channels.get('web-pal'); + +// Subscribe to messages on channel +channel.subscribe('greeting', function(message) { + alert(message.data); +});`, + }, +}; + +export const Swift: Story = { + args: { + language: "swift", + snippet: `let ably = ARTRealtime(key: "1WChTA.mc0Biw:kNfiYG4KiPgmHHgH") +let channel = ably.channels.get("web-pal") + +// Subscribe to messages on channel +channel.subscribe("greeting") { message in + print("\\(message.data)") +}`, + }, +}; + +export const Java: Story = { + args: { + language: "java", + snippet: `AblyRealtime ably = new AblyRealtime("1WChTA.mc0Biw:kNfiYG4KiPgmHHgH"); +Channel channel = ably.channels.get("web-pal"); + +/* Subscribe to messages on channel */ + +MessageListener listener; +listener = new MessageListener() { + @Override + public void onMessage(Message message) { + System.out.print(message.data); + }; +}; +channel.subscribe("greeting", listener);`, + }, +}; + +export const Kotlin: Story = { + args: { + language: "kotlin", + snippet: `var ably = new Ably.Realtime('1WChTA.mc0Biw:kNfiYG4KiPgmHHgH'); +val exampleConstraints = DefaultResolutionConstraints( + DefaultResolutionSet( // this constructor provides one Resolution for all states + Resolution( + accuracy = Accuracy.BALANCED, + desiredInterval = 1000L, + minimumDisplacement = 1.0 + ) + ), + proximityThreshold = DefaultProximity(spatial = 1.0), + batteryLevelThreshold = 10.0f, + lowBatteryMultiplier = 2.0f +)`, + }, +}; diff --git a/src/core/ContactFooter/ContactFooter.stories.tsx b/src/core/ContactFooter/ContactFooter.stories.tsx new file mode 100644 index 000000000..233f7f56f --- /dev/null +++ b/src/core/ContactFooter/ContactFooter.stories.tsx @@ -0,0 +1,11 @@ +import ContactFooter from "./component.jsx"; + +export default { + title: "Components/Contact Footer", + component: ContactFooter, + parameters: { + layout: "fullscreen", + }, +}; + +export const Default = {}; diff --git a/src/core/ContactFooter/component.css b/src/core/ContactFooter/component.css index e55d9cf9b..1976d1fb3 100644 --- a/src/core/ContactFooter/component.css +++ b/src/core/ContactFooter/component.css @@ -1,11 +1,9 @@ -@layer components { - .ui-contact-footer { - background-size: 100% 100%; - background-position: right center; - @apply w-full bg-gradient-active-orange; - } +.ui-contact-footer { + background-size: 100% 100%; + background-position: right center; + @apply w-full bg-gradient-active-orange; +} - .ui-contact-footer-box { - @apply p-24 sm:p-32 xl:p-40 bg-white flex flex-col justify-between rounded-sm; - } +.ui-contact-footer-box { + @apply p-24 sm:p-32 xl:p-40 bg-white flex flex-col justify-between rounded-sm; } diff --git a/src/core/CookieMessage/CookieMessage.stories.tsx b/src/core/CookieMessage/CookieMessage.stories.tsx new file mode 100644 index 000000000..72b141410 --- /dev/null +++ b/src/core/CookieMessage/CookieMessage.stories.tsx @@ -0,0 +1,12 @@ +import CookieMessage from "./component.jsx"; + +export default { + title: "Components/Cookie Message", + component: CookieMessage, + args: { + cookieId: "cookie-namespace", + noticeId: "cookie-message", + }, +}; + +export const Default = {}; diff --git a/src/core/CustomerLogos/CustomerLogos.stories.tsx b/src/core/CustomerLogos/CustomerLogos.stories.tsx new file mode 100644 index 000000000..ec688c0f6 --- /dev/null +++ b/src/core/CustomerLogos/CustomerLogos.stories.tsx @@ -0,0 +1,43 @@ +import CustomerLogos from "../../core/CustomerLogos/component.jsx"; + +import hubspot from "../images/cust-logo-hubspot-mono-pos.svg"; +import webflow from "../images/cust-logo-webflow-col-pos.svg"; +import mentimeter from "../images/cust-logo-mentimeter-mono-pos.svg"; +import toyota from "../images/cust-logo-toyota-mono-pos.svg"; +import split from "../images/cust-logo-split-mono-pos.svg"; +import australian from "../images/cust-logo-ausopen-mono-pos.svg"; + +export default { + title: "Components/Customer Logos", + component: CustomerLogos, + args: { + companies: [ + { + label: "Hubspot", + logo: hubspot, + }, + { + label: "Webflow", + logo: webflow, + }, + { + label: "Mentimeter", + logo: mentimeter, + }, + { + label: "Toyota", + logo: toyota, + }, + { + label: "Split", + logo: split, + }, + { + label: "Australian Open", + logo: australian, + }, + ], + }, +}; + +export const Default = {}; diff --git a/src/core/DropdownMenu/DropdownMenu.stories.tsx b/src/core/DropdownMenu/DropdownMenu.stories.tsx new file mode 100644 index 000000000..54f40e9e0 --- /dev/null +++ b/src/core/DropdownMenu/DropdownMenu.stories.tsx @@ -0,0 +1,50 @@ +import React from "react"; +import Icon from "../Icon/component.jsx"; +import DropdownMenu from "./component.jsx"; +import { StoryObj } from "@storybook/react"; + +export default { + title: "Components/Dropdown Menu", + component: DropdownMenu, +}; + +type Story = StoryObj; + +export const Default: Story = { + render: () => ( + + + Dropdown Menu Trigger + + + +

I am a child! 🐣

+
+ + +

+ Using plain HTML + +

+
+
+
+ ), +}; diff --git a/src/core/FeaturedLink/FeaturedLink.stories.tsx b/src/core/FeaturedLink/FeaturedLink.stories.tsx new file mode 100644 index 000000000..db75f0b94 --- /dev/null +++ b/src/core/FeaturedLink/FeaturedLink.stories.tsx @@ -0,0 +1,43 @@ +import FeaturedLink from "./component.jsx"; + +export default { + title: "Components/Featured Link", + component: FeaturedLink, + parameters: { + layout: "centered", + }, + tags: ["autodocs"], + args: { + url: "#", + children: "Featured link", + }, +}; + +export const Default = { + args: {}, +}; + +export const Reverse = { + args: { + reverse: true, + }, +}; + +export const Large = { + args: { + textSize: "text-p1", + }, +}; + +export const Small = { + args: { + textSize: "text-p3", + }, +}; + +export const Pink = { + args: { + iconColor: "text-pink-500", + additionalCSS: "text-pink-800", + }, +}; diff --git a/src/core/Flash/Flash.stories.tsx b/src/core/Flash/Flash.stories.tsx new file mode 100644 index 000000000..7e3311126 --- /dev/null +++ b/src/core/Flash/Flash.stories.tsx @@ -0,0 +1,37 @@ +import React from "react"; +import Flash, { reducerFlashes } from "./component.jsx"; + +import { + attachStoreToWindow, + createRemoteDataStore, +} from "../remote-data-store.js"; +import { reducerBlogPosts } from "../remote-blogs-posts.js"; +import { reducerSessionData } from "../remote-session-data.js"; + +export default { + title: "Components/Flash", + component: Flash, + args: { + flashes: [ + ["success", "Congratulations! You've won the Oscar"], + ["notice", "This is a notice"], + ["error", "This is an error, very bad"], + ["alert", "This is an alert"], + ["info", "Some useful information, you are welcome"], + ], + }, +}; + +export const Default = { + render: (args) => { + const store = createRemoteDataStore({ + ...reducerBlogPosts, + ...reducerSessionData, + ...reducerFlashes, + }); + + attachStoreToWindow(store); + + return ; + }, +}; diff --git a/src/core/Flash/component.css b/src/core/Flash/component.css index c200fbe8a..4ed245735 100644 --- a/src/core/Flash/component.css +++ b/src/core/Flash/component.css @@ -1,25 +1,23 @@ -@layer components { - .ui-flash { - @apply w-full fixed; - top: 5.5rem; - z-index: calc(var(--stacking-context-page-meganav) - 10); - transition: margin-top 200ms; - } +.ui-flash { + @apply w-full fixed; + top: 5.5rem; + z-index: calc(var(--stacking-context-page-meganav) - 10); + transition: margin-top 200ms; +} - .ui-flash-message { - @apply font-sans font-light antialiased max-w-screen-xl mx-auto mt-8 opacity-0 relative; - transition: opacity 200ms, transform 200ms, height 200ms 200ms, - margin-top 200ms 200ms; - transform: translateY(-200%) rotateX(-90deg); - } +.ui-flash-message { + @apply font-sans font-light antialiased max-w-screen-xl mx-auto mt-8 opacity-0 relative; + transition: opacity 200ms, transform 200ms, height 200ms 200ms, + margin-top 200ms 200ms; + transform: translateY(-200%) rotateX(-90deg); +} - /* dynamic content inside flash, can't add classes */ - .ui-flash-text a { - @apply underline; - } +/* dynamic content inside flash, can't add classes */ +.ui-flash-text a { + @apply underline; +} - .ui-flash-message-enter { - @apply opacity-100; - transform: translateY(0) rotateX(0); - } +.ui-flash-message-enter { + @apply opacity-100; + transform: translateY(0) rotateX(0); } diff --git a/src/core/Flash/component.jsx b/src/core/Flash/component.jsx index 3266eb415..91221702a 100644 --- a/src/core/Flash/component.jsx +++ b/src/core/Flash/component.jsx @@ -3,9 +3,10 @@ import DOMPurify from "dompurify"; import T from "prop-types"; import { nanoid } from "nanoid/non-secure"; -import { getRemoteDataStore } from "../remote-data-store"; +import { getRemoteDataStore } from "../remote-data-store.js"; import ConnectStateWrapper from "../ConnectStateWrapper/component.jsx"; import Icon from "../Icon/component.jsx"; +import "./component.css"; const REDUCER_KEY = "flashes"; const FLASH_DATA_ID = "ui-flashes"; diff --git a/src/core/Footer/Footer.stories.tsx b/src/core/Footer/Footer.stories.tsx new file mode 100644 index 000000000..42dc1b994 --- /dev/null +++ b/src/core/Footer/Footer.stories.tsx @@ -0,0 +1,26 @@ +import Footer from "./component.jsx"; + +import ablyStack from "../../core/images/ably-stack.svg"; +import highestPerformer from "../../core/images/high-performer-2023.svg"; +import bestSupport from "../../core/images/best-support-2023.svg"; +import fastestImplementation from "../../core/images/fastest-implementation-2023.svg"; +import highestUserAdoption from "../../core/images/highest-user-adoption-2023.svg"; + +export default { + title: "Components/Footer", + component: Footer, + parameters: { + layout: "fullscreen", + }, + args: { + paths: { + ablyStack, + highestPerformer, + bestSupport, + fastestImplementation, + highestUserAdoption, + }, + }, +}; + +export const Default = {}; diff --git a/src/core/Footer/component.css b/src/core/Footer/component.css index a4c98f3a2..f4473ce93 100644 --- a/src/core/Footer/component.css +++ b/src/core/Footer/component.css @@ -1,33 +1,31 @@ -@layer components { - .ui-footer-col-title { - @apply font-mono text-overline2 p-menu-row-title font-medium uppercase tracking-widen-0.16; - } +.ui-footer-col-title { + @apply font-mono text-overline2 p-menu-row-title font-medium uppercase tracking-widen-0.16; +} - .ui-footer-menu-row-link { - @apply text-menu3 text-cool-black font-sans font-medium hover:text-gui-hover block; - } +.ui-footer-menu-row-link { + @apply text-menu3 text-cool-black font-sans font-medium hover:text-gui-hover block; +} - .ui-footer-link { - @apply text-gui-default hover:text-gui-hover text-menu3 font-sans font-medium; - } +.ui-footer-link { + @apply text-gui-default hover:text-gui-hover text-menu3 font-sans font-medium; +} - .ui-footer-compliance-text { - font-size: 12px; - } +.ui-footer-compliance-text { + font-size: 12px; +} - .ui-footer-tick-icon { - min-width: 1.5rem; - } +.ui-footer-tick-icon { + min-width: 1.5rem; +} - @media (max-width: 1040px) { - .ui-footer-bottom-links { - @apply pb-40; - } +@media (max-width: 1040px) { + .ui-footer-bottom-links { + @apply pb-40; } +} - @media screen { - .ui-footer-glassdoor { - display: none; - } +@media screen { + .ui-footer-glassdoor { + display: none; } } diff --git a/src/core/Footer/component.jsx b/src/core/Footer/component.jsx index 7cf799360..1f3a061e5 100644 --- a/src/core/Footer/component.jsx +++ b/src/core/Footer/component.jsx @@ -3,6 +3,7 @@ import T from "prop-types"; import Icon from "../Icon/component.jsx"; import _absUrl from "../url-base"; +import "./component.css"; export default function Footer({ paths, urlBase }) { const absUrl = (path) => _absUrl(path, urlBase); @@ -141,7 +142,7 @@ export default function Footer({ paths, urlBase }) {