diff --git a/.gitignore b/.gitignore
index 69b06dde8a..ccb66a9c4e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -91,6 +91,9 @@ sw.*
# Mac OSX
.DS_Store
-
+# Cypress e2e testing
cypress/videos
-cypress/screenshots
\ No newline at end of file
+cypress/screenshots
+
+# Storybook
+storybook-static/
diff --git a/.storybook/main.js b/.storybook/main.js
index 0e8dbfb8d6..9f16362681 100644
--- a/.storybook/main.js
+++ b/.storybook/main.js
@@ -1,66 +1,47 @@
-const { CreateVolumePermissionModifications, Phase1DHGroupNumbersListValue } = require('@aws-sdk/client-ec2');
const path = require('path');
module.exports = {
"stories": [
+ "../stories/**/Welcome.stories.mdx",
"../stories/**/*.stories.mdx",
"../stories/**/*.stories.@(js|jsx|ts|tsx)"
],
"addons": [
"@storybook/addon-links",
"@storybook/addon-essentials",
- "@storybook/addon-a11y"
+ "@storybook/addon-a11y",
+ "storybook-dark-mode"
],
webpackFinal: async (config, { configType }) => {
const baseFolder = path.resolve(__dirname, '..');
- const assets = path.resolve(__dirname, '..', 'assets');
const sassLoader = {
loader: 'sass-loader',
options: {
- // prependData: `@import '${assets}/styles/app.scss'; @import '$a`,
prependData: `@import '~assets/styles/app.scss'; @import '~stories/global.scss'; `,
- sassOptions: {
- // importer: (url, resourcePath) => {
- // console.log('>>>>>> ' + url);
- // return null;
- // }
- }
},
}
- // Make whatever fine-grained changes you need
config.module.rules.push({
test: /\.scss$/,
use: ['style-loader', 'css-loader', sassLoader],
include: path.resolve(__dirname, '../'),
});
+ config.module.rules.unshift({
+ test: /\.ya?ml$/i,
+ loader: 'js-yaml-loader',
+ options: { name: '[path][name].[ext]' },
+ });
+
// Root path
- config.resolve.alias['~'] = path.dirname(path.resolve(__dirname));
- config.resolve.alias['@'] = path.dirname(path.resolve(__dirname));
+ config.resolve.alias['~'] = baseFolder;
+ config.resolve.alias['@'] = baseFolder;
// Cheat for importing ~assets
config.resolve.modules.push(baseFolder);
- // console.log(config);
-
- // config.resolve.roots = [path.dirname(path.resolve(__dirname))];
-
- // TODO: To fix xterm and a couple of others
- // config.resolve.plugins.push({
- // apply: (config, resolver) => {
- // config.plugin('module', function (init, callback) {
- // console.log(init);
-
- // return resolver.doResolve()
- // callback(null);
- // });
- // }
- // });
-
- // Return the altered config
return config;
},
}
\ No newline at end of file
diff --git a/.storybook/manager-head.html b/.storybook/manager-head.html
new file mode 100644
index 0000000000..e8233452e2
--- /dev/null
+++ b/.storybook/manager-head.html
@@ -0,0 +1,2 @@
+
+
diff --git a/.storybook/manager.js b/.storybook/manager.js
index f0950ee86d..3363ff5828 100644
--- a/.storybook/manager.js
+++ b/.storybook/manager.js
@@ -1,27 +1,39 @@
-// .storybook/manager.js
-
import { addons } from '@storybook/addons';
import rancherTheme from './theme';
+import installShortcut from './theme-shortcut';
addons.setConfig({
panelPosition: 'right',
theme: rancherTheme
});
+const loader = window.onload;
+window.onload = () => {
+ const ch = addons.getChannel();
+ ch.on('shortcut-toggle-dark-mode', (a) => {
+ const toolbar = document.getElementsByClassName('os-content');
+ if (toolbar.length > 2) {
+ const aTags = toolbar[1].getElementsByTagName("button");
+ const searchText = "Change theme to ";
+ let found;
-// let firstLoad = true;
-// addons.register('my-organisation/my-addon', (storybookAPI) => {
-// storybookAPI.on(SET_CURRENT_STORY, ((kind, story) => {
+ for (var i = 0; i < aTags.length; i++) {
+ if (aTags[i].title && aTags[i].title.indexOf(searchText) === 0) {
+ found = aTags[i];
+ break;
+ }
+ }
-// console.log('<<<<<<<<<<<<<<<<<<<<<<<<<');
-// // when you enter a story, if you are just loading storybook up, default to a specific kind/story.
-// if (firstLoad) {
-// firstLoad = false; // make sure to set this flag to false, otherwise you will never be able to look at another story.
+ if (found) {
+ found.click();
+ }
+ }
+ });
-// console.log('selecting story....');
-// console.log(kind);
-// console.log(story);
-// storybookAPI.selectStory('BadgeState', 'Primary');
-// }
-// }));
-// });
+ // Add keyboard shortcut to toggle between dark and light modes
+ installShortcut();
+
+ if (loader) {
+ loader();
+ }
+}
diff --git a/.storybook/package.json b/.storybook/package.json
index 298f9772eb..96debbe45f 100644
--- a/.storybook/package.json
+++ b/.storybook/package.json
@@ -11,11 +11,13 @@
"scripts": {
},
"devDependencies": {
- "@storybook/addon-actions": "^6.3.7",
- "@storybook/addon-essentials": "^6.3.7",
- "@storybook/addon-links": "^6.3.7",
- "@storybook/addon-a11y": "^6.3.7",
- "@storybook/vue": "^6.3.7",
+ "@storybook/addon-actions": "^6.3.8",
+ "@storybook/addon-essentials": "^6.3.8",
+ "@storybook/addon-links": "^6.3.8",
+ "@storybook/addon-a11y": "^6.3.8",
+ "@storybook/vue": "^6.3.8",
+ "storybook-dark-mode": "^1.0.8",
+ "storybook-auto-events": "^0.1.1",
"vue-loader": "^15.9.8"
}
}
diff --git a/.storybook/preview.js b/.storybook/preview.js
index 99f467b0cf..45dce7aa5b 100644
--- a/.storybook/preview.js
+++ b/.storybook/preview.js
@@ -1,11 +1,15 @@
import Vue from 'vue';
import Vuex from 'vuex';
+import { themes } from '@storybook/theming';
+import { get } from '@/utils/object';
+import IntlMessageFormat from 'intl-messageformat';
+import installShortcut from './theme-shortcut';
+import withEvents from 'storybook-auto-events';
-// const i18n = require('../store/i18n');
+const i18nStrings = require('../assets/translations/en-us.yaml');
-// require('../plugins/i18n');
-
-// console.log(i18n);
+// Register custom i18n plugin
+require('../plugins/i18n');
//const store = require('./store');
@@ -14,9 +18,14 @@ Vue.use(Vuex);
const store = new Vuex.Store({
getters: {
'i18n/t': state => (key, args) => {
- console.log('get');
+ const msg = get(i18nStrings, key) || key;
- return key;
+ if ( msg?.includes('{')) {
+ const formatter = new IntlMessageFormat(msg, state.selected);
+ return formatter.format(args);
+ }
+
+ return msg;
}
}
});
@@ -33,40 +42,36 @@ Vue.use(storePlugin);
export const parameters = {
actions: { argTypesRegex: "^on[A-Z].*" },
layout: 'centered',
+ // viewMode: 'docs',
+ // Auto set controls based on the property name
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
+ darkMode: {
+ dark: {
+ ...themes.dark,
+ brandTitle: 'Rancher Storybook',
+ brandImage: 'https://raw.githubusercontent.com/rancher/dashboard/master/assets/images/pl/dark/rancher-logo.svg'
+ },
+ light: {
+ ...themes.normal,
+ brandTitle: 'Rancher Storybook',
+ brandImage: 'https://raw.githubusercontent.com/rancher/dashboard/master/assets/images/pl/rancher-logo.svg'
+ },
+ darkClass: 'theme-dark',
+ lightClass: 'theme-light',
+ stylePreview: true
+ }
}
-// export const globalTypes = {
-// language: {
-// name: "i18n",
-// description: "i18n selector",
-// defaultValue: "en",
-// toolbar: {
-// items: ["en", "fr"],
-// },
-// },
-// lightTheme: {
-// name: "Light",
-// description: "theme selector",
-// defaultValue: "light",
-// toolbar: {
-// showName: true,
-// items: []
-// },
-// },
-// darkTheme: {
-// name: "Dark",
-// description: "theme selector",
-// defaultValue: "dark",
-// toolbar: {
-// showName: true,
-// items: []
-// },
-// },
-// };
+export const decorators = [
+ withEvents
+];
+// Add keyboard shortcut to toggle between dark and light modes
+window.onload = () => {
+ installShortcut();
+}
diff --git a/.storybook/public/storybook.ico b/.storybook/public/storybook.ico
new file mode 100755
index 0000000000..09b2909b89
Binary files /dev/null and b/.storybook/public/storybook.ico differ
diff --git a/.storybook/public/storybook.png b/.storybook/public/storybook.png
new file mode 100755
index 0000000000..c200f598fa
Binary files /dev/null and b/.storybook/public/storybook.png differ
diff --git a/.storybook/theme-shortcut.js b/.storybook/theme-shortcut.js
new file mode 100644
index 0000000000..282b1bf453
--- /dev/null
+++ b/.storybook/theme-shortcut.js
@@ -0,0 +1,15 @@
+import { addons } from '@storybook/addons';
+
+// Insall keyboard shortcut to change theme
+export default function() {
+ const platform = window.navigator.platform;
+ const isMac = platform.indexOf('Mac') === 0;
+
+ document.onkeyup = function(e) {
+ const modifier = isMac ? e.altKey : e.ctrlKey;
+ if (modifier && e.which === 84) {
+ const ch = addons.getChannel();
+ ch.emit('shortcut-toggle-dark-mode');
+ }
+ };
+}
\ No newline at end of file
diff --git a/.storybook/theme.js b/.storybook/theme.js
index 944bec0c57..c3ad4903db 100644
--- a/.storybook/theme.js
+++ b/.storybook/theme.js
@@ -3,6 +3,5 @@ import { create } from '@storybook/theming';
export default create({
base: 'light',
brandTitle: 'Rancher Storybook',
- // brandUrl: '/https://github.com/rancher/dashboard',
brandImage: 'https://raw.githubusercontent.com/rancher/dashboard/master/assets/images/pl/rancher-logo.svg',
});
\ No newline at end of file
diff --git a/assets/styles/base/_basic.scss b/assets/styles/base/_basic.scss
index 34d010b5cf..6bbb9f8d1a 100644
--- a/assets/styles/base/_basic.scss
+++ b/assets/styles/base/_basic.scss
@@ -77,7 +77,7 @@ TEXTAREA,
BUTTON,
.btn {
&:focus, &.focused {
- outline: none;
+ outline: none;
box-shadow: 0 0 0 var(--outline-width) var(--outline);
background: var(--primary-hover-bg);
}
diff --git a/assets/styles/global/_button.scss b/assets/styles/global/_button.scss
index 19bd9e886d..41fbbf0db8 100644
--- a/assets/styles/global/_button.scss
+++ b/assets/styles/global/_button.scss
@@ -68,7 +68,7 @@ button,
color: var(--primary-text);
}
- &:focus, &._focus {
+ &:focus, &.focused {
background-color: var(--primary-hover-bg);
color: var(--primary-text);
}
diff --git a/components/ConsumptionGauge.vue b/components/ConsumptionGauge.vue
index 0cf1c5fb0c..953265109e 100644
--- a/components/ConsumptionGauge.vue
+++ b/components/ConsumptionGauge.vue
@@ -88,6 +88,7 @@ export default {
{{ resourceName }}
+
{{ t('node.detail.glance.consumptionGauge.used') }} {{ t('node.detail.glance.consumptionGauge.amount', amountTemplateValues) }} / {{ formattedPercentage }}
diff --git a/components/PercentageBar.vue b/components/PercentageBar.vue
index 9cc42a8313..3113ee805d 100644
--- a/components/PercentageBar.vue
+++ b/components/PercentageBar.vue
@@ -25,7 +25,7 @@ export default {
},
/**
- * A value which indicates which direction is better so we can change the color appropriately.
+ * A value which indicates which direction is better so we can change the color appropriately (Valid values: 'LESS' or 'MORE')
*/
preferredDirection: {
type: String,
diff --git a/package.json b/package.json
index e70ebb177e..3ff93f443c 100644
--- a/package.json
+++ b/package.json
@@ -23,10 +23,11 @@
"dev-debug": "node --inspect ./node_modules/.bin/nuxt",
"cy:open": "cypress open",
"e2e:dev": "NODE_ENV=dev START_SERVER_AND_TEST_INSECURE=1 start-server-and-test dev https://localhost:8005 cy:open",
- "storybook": "start-storybook -s ./ -p 6006",
- "build-storybook": "build-storybook",
+ "storybook": "yarn run install-storybook && start-storybook -s .storybook/public,assets -p 6006",
+ "build-storybook": "yarn run install-storybook && build-storybook -s .storybook/public,assets",
"install-storybook": "./scripts/storybook-install",
- "remove-storybook": "./scripts/storybook-install -d"
+ "remove-storybook": "./scripts/storybook-install -d",
+ "remove-storybook-deps": "./scripts/storybook-install -r"
},
"dependencies": {
"@aws-sdk/client-ec2": "^3.1.0",
diff --git a/scripts/storybook-install b/scripts/storybook-install
index 4cea9e65ae..8f09ed7797 100755
--- a/scripts/storybook-install
+++ b/scripts/storybook-install
@@ -13,9 +13,14 @@ const { spawn } = require("child_process");
async function main() {
let remove = false;
- if (process.argv.length > 2 && process.argv[2] === '-d') {
+ let doInstall = true;
+ if (process.argv.length > 2 && (process.argv[2] === '-d' || process.argv[2] === '-r')) {
remove = true;
console.log('Removing storybook from package.json');
+
+ if (process.argv[2] === '-d') {
+ doInstall = false;
+ }
}
const rootFolder = path.resolve(__dirname, '..');
@@ -61,7 +66,7 @@ async function main() {
});
}
- if (didChange) {
+ if (didChange && doInstall) {
console.log('Updating dependencies...');
await yarnInstall();
}
diff --git a/stories/Banner.stories.js b/stories/Banner.stories.js
index faa70f6773..c12439aea4 100644
--- a/stories/Banner.stories.js
+++ b/stories/Banner.stories.js
@@ -19,29 +19,29 @@ export default {
},
icon: {
description: 'Optional icon to show before the label'
- }
+ },
}
};
-const Template = (args, { argTypes}) => ({
+const Template = (args, { argTypes, events }) => ({
components: { Banner },
props: Object.keys(argTypes),
- // props: argTypes,
- template: '
',
+ template: '
',
+ data: () => ({ events }),
});
export const Primary = Template.bind({});
Primary.args = {
label: 'Banner Component - Primary',
closable: false,
- color: 'primary'
+ color: 'primary',
};
export const Info = Template.bind({});
Info.args = {
label: 'Banner Component - Info',
closable: false,
- color: 'info'
+ color: 'info',
};
export const Warning = Template.bind({});
diff --git a/stories/Buttons.stories.mdx b/stories/Buttons.stories.mdx
index 78a1bfc510..13d38683d3 100644
--- a/stories/Buttons.stories.mdx
+++ b/stories/Buttons.stories.mdx
@@ -2,7 +2,7 @@ import { Meta } from '@storybook/addon-docs';
-
+
# Buttons
@@ -19,10 +19,10 @@ e.g.
Use class `role-primary`:
-
Primary Button
-
Primary Button
-
Primary Button (Focus)
-
Primary Button (Hover)
+
Primary Button
+
Primary Button
+
Primary Button (Focus)
+
Primary Button (Hover)
@@ -31,10 +31,24 @@ Use class `role-primary`:
Use class `role-secondary`:
-
Secondary Button
-
Secondary Button
-
Secondary Button (Focus)
-
Secondary Button (Hover)
+
Secondary Button
+
Secondary Button
+
Secondary Button (Focus)
+
Secondary Button (Hover)
+
+
+
+
+
+## Tertiary Button
+
+Use class `role-tertiary`:
+
+
+
Tertiary Button
+
Tertiary Button
+
Tertiary Button (Focus)
+
Tertiary Button (Hover)
diff --git a/stories/FormExample.stories.mdx b/stories/FormExample.stories.mdx
new file mode 100644
index 0000000000..c8226bca31
--- /dev/null
+++ b/stories/FormExample.stories.mdx
@@ -0,0 +1,12 @@
+import { Meta, Canvas, Story } from '@storybook/addon-docs';
+import Banner from '../components/Banner.vue';
+
+
+
+
+
+# Form Example
+
+TODO
+
+
diff --git a/stories/Icons.stories.mdx b/stories/Icons.stories.mdx
new file mode 100644
index 0000000000..7588df0cb7
--- /dev/null
+++ b/stories/Icons.stories.mdx
@@ -0,0 +1,48 @@
+import { Meta } from '@storybook/addon-docs';
+import icons from '~/assets/fonts/icons/selection.json';
+
+
+
+
+
+# Icons
+
+Icons are created using the standard HTML <i> element with the class `icon` and the class name of the icon, as shown below.
+
+e.g.
+
+
<i class="icon icon-show" />
+
+
+
+{icons.icons.map((icon, index) => (
+
+
{function(icon) { const name = icon.properties.name.split(','); return `icon-${name[0]}`}(icon)}
+
+
+))}
+
\ No newline at end of file
diff --git a/stories/Introduction.stories.mdx b/stories/Introduction.stories.mdx
deleted file mode 100644
index 25d845643c..0000000000
--- a/stories/Introduction.stories.mdx
+++ /dev/null
@@ -1,110 +0,0 @@
-import { Meta } from '@storybook/addon-docs';
-
-
-
-
-
-# Welcome to the Rancher Dashboard Storybook
-
diff --git a/stories/ProviderImages.stories.mdx b/stories/ProviderImages.stories.mdx
new file mode 100644
index 0000000000..1100912b60
--- /dev/null
+++ b/stories/ProviderImages.stories.mdx
@@ -0,0 +1,79 @@
+
+import { Meta } from "@storybook/addon-docs";
+import icons from './provider-icons.json';
+
+
+
+
+
+# Provider Images
+
+Images for various providers are included in the `assets/images/providers` folder.
+
+
Full Color Logos
+
+
+{icons.map((icon, index) => (
+
+
{icon}
+
+
+))}
+
+
+
Single Color Logos
+
+
+{icons.map((icon, index) => (
+
+
{icon}
+
+
+))}
+
+
+
Inverted with CSS - Single Color Logos
+
+
+
+{icons.map((icon, index) => (
+
+
{icon}
+
+
+))}
+
diff --git a/stories/README.md b/stories/README.md
index efd5c81a9f..8c9cbdc582 100644
--- a/stories/README.md
+++ b/stories/README.md
@@ -1,6 +1,6 @@
# Storybook
-By default the Storybook dependencies are not included in the main package file.
+By default the Storybook dependencies are not included in the main package file (it adds a lot of weight to the install process).
If you run any of the targets:
@@ -19,16 +19,24 @@ You can remove the Storybook dependencies with:
- yarn remove-storybook
+# Running Storybook
+
+Just run the command:
+
+```
+yarn storybook
+```
+
+You can access Storybook at the URL: http://127.0.0.1:6006
+
## TODO
-- Make 'Introduction' the landing story
-- Add light/dark theme support with keyboard shortcut (Command-T) to toggle
-- Add Icons page (maybe generated from the info in assets/fonts/icons/variables.scss)
+- Port design-system pages to Storybook
- Add Typography page (for headers etc and padding and margin classes)
- Write short developer reference for how to document components and write stories
- Build out stories for components
- Auto-build via CI and publish to Github pages ?
-- Add some content to the Introduction page
+- Add some content to the Welcome page
- Get i18n working
- Get store working (where required by a component)
diff --git a/stories/Welcome.stories.mdx b/stories/Welcome.stories.mdx
new file mode 100644
index 0000000000..547b1d5e21
--- /dev/null
+++ b/stories/Welcome.stories.mdx
@@ -0,0 +1,12 @@
+
+import { Meta } from "@storybook/addon-docs";
+
+
+
+# Rancher Storybook
+
+Welcome to the Rancher Storybook.
+
+This Storybook documents the components and the design system that is used in the Rancher UIs.
+
+> Note that you can toggle between dark and light modes using the moon/sun icon the toolbar or with the keyboard shortcut CMD+T (Mac) or CTRL+T (Windows/Linux).
\ No newline at end of file
diff --git a/stories/components/ConsumptionGauge.stories.js b/stories/components/ConsumptionGauge.stories.js
new file mode 100644
index 0000000000..e6960513b7
--- /dev/null
+++ b/stories/components/ConsumptionGauge.stories.js
@@ -0,0 +1,33 @@
+import ConsumptionGauge from '@/components/ConsumptionGauge';
+
+export default {
+ title: 'Components/ConsumptionGauge',
+ component: ConsumptionGauge,
+
+};
+
+export const Story = (args, { argTypes}) => ({
+ components: { ConsumptionGauge },
+ props: Object.keys(argTypes),
+ methods: {
+ formatter(value) {
+ const valueAsArray = value.toString().split('');
+
+ valueAsArray.splice(1, 0, ',');
+
+ return valueAsArray.join('');
+ }
+ },
+ template: `
+
+
+
`,
+});
+
+Story.story = { name: 'ConsumptionGauge' };
+Story.args = {
+ used: 300,
+ capacity: 789,
+ units: 'GB',
+ resourceName: 'DISK'
+}
diff --git a/stories/components/IconIsDefault.stories.js b/stories/components/IconIsDefault.stories.js
new file mode 100644
index 0000000000..461598d46c
--- /dev/null
+++ b/stories/components/IconIsDefault.stories.js
@@ -0,0 +1,42 @@
+import IconIsDefault from '@/components/formatter/IconIsDefault';
+
+const DefaultColumnState = {
+ name: 'builtIn',
+ value: 'builtIn',
+ formatter: 'IconIsDefault'
+};
+
+const DefaultNotTrue = { builtIn: false };
+const DefaultIsTrue = { builtIn: true };
+
+export default {
+ title: 'Components/Formatters/IconIsDefault',
+ component: IconIsDefault,
+ decorators: [],
+};
+
+export const DefaultStory = () => ({
+ components: { IconIsDefault },
+ data() {
+ return {
+ row: DefaultNotTrue,
+ col: DefaultColumnState
+ };
+ },
+ template: `
`
+});
+
+DefaultStory.story = { name: 'Input Not Default' };
+
+export const IsDefault = () => ({
+ components: { IconIsDefault },
+ data() {
+ return {
+ row: DefaultIsTrue,
+ col: DefaultColumnState
+ };
+ },
+ template: `
`
+});
+
+IsDefault.story = { name: 'Input Is Default' };
diff --git a/stories/components/PercentageBar.stories.js b/stories/components/PercentageBar.stories.js
new file mode 100644
index 0000000000..0a43261d5a
--- /dev/null
+++ b/stories/components/PercentageBar.stories.js
@@ -0,0 +1,37 @@
+import PercentageBar from '@/components/PercentageBar';
+import { DescribeSpotPriceHistoryCommand } from '@aws-sdk/client-ec2';
+
+export default {
+ title: 'Components/PercentageBar',
+ component: PercentageBar,
+ argTypes: {
+ preferredDirection: {
+ control: {
+ type: 'select',
+ options: ['LESS', 'MORE']
+ }
+ }
+ }
+};
+
+export const Story = (args, { argTypes}) => ({
+ components: { PercentageBar },
+ props: Object.keys(argTypes),
+ // props: {
+ // value1: { default: number('First Value', 67) },
+ // value2: { default: number('Second Value', 83) },
+ // value3: { default: number('Third Value', 97) },
+ // },
+ template: `
+
`,
+});
+
+Story.story = { name: 'PercentageBar' };
+Story.args = {
+ value: 45,
+ showPercentage: true.valueOf,
+ preferredDirection: 'LESS'
+};
+
diff --git a/stories/global.scss b/stories/global.scss
index 3167bb8582..45c0c26708 100644
--- a/stories/global.scss
+++ b/stories/global.scss
@@ -9,4 +9,20 @@ button {
pre.sbdocs {
font-family: monospace;
font-size: 14px;
-}
\ No newline at end of file
+}
+
+BODY, .sbdocs-wrapper {
+ background: var(--body-bg) !important;
+ color: var(--body-text);
+}
+
+H1, H2, H3, H4, H5, H6, P {
+ color: var(--body-text) !important;
+}
+
+CODE:not(.language-jsx) {
+ background-color: var(--box-bg) !important;;
+ border: 1px solid var(--border) !important;;
+ border-radius: var(--border-radius) !important;;
+ color: var(--body-text) !important;
+}
diff --git a/stories/provider-icons.json b/stories/provider-icons.json
new file mode 100644
index 0000000000..7455a480e7
--- /dev/null
+++ b/stories/provider-icons.json
@@ -0,0 +1,18 @@
+[
+ "aliyunecs",
+ "amazonec2",
+ "amazoneks",
+ "azureaks",
+ "digitalocean",
+ "equinix",
+ "googlegke",
+ "k3s",
+ "kubernetes",
+ "linodelke",
+ "minikube",
+ "oci",
+ "openstack",
+ "otc",
+ "pinganyunecs",
+ "pnap"
+]