Skip to content
Permalink
Browse files
feat: backgroundPresets add contrast color (#335)
Co-authored-by: faga <lzc295592@163.com>
Co-authored-by: Guillaume Chau <guillaume.b.chau@gmail.com>
  • Loading branch information
3 people committed Oct 26, 2022
1 parent 086235d commit c7ced23e16c950cc2f6202edfad6dc7337ff81b8
Show file tree
Hide file tree
Showing 14 changed files with 167 additions and 16 deletions.
@@ -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'`
@@ -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
@@ -0,0 +1,57 @@
/// <reference types="cypress" />

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()
})
})
})
@@ -1,9 +1,17 @@
import { defineConfig } from 'histoire'
import { defineConfig, getDefaultConfig } from 'histoire'
import { HstVue } from '@histoire/plugin-vue'

export default defineConfig({
// outDir: 'hdist',
plugins: [
HstVue(),
],
backgroundPresets: [
...(getDefaultConfig().backgroundPresets || []),
{
label: 'Custom gray',
color: '#cafff5',
contrastColor: '#005142',
},
],
})
@@ -18,3 +18,9 @@ const props = defineProps<{
{{ recursiveParameter.name }}
</div>
</template>

<style scoped>
.native-story{
color: var(--histoire-contrast-color)
}
</style>
@@ -0,0 +1,11 @@
<template>
<Story>
<span class="contrast-color">Contrast color</span>
</Story>
</template>

<style scoped>
.contrast-color{
color: var(--histoire-contrast-color)
}
</style>
@@ -7,7 +7,15 @@
}"
>
<Variant title="default">
<p>This is a sub story</p>
<p class="text">
This is a sub story
</p>
</Variant>
</Story>
</template>

<style scoped>
.text {
color: var(--histoire-contrast-color);
}
</style>
@@ -139,7 +139,10 @@ const isResponsiveEnabled = computed(() => !props.variant.responsiveDisabled)
'htw-h-fit': !!finalHeight
} : undefined"
>
<div class="bind-preview-bg htw-rounded-lg htw-h-full">
<div
class="bind-preview-bg htw-rounded-lg htw-h-full"
data-test-id="responsive-preview-bg"
>
<CheckerboardPattern
v-if="settings.checkerboard"
class="htw-absolute htw-inset-0 htw-w-full htw-h-full htw-text-gray-500/20"
@@ -10,6 +10,7 @@ import { usePreviewSettingsStore } from '../../stores/preview-settings'
import GenericRenderStory from './GenericRenderStory.vue'
import ToolbarNewTab from '../toolbar/ToolbarNewTab.vue'
import CheckerboardPattern from '../misc/CheckerboardPattern.vue'
import { getContrastColor } from '../../util/preview-settings'
const props = defineProps({
variant: {
@@ -109,14 +110,22 @@ const settings = usePreviewSettingsStore().currentSettings
@click.stop="selectVariant()"
@keyup="selectVariant()"
>
<div class="htw-absolute htw-inset-0 htw-rounded bind-preview-bg" />
<div
class="htw-absolute htw-inset-0 htw-rounded bind-preview-bg"
data-test-id="responsive-preview-bg"
/>

<CheckerboardPattern
v-if="settings.checkerboard"
class="htw-absolute htw-inset-0 htw-w-full htw-h-full htw-text-gray-500/20"
/>

<div class="htw-relative htw-h-full">
<div
class="htw-relative htw-h-full"
:style="{
'--histoire-contrast-color': getContrastColor(settings),
}"
>
<GenericRenderStory
:key="`${story.id}-${variant.id}`"
:variant="variant"
@@ -5,6 +5,7 @@ import { isDark } from '../../util/dark'
import { histoireConfig } from '../../util/config'
import { usePreviewSettingsStore } from '../../stores/preview-settings'
import StoryResponsivePreview from './StoryResponsivePreview.vue'
import { getContrastColor } from '../../util/preview-settings'
const props = defineProps<{
story: Story
@@ -31,10 +32,15 @@ const settings = usePreviewSettingsStore().currentSettings
:variant="variant"
>
<div
:style="isResponsiveEnabled ? {
width: finalWidth ? `${finalWidth}px` : '100%',
height: finalHeight ? `${finalHeight}px` : '100%',
} : { width: '100%', height: '100%' }"
:style="[
isResponsiveEnabled ? {
width: finalWidth ? `${finalWidth}px` : '100%',
height: finalHeight ? `${finalHeight}px` : '100%',
} : { width: '100%', height: '100%' },
{
'--histoire-contrast-color': getContrastColor(settings),
},
]"
class="htw-relative"
data-test-id="sandbox-render"
>
@@ -1,10 +1,14 @@
<script lang="ts" setup>
import { Icon } from '@iconify/vue'
import { computed } from 'vue'
import { usePreviewSettingsStore } from '../../stores/preview-settings'
import { histoireConfig } from '../../util/config'
import { getContrastColor } from '../../util/preview-settings'
import BaseCheckbox from '../base/BaseCheckbox.vue'
const settings = usePreviewSettingsStore().currentSettings
const contrastColor = computed(() => getContrastColor(settings))
</script>

<template>
@@ -13,30 +17,36 @@ const settings = usePreviewSettingsStore().currentSettings
placement="bottom-end"
:skidding="6"
class="histoire-toolbar-background htw-h-full htw-flex-none"
data-test-id="toolbar-background"
>
<div
v-tooltip="'Background color'"
class="htw-cursor-pointer hover:htw-text-primary-500 htw-flex htw-items-center htw-gap-1 htw-h-full htw-px-2 htw-group"
>
<div
class="bind-preview-bg htw-w-4 htw-h-4 htw-rounded-full htw-border htw-border-black/50 dark:htw-border-white/50"
/>
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"
>
<span v-if="contrastColor">a</span>
</div>
<Icon
icon="carbon:caret-down"
class="htw-w-4 htw-h-4 htw-opacity-50 group-hover:htw-opacity-100"
/>
</div>

<template #popper="{ hide }">
<div class="htw-flex htw-flex-col htw-items-stretch">
<div
class="htw-flex htw-flex-col htw-items-stretch"
data-test-id="background-popper"
>
<BaseCheckbox v-model="settings.checkerboard">
Checkerboard
</BaseCheckbox>

<button
v-for="(option, index) in histoireConfig.backgroundPresets"
:key="index"
class="htw-px-4 htw-py-3 htw-cursor-pointer htw-text-left htw-flex htw-gap-4"
class="htw-px-4 htw-py-3 htw-cursor-pointer htw-text-left htw-flex htw-items-baseline htw-gap-4"
:class="[
settings.backgroundColor === option.color
? 'htw-bg-primary-500 hover:htw-bg-primary-600 htw-text-white dark:htw-text-black'
@@ -48,11 +58,14 @@ const settings = usePreviewSettingsStore().currentSettings
<template v-if="option.color !== '$checkerboard'">
<span class="htw-ml-auto htw-opacity-70">{{ option.color }}</span>
<div
class="htw-w-4 htw-h-4 htw-rounded-full htw-border htw-border-black/20 dark:htw-border-white/20"
class="htw-w-4 htw-h-4 htw-rounded-full htw-border htw-border-black/20 dark:htw-border-white/20 htw-flex htw-items-center htw-justify-center htw-text-xs"
:style="{
backgroundColor: option.color,
color: option.contrastColor,
}"
/>
>
<span v-if="option.contrastColor">a</span>
</div>
</template>
</button>
</div>
@@ -63,5 +76,6 @@ const settings = usePreviewSettingsStore().currentSettings
<style scoped>
.bind-preview-bg {
background-color: v-bind('settings.backgroundColor');
color: v-bind('contrastColor');
}
</style>
@@ -1,10 +1,19 @@
import { reactive } from 'vue'
import { histoireConfig } from './config'
import type { PreviewSettings } from '../types'

export const receivedSettings = reactive<PreviewSettings>({} 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'
}
@@ -25,6 +25,7 @@ export interface ResponsivePreset {
export interface BackgroundPreset {
label: string
color: string
contrastColor?: string
}

export interface TreeGroupConfig {
@@ -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',

0 comments on commit c7ced23

Please sign in to comment.