ui icon indicating copy to clipboard operation
ui copied to clipboard

Sheet component with color prop

Open TechAkayy opened this issue 1 year ago • 2 comments

Would be great to have a basic "Sheet" component that "color" as prop. Here is some inspiration - https://vuetifyjs.com/en/components/sheets/#color

At the moment, there is no "sheet" component which can take a bg-color-pair that works for both light & dark modes, and accordingly set the content colors in contrast. Thank you!

Or, may be instead, you could add "color" prop to the container component, and re-purpose it? Thanks!

TechAkayy avatar May 19 '23 14:05 TechAkayy

Another inspiration - https://flowbite-vue.com/components/flowbiteThemable/flowbiteThemable

TechAkayy avatar May 20 '23 08:05 TechAkayy

I gave it a shot, and came up with this. Seem to work okay, this is just to explain the thought.

Here is a sample app using the below sheet component - https://github.com/TechAkayy/pg-nuxt-tailwindcss-nuxt-ui / https://stackblitz.com/github/TechAkayy/pg-nuxt-tailwindcss-nuxt-ui

USheet.vue

<script setup lang="ts">
  import { computed } from 'vue'
  import type { PropType } from 'vue'
  import { defu } from 'defu'
  // import { useAppConfig } from '#imports'
  // TODO: Remove
  // @ts-expect-error
  import appConfig from '#build/app.config'

  // TODO: Remove
  // const appConfig = useAppConfig()

  // eslint-disable-next-line vue/no-dupe-keys
  const ui = computed<Partial<typeof appConfig.ui.sheet>>(() =>
    defu({}, props.ui, appConfig.ui.sheet),
  )

  const props = defineProps({
    ui: {
      type: Object as PropType<Partial<typeof appConfig.ui.sheet>>,
      default: () => appConfig.ui.sheet,
    },
    color: {
      type: String,
      default: () => appConfig.ui.sheet.default.color,
      validator(value: string) {
        return [
          ...appConfig.ui.colors,
          ...Object.keys(appConfig.ui.sheet.color),
          appConfig.ui.sheet.default.color,
        ].includes(value)
      },
    },
    variant: {
      type: String,
      default: () => appConfig.ui.sheet.default.variant,
      validator(value: string) {
        return [
          ...Object.keys(appConfig.ui.sheet.variant),
          ...Object.values(appConfig.ui.sheet.color).flatMap((value) =>
            Object.keys(value),
          ),
        ].includes(value)
      },
    },
    contrast: {
      type: Boolean,
      default: true,
    },
    tag: {
      type: String,
      default: 'div',
    },
  })

  const classNames = (...classes) => {
    return classes.filter(Boolean).join(' ')
  }

  const uiStyle = computed(() => {
    const variant =
      ui.value.color?.[props.color as string]?.[props.variant as string] ||
      ui.value.variant[props.variant]
    const variantClasses = variant?.replaceAll('{color}', props.color)
    const variantClassesWithContrast = props.contrast
      ? variantClasses
      : [
          variantClasses
            ?.split(' ')
            .filter((cls) => !cls.includes('text-'))
            .join(' '),
          ui.value.text,
        ]
    return classNames(variantClassesWithContrast)
  })
</script>
<template>
  <component :is="tag" id="sheet" :class="[ui.shadow, uiStyle]">
    <slot />
  </component>
</template>
<style scoped></style>

app.config.ts

export default defineAppConfig({
  ui: {
    sheet: {
      shadow: 'shadow-sm',
      // Inspired by Material design guideline - https://m3.material.io/styles/color/the-color-system/tokens#e26e130c-fa67-48e1-81ca-d28f6e4ed398
      text: 'text-gray-900 dark:text-white',
      variant: {
        solid:
          'bg-{color}-600 dark:bg-{color}-200 text-white dark:text-{color}-800',
        container:
          'bg-{color}-100 dark:bg-{color}-700 text-{color}-900 dark:text-{color}-100',
      },
      color: {
        gray: {
          solid:
            'bg-white dark:bg-{color}-950 text-{color}-900 dark:text-{color}-50',
          container:
            'bg-{color}-50 dark:bg-{color}-900 text-{color}-900 dark:text-{color}-100',
          contained:
            'bg-{color}-100 dark:bg-{color}-700 text-{color}-700 dark:text-{color}-200',
        },
      },
      default: {
        color: 'gray',
        variant: 'solid',
      },
    },
  },
})

tailwind.config.ts (safelist needs a little more work)

  safelist: [
    {
      pattern:
        /(bg|text)-(gray|primary)-(50|100|200|600|700|800|900|950)/, 
    },
    {
      pattern:
        /(bg|text)-(gray|primary)-(50|100|200|600|700|800|900|950)/, 
      variants: ['dark'],
    },
  ],

TechAkayy avatar May 20 '23 15:05 TechAkayy

Why not use the UCard component to achieve this? https://ui.nuxtlabs.com/layout/card

benjamincanac avatar May 21 '23 21:05 benjamincanac

Thanks for the suggestion @benjamincanac :-) Looking at UCard preset, it sets only a background, and doesn't set color of any text or icons that goes on top of it. If we use the UCard and override with a "primary" bg, then we have to set the text color of the content to be at the right contrast, and these two bg & text combo needs an equivalent dark variant as well.

Overriding the bg, setting text color of the content, and removing the padding to use it as a layout "sheet", really means it's not really a card anymore, hence the idea to have a separate "Sheet" component.

My above sample works fine for me atm, and if you see this idea not fitting the library's goals, please feel free to close this request.

Meanwhile, the way you have architectured the library via presets & app.config.ts is amazing! Thanks!

TechAkayy avatar May 26 '23 01:05 TechAkayy

You can use ui.card.body.background, ui.card.header.background and ui.card.footer.background to achieve this I guess.

benjamincanac avatar May 26 '23 08:05 benjamincanac