Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: backgroundPresets add contrast color #335

Merged
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
14 changes: 14 additions & 0 deletions docs/reference/config.md
Expand Up @@ -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:

Expand All @@ -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'`
Expand Down
2 changes: 1 addition & 1 deletion examples/vue3/cypress/integration/stories-list.js
Expand Up @@ -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
Expand Down
57 changes: 57 additions & 0 deletions examples/vue3/cypress/integration/toolbar-background.js
@@ -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()
})
})
})
10 changes: 9 additions & 1 deletion examples/vue3/histoire.config.ts
@@ -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',
},
],
})
6 changes: 6 additions & 0 deletions examples/vue3/src/components/ComplexParameter.vue
Expand Up @@ -18,3 +18,9 @@ const props = defineProps<{
{{ recursiveParameter.name }}
</div>
</template>

<style scoped>
.native-story{
color: var(--histoire-contrast-color)
}
</style>
11 changes: 11 additions & 0 deletions examples/vue3/src/components/ContrastColor.story.vue
@@ -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>
10 changes: 9 additions & 1 deletion examples/vue3/src/components/SubStory.story.vue
Expand Up @@ -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>
Expand Up @@ -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"
Expand Down
Expand Up @@ -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: {
Expand Down Expand Up @@ -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"
Expand Down
Expand Up @@ -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
Expand All @@ -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"
>
Expand Down
@@ -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>
Expand All @@ -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'
Expand All @@ -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>
Expand All @@ -63,5 +76,6 @@ const settings = usePreviewSettingsStore().currentSettings
<style scoped>
.bind-preview-bg {
background-color: v-bind('settings.backgroundColor');
color: v-bind('contrastColor');
}
</style>
9 changes: 9 additions & 0 deletions packages/histoire-app/src/app/util/preview-settings.ts
@@ -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'
}
1 change: 1 addition & 0 deletions packages/histoire-shared/src/types/config.ts
Expand Up @@ -25,6 +25,7 @@ export interface ResponsivePreset {
export interface BackgroundPreset {
label: string
color: string
contrastColor?: string
}

export interface TreeGroupConfig {
Expand Down
5 changes: 5 additions & 0 deletions packages/histoire/src/node/config.ts
Expand Up @@ -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',
Expand Down