Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions .github/workflows/deploy-storybook.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
name: Deploy Storybook

on:
push:
branches: [main]
paths:
- 'src/**'
- '.storybook/**'
- 'package.json'
- '.github/workflows/deploy-storybook.yml'
workflow_dispatch:

jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ github.ref }}
clean: true

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 22

- name: Setup Yarn
run: npm install -g yarn

- name: Install Dependencies
run: yarn install --frozen-lockfile

- name: Build Storybook (includes Stencil build)
run: yarn run storybook-build

- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: 'us-east-1'

- name: Deploy to S3 bucket
run: aws s3 sync ./storybook-static/ s3://${{ secrets.AWS_S3_BUCKET }} --delete --acl public-read

- name: Set proper MIME types and headers for all files
run: |
# JavaScript files with proper CORS
aws s3 cp s3://${{ secrets.AWS_S3_BUCKET }}/ s3://${{ secrets.AWS_S3_BUCKET }}/ --recursive --exclude "*" --include "*.js" --content-type "application/javascript" --metadata-directive REPLACE --acl public-read --cache-control "public, max-age=31536000"
# CSS files
aws s3 cp s3://${{ secrets.AWS_S3_BUCKET }}/ s3://${{ secrets.AWS_S3_BUCKET }}/ --recursive --exclude "*" --include "*.css" --content-type "text/css" --metadata-directive REPLACE --acl public-read --cache-control "public, max-age=31536000"
# JSON files
aws s3 cp s3://${{ secrets.AWS_S3_BUCKET }}/ s3://${{ secrets.AWS_S3_BUCKET }}/ --recursive --exclude "*" --include "*.json" --content-type "application/json" --metadata-directive REPLACE --acl public-read
# HTML files (iframe.html, etc.)
aws s3 cp s3://${{ secrets.AWS_S3_BUCKET }}/ s3://${{ secrets.AWS_S3_BUCKET }}/ --recursive --exclude "*" --include "*.html" --content-type "text/html" --metadata-directive REPLACE --acl public-read --cache-control "no-cache"

- name: No-cache index.html
run: aws s3 cp s3://${{ secrets.AWS_S3_BUCKET }}/index.html s3://${{ secrets.AWS_S3_BUCKET }}/index.html --metadata-directive REPLACE --cache-control max-age=0,no-cache,no-store,must-revalidate --content-type text/html --acl public-read

- name: Slack Notification
uses: rtCamp/action-slack-notify@master
env:
SLACK_ICON_EMOJI: ':books:'
SLACK_USERNAME: mx-sdk-dapp-ui
SLACK_TITLE: 'Storybook'
SLACK_MESSAGE: 'Successfully deployed to: https://${{ secrets.AWS_S3_BUCKET }}'
SLACK_COLOR: 'good'
SLACK_FOOTER: 'Deployed from branch: ${{ github.ref_name }}'
MSG_MINIMAL: false
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
107 changes: 107 additions & 0 deletions .storybook/jsxToHtml.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// Interface representing a Stencil VNode structure
interface VNodeStencil {
$tag$?: string;
$attrs$?: Record<string, unknown> | null;
$children$?: Array<VNodeStencil | VNodeLike | string> | null;
$text$?: string | null;
}

// Interface representing a React-like VNode structure
interface VNodeLike {
type?: string | ((...args: unknown[]) => unknown);
props?: Record<string, unknown> & { children?: Array<VNodeLike | string> | VNodeLike | string };
children?: Array<VNodeLike | string> | VNodeLike | string;
}

// Escapes HTML special characters to prevent XSS attacks
const escapeHtml = (value: string): string =>
value
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');

// Converts attribute object to HTML attribute string
const attrsToString = (attrs: Record<string, unknown> | null | undefined): string => {
if (!attrs) {
return '';
}

return Object.entries(attrs)
.filter(([, v]) => v !== undefined && v !== null && v !== false)
.map(([k, v]) => (v === true ? k : `${k}="${escapeHtml(String(v))}"`))
.join(' ');
};

// Normalizes children to a consistent array format
const normalizeChildren = (children: unknown): Array<VNodeStencil | VNodeLike | string> => {
if (children == null) {
return [];
}

if (Array.isArray(children)) {
return children as Array<VNodeStencil | VNodeLike | string>;
}

return [children as VNodeStencil | VNodeLike | string];
};

// Renders a Stencil VNode to HTML string
const renderStencil = (node: VNodeStencil): string => {
if (node.$text$ != null) {
return escapeHtml(String(node.$text$));
}

const tag = node.$tag$ || 'div';
const attrs = attrsToString(node.$attrs$ || undefined);
const children = (node.$children$ || []).map(renderAny).join('');
const attrsStr = attrs ? ` ${attrs}` : '';

return `<${tag}${attrsStr}>${children}</${tag}>`;
};

// Renders a React-like VNode to HTML string
const renderLike = (node: VNodeLike): string => {
const tag = typeof node.type === 'string' ? node.type : 'div';
const props = node.props || {};
const { children, ...rest } = props as Record<string, unknown> & { children?: unknown };
const attrs = attrsToString(rest);
const childrenArr = normalizeChildren(children ?? node.children);
const childrenStr = childrenArr.map(renderAny).join('');
const attrsStr = attrs ? ` ${attrs}` : '';

return `<${tag}${attrsStr}>${childrenStr}</${tag}>`;
};

// Main export function that converts JSX to HTML string
export const renderJsxToHtml = (node: unknown): string => renderAny(node);

// Renders any node type to HTML string with type detection
const renderAny = (node: unknown): string => {
if (node == null || node === false) {
return '';
}

if (typeof node === 'string' || typeof node === 'number') {
return escapeHtml(String(node));
}

// Heuristics for detecting node types
if (typeof node === 'object') {
const o = node as Record<string, unknown>;

// Check for Stencil VNode
if ('$tag$' in o || '$text$' in o || '$children$' in o) {
return renderStencil(node as VNodeStencil);
}

// Check for React-like VNode
if ('type' in o || 'props' in o || 'children' in o) {
return renderLike(node as VNodeLike);
}
}

// Fallback for unknown node types
return escapeHtml(String(node));
};
10 changes: 9 additions & 1 deletion .storybook/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,16 @@ const config = {
stories: ['../src/**/*.stories.@(js|jsx|ts|tsx)'],
addons: ['@storybook/addon-links', '@storybook/addon-docs'],
framework: {
name: '@stencil/storybook-plugin',
name: '@storybook/html-vite',
},
managerHead: head => `
${head}
<base href="./" />
`,
previewHead: head => `
${head}
<base href="./" />
`,
};

export default config;
14 changes: 8 additions & 6 deletions .storybook/preview.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/** @jsx h */
import { h } from '@stencil/core';
import type { Preview } from '@stencil/storybook-plugin';
import type { Preview } from '@storybook/html-vite';
import { renderJsxToHtml } from './jsxToHtml';

import { defineCustomElements } from '../dist/web-components';

Expand All @@ -11,11 +12,12 @@ import './tailwind.css';
defineCustomElements();

export const decorators: Preview['decorators'] = [
(Story, context) => (
<div data-mvx-theme={`mvx:${context.globals.backgrounds.value}-theme`}>
<Story />
</div>
),
(Story, context) =>
renderJsxToHtml(
<div data-mvx-theme={`mvx:${context.globals.backgrounds.value}-theme`}>
<Story />
</div>,
),
];

export const initialGlobals: Preview['initialGlobals'] = {
Expand Down
1 change: 1 addition & 0 deletions .storybook/public/CNAME
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
sdk-dapp-ui.multiversx.com
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [[0.0.36](https://github.com/multiversx/mx-sdk-dapp-ui/pull/251)] - 2025-10-15

- [Updated icons type](https://github.com/multiversx/mx-sdk-dapp-ui/pull/250)

- [Added the Storybook as a live server deployment](https://github.com/multiversx/mx-sdk-dapp-ui/pull/248)

- [Refactor components in transactions table](https://github.com/multiversx/mx-sdk-dapp-ui/pull/243)

## [[0.0.35](https://github.com/multiversx/mx-sdk-dapp-ui/pull/246)] - 2025-10-10

- [Added passkey provider](https://github.com/multiversx/mx-sdk-dapp-ui/pull/242)
Expand Down
11 changes: 7 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@multiversx/sdk-dapp-ui",
"version": "0.0.35",
"version": "0.0.36",
"description": "A library to hold UI components for a dApp on the MultiversX blockchain",
"author": "MultiversX",
"license": "MIT",
Expand Down Expand Up @@ -84,8 +84,9 @@
"build:dev": "stencil build --dev",
"build": "stencil build --prod && tsc -p tsconfig.utils.json && tsc-alias -p tsconfig.utils.json",
"start": "yarn run build:dev --watch --serve",
"storybook-tailwind": "npx ts-node .storybook/generate-safelist.ts && npx @tailwindcss/cli -i src/global/tailwind.css -o .storybook/tailwind.css --content '.storybook/tailwind-safelist.html' && rm -f .storybook/tailwind-safelist.html",
"storybook": "yarn run storybook-tailwind && storybook dev -p 6006 --no-open"
"storybook-build-tailwind": "npx ts-node .storybook/generate-safelist.ts && npx @tailwindcss/cli -i src/global/tailwind.css -o .storybook/tailwind.css --content '.storybook/tailwind-safelist.html' && rm -f .storybook/tailwind-safelist.html",
"storybook-dev": "yarn run build && yarn run storybook-build-tailwind && storybook dev -p 6006 --no-open",
"storybook-build": "yarn run build && yarn run storybook-build-tailwind && storybook build"
},
"dependencies": {
"@stencil/core": "^4.36.2",
Expand All @@ -103,6 +104,8 @@
"@stencil/storybook-plugin": "^0.4.2",
"@storybook/addon-docs": "9.1.7",
"@storybook/addon-links": "9.1.7",
"@storybook/html": "^9.1.10",
"@storybook/html-vite": "^9.1.10",
"@tailwindcss/cli": "4.0.17",
"@tailwindcss/postcss": "4.1.3",
"@types/jest": "^29.5.14",
Expand All @@ -129,4 +132,4 @@
"typescript": "^5.7.3",
"vite": "^7.0.6"
}
}
}
Loading