diff --git a/docs/reference/config.md b/docs/reference/config.md
index 6dc3fe41..c71b9e00 100644
--- a/docs/reference/config.md
+++ b/docs/reference/config.md
@@ -252,6 +252,7 @@ Each object in the array is a preset with the following properties:
- `label: string`: Label for the preset.
- `color: string`: Color of the preset.
+- `contrastColor?: string`: Contrast color of preset
Default values are shown in the example below:
@@ -261,27 +262,40 @@ export default defineConfig({
{
label: 'Transparent',
color: 'transparent',
+ contrastColor: '#333'
},
{
label: 'White',
color: '#fff',
+ contrastColor: '#333'
},
{
label: 'Light gray',
color: '#aaa',
+ contrastColor: '#eee'
},
{
label: 'Dark gray',
color: '#333',
+ contrastColor: '#ccc'
},
{
label: 'Black',
color: '#000',
+ contrastColor: '#fff'
},
],
})
```
+You can use current contrast color via the css variable `--histoire-contrast-color`:
+
+```css
+.my-class {
+ color: var(--histoire-contrast-color);
+}
+```
+
## `sandboxDarkClass`
`string` - Default: `'dark'`
diff --git a/examples/vue3/cypress/integration/stories-list.js b/examples/vue3/cypress/integration/stories-list.js
index b79aa44d..a6acb601 100644
--- a/examples/vue3/cypress/integration/stories-list.js
+++ b/examples/vue3/cypress/integration/stories-list.js
@@ -4,7 +4,7 @@ describe('Stories list', () => {
it('should display the stories', () => {
cy.clearLocalStorage()
cy.visit('/')
- cy.get('[data-test-id="story-list-item"]').should('have.length', 26)
+ cy.get('[data-test-id="story-list-item"]').should('have.length', 27)
cy.get('[data-test-id="story-list-item"]').contains('🐱 Meow')
cy.get('[data-test-id="story-list-item"]').contains('BaseButton')
.contains('3') // Variants count
diff --git a/examples/vue3/cypress/integration/toolbar-background.js b/examples/vue3/cypress/integration/toolbar-background.js
new file mode 100644
index 00000000..ad3ff05f
--- /dev/null
+++ b/examples/vue3/cypress/integration/toolbar-background.js
@@ -0,0 +1,57 @@
+///
+
+describe('background color', () => {
+ const getIframeBody = () => cy.get('iframe[data-test-id="preview-iframe"]')
+ .its('0.contentDocument.body').should('not.be.empty')
+ .then(cy.wrap)
+
+ const backgroundColorShouldBe = [
+ 'rgba(0, 0, 0, 0)',
+ 'rgb(255, 255, 255)',
+ 'rgb(170, 170, 170)',
+ 'rgb(51, 51, 51)',
+ 'rgb(0, 0, 0)',
+ 'rgb(202, 255, 245)',
+ ]
+
+ const contrastColorShouldBe = [
+ 'rgb(51, 51, 51)',
+ 'rgb(51, 51, 51)',
+ 'rgb(0, 0, 0)',
+ 'rgb(255, 255, 255)',
+ 'rgb(238, 238, 238)',
+ 'rgb(0, 81, 66)',
+ ]
+
+ it('should provide background and contrast color (no iframe)', () => {
+ cy.visit('/story/src-components-complexparameter-story-vue?variantId=_default')
+ cy.get('[data-test-id="toolbar-background"]').click()
+ cy.get('[data-test-id="background-popper"]').should('be.visible').find('button').should('have.length', 6).each(($el, index) => {
+ cy.wrap($el).click()
+ cy.get('[data-test-id="responsive-preview-bg"]').should('have.css', 'background-color', backgroundColorShouldBe[index])
+ cy.get('[data-test-id="story-variant-single-view"] .native-story').should('have.css', 'color', contrastColorShouldBe[index])
+ cy.get('[data-test-id="toolbar-background"]').click()
+ })
+ })
+
+ it('should provide background and contrast color (with iframe)', () => {
+ cy.visit('story/src-components-contrastcolor-story-vue?variantId=_default')
+ cy.get('[data-test-id="toolbar-background"]').click()
+ cy.get('[data-test-id="background-popper"]').should('be.visible').find('button').should('have.length', 6).each(($el, index) => {
+ cy.wrap($el).click()
+ getIframeBody().find('.contrast-color').should('have.css', 'color', contrastColorShouldBe[index])
+ cy.get('[data-test-id="toolbar-background"]').click()
+ })
+ })
+
+ it('should provide background and contrast color (grid)', () => {
+ cy.visit('/story/src-components-substory-story-vue?variantId=src-components-substory-story-vue-0')
+ cy.get('[data-test-id="toolbar-background"]').click()
+ cy.get('[data-test-id="background-popper"]').should('be.visible').find('button').should('have.length', 6).each(($el, index) => {
+ cy.wrap($el).click()
+ cy.get('[data-test-id="responsive-preview-bg"]').should('have.css', 'background-color', backgroundColorShouldBe[index])
+ cy.get('.__histoire-sandbox .text').should('have.css', 'color', contrastColorShouldBe[index])
+ cy.get('[data-test-id="toolbar-background"]').click()
+ })
+ })
+})
diff --git a/examples/vue3/histoire.config.ts b/examples/vue3/histoire.config.ts
index a9fed307..a24d6981 100644
--- a/examples/vue3/histoire.config.ts
+++ b/examples/vue3/histoire.config.ts
@@ -1,4 +1,4 @@
-import { defineConfig } from 'histoire'
+import { defineConfig, getDefaultConfig } from 'histoire'
import { HstVue } from '@histoire/plugin-vue'
export default defineConfig({
@@ -6,4 +6,12 @@ export default defineConfig({
plugins: [
HstVue(),
],
+ backgroundPresets: [
+ ...(getDefaultConfig().backgroundPresets || []),
+ {
+ label: 'Custom gray',
+ color: '#cafff5',
+ contrastColor: '#005142',
+ },
+ ],
})
diff --git a/examples/vue3/src/components/ComplexParameter.vue b/examples/vue3/src/components/ComplexParameter.vue
index e261054b..85ebee1d 100644
--- a/examples/vue3/src/components/ComplexParameter.vue
+++ b/examples/vue3/src/components/ComplexParameter.vue
@@ -18,3 +18,9 @@ const props = defineProps<{
{{ recursiveParameter.name }}
+
+
diff --git a/examples/vue3/src/components/ContrastColor.story.vue b/examples/vue3/src/components/ContrastColor.story.vue
new file mode 100644
index 00000000..2326eb84
--- /dev/null
+++ b/examples/vue3/src/components/ContrastColor.story.vue
@@ -0,0 +1,11 @@
+
+
+ Contrast color
+
+
+
+
diff --git a/examples/vue3/src/components/SubStory.story.vue b/examples/vue3/src/components/SubStory.story.vue
index f477f8f6..96c36322 100644
--- a/examples/vue3/src/components/SubStory.story.vue
+++ b/examples/vue3/src/components/SubStory.story.vue
@@ -7,7 +7,15 @@
}"
>
- This is a sub story
+
+ This is a sub story
+
+
+
diff --git a/packages/histoire-app/src/app/components/story/StoryResponsivePreview.vue b/packages/histoire-app/src/app/components/story/StoryResponsivePreview.vue
index 53b8dff9..5c2f1398 100644
--- a/packages/histoire-app/src/app/components/story/StoryResponsivePreview.vue
+++ b/packages/histoire-app/src/app/components/story/StoryResponsivePreview.vue
@@ -139,7 +139,10 @@ const isResponsiveEnabled = computed(() => !props.variant.responsiveDisabled)
'htw-h-fit': !!finalHeight
} : undefined"
>
-
+
-
+
-
+
diff --git a/packages/histoire-app/src/app/components/toolbar/ToolbarBackground.vue b/packages/histoire-app/src/app/components/toolbar/ToolbarBackground.vue
index add0bab3..5f0d7f7c 100644
--- a/packages/histoire-app/src/app/components/toolbar/ToolbarBackground.vue
+++ b/packages/histoire-app/src/app/components/toolbar/ToolbarBackground.vue
@@ -1,10 +1,14 @@
@@ -13,14 +17,17 @@ const settings = usePreviewSettingsStore().currentSettings
placement="bottom-end"
:skidding="6"
class="histoire-toolbar-background htw-h-full htw-flex-none"
+ data-test-id="toolbar-background"
>
+ class="bind-preview-bg htw-w-4 htw-h-4 htw-rounded-full htw-border htw-border-black/50 dark:htw-border-white/50 htw-flex htw-items-center htw-justify-center htw-text-xs"
+ >
+
a
+
-
+
Checkerboard
@@ -36,7 +46,7 @@ const settings = usePreviewSettingsStore().currentSettings
@@ -63,5 +76,6 @@ const settings = usePreviewSettingsStore().currentSettings
diff --git a/packages/histoire-app/src/app/util/preview-settings.ts b/packages/histoire-app/src/app/util/preview-settings.ts
index ab6d48ea..e6354f24 100644
--- a/packages/histoire-app/src/app/util/preview-settings.ts
+++ b/packages/histoire-app/src/app/util/preview-settings.ts
@@ -1,4 +1,5 @@
import { reactive } from 'vue'
+import { histoireConfig } from './config'
import type { PreviewSettings } from '../types'
export const receivedSettings = reactive({} as PreviewSettings)
@@ -6,5 +7,13 @@ export const receivedSettings = reactive({} as PreviewSettings)
export function applyPreviewSettings (settings: PreviewSettings) {
Object.assign(receivedSettings, settings)
+ // Text direction
document.documentElement.setAttribute('dir', settings.textDirection)
+
+ // Contrast color
+ document.documentElement.style.setProperty('--histoire-contrast-color', getContrastColor(settings))
+}
+
+export function getContrastColor (setting: PreviewSettings) {
+ return histoireConfig.backgroundPresets.find(preset => preset.color === setting.backgroundColor)?.contrastColor ?? 'unset'
}
diff --git a/packages/histoire-shared/src/types/config.ts b/packages/histoire-shared/src/types/config.ts
index 3661bf34..6d52eaed 100644
--- a/packages/histoire-shared/src/types/config.ts
+++ b/packages/histoire-shared/src/types/config.ts
@@ -25,6 +25,7 @@ export interface ResponsivePreset {
export interface BackgroundPreset {
label: string
color: string
+ contrastColor?: string
}
export interface TreeGroupConfig {
diff --git a/packages/histoire/src/node/config.ts b/packages/histoire/src/node/config.ts
index 8546ffbb..c1a1e806 100644
--- a/packages/histoire/src/node/config.ts
+++ b/packages/histoire/src/node/config.ts
@@ -93,22 +93,27 @@ export function getDefaultConfig (): HistoireConfig {
{
label: 'Transparent',
color: 'transparent',
+ contrastColor: '#333',
},
{
label: 'White',
color: '#fff',
+ contrastColor: '#333',
},
{
label: 'Light gray',
color: '#aaa',
+ contrastColor: '#000',
},
{
label: 'Dark gray',
color: '#333',
+ contrastColor: '#fff',
},
{
label: 'Black',
color: '#000',
+ contrastColor: '#eee',
},
],
sandboxDarkClass: 'dark',