Commit ab5c73d9 authored by Дмитрий Малюгин's avatar Дмитрий Малюгин 🕓
Browse files

feat: 'ColorPicker'

parent 70caee0b
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -182,6 +182,13 @@ export interface IPopupProps {
  left?: number;
}

export interface IColorPickerProps {
  size?: TSize;
  disabled?: boolean;
  buttonProps?: IButtonProps;
  sameButtonColor?: boolean;
}

export interface ISelectProps {
  options?: ISelectOption[];
  groups?: ISelectGroup[];
+1 −1
Original line number Diff line number Diff line
@@ -9,7 +9,7 @@ const meta: Meta = {
  parameters: {
    docs: {
      description: {
        component: 'A component to define number inputs with a dial.',
        component: 'A content slider.',
      },
    },
  },
+94 −0
Original line number Diff line number Diff line
<script setup lang="ts">
import { computed } from 'vue';
import type { IButtonProps } from '@interfaces/componentsProps';
import { convertThemeToSecondaryColor, convertThemeToColor } from '@helpers/common';

interface Props extends IButtonProps {
  disabled?: boolean;
  color: string | null;
}
const props = withDefaults(defineProps<Props>(), {
  size: 'normal',
  theme: 'white',
  iconPos: 'left',
  darknessTheme: '500',
  darknessTextColor: '500',
});

const themeColor = computed(() => props.color ?? convertThemeToColor(props.theme, props.darknessTheme));
const borderColor = computed(() =>
  props.disabled ? '#62708c' : convertThemeToSecondaryColor(props.theme, props.darknessTheme),
);
const width = computed(() => (props.width ? props.width : 'max-content'));
</script>

<template>
  <div
    :class="[
      'button',
      {
        'flex-column': iconPos === 'top' || iconPos === 'bottom',
        border: borderColor,
      },
    ]"
    :style="`width: ${width}`"
  >
    <span :style="`background-color: ${themeColor}`" class="background"></span>
    <span
      v-if="$slots.default"
      :class="[
        'icon',
        {
          'order-1': iconPos === 'left' || iconPos === 'top',
        },
      ]"
    >
      <slot />
    </span>
  </div>
</template>

<style scoped>
.button {
  position: relative;
  border-radius: 7px;
  display: inline-flex;
  justify-content: center;
  align-items: center;
  user-select: none;
  transition: filter 0.2s ease-in-out;
}
.button:hover {
  filter: brightness(90%);
}
.button:active {
  filter: brightness(75%);
}
.background {
  width: 100%;
  height: 100%;
  position: absolute;
  top: 0;
  left: 0;
  border-radius: 5px;
}
.text {
  position: relative;
  z-index: 2;
  line-height: 1;
}
.icon {
  position: relative;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 5px;
}
.order-1 {
  order: -1;
}
.border {
  border: 2px solid v-bind(borderColor);
}
</style>
+43 −0
Original line number Diff line number Diff line
import type { Meta, StoryObj } from '@storybook/vue3';

import ColorPicker from './ColorPicker.vue';

const meta: Meta = {
  title: 'Components/ColorPicker',
  component: ColorPicker,
  tags: ['pick'],
  parameters: {
    docs: {
      description: {
        component: 'A component to define number inputs with a dial.',
      },
    },
  },
  argTypes: {
    buttonProps: { control: 'object' },
    sameButtonColor: { control: 'boolean' },
    disabled: { control: 'boolean' },
    size: { control: 'select', options: ['small', 'normal', 'large', 'huge'] },
  },
} satisfies Meta<typeof ColorPicker>;

export default meta;

type Story = StoryObj<typeof meta>;

export const Simple: Story = {
  args: {},
};

export const Full: Story = {
  args: {
    buttonProps: {
      label: 'Pick color!',
      theme: 'red',
      textStyle: 'bold',
    },

    size: 'large',
    sameButtonColor: true,
  },
};
+149 −0
Original line number Diff line number Diff line
<script setup lang="ts">
import type { IColorPickerProps } from '@interfaces/componentsProps';
import { computed, type Ref } from 'vue';
import Button from './Button.vue';
import { convertThemeToColor, convertThemeToTextColor } from '@helpers/common';

const props = withDefaults(defineProps<IColorPickerProps>(), {
  size: 'normal',
  disabled: false,
});

const value = defineModel() as Ref<string>;
const size = computed(() => {
  const size = props.size;
  if (size === 'normal') return '25px';
  if (size === 'large') return '40px';
  if (size === 'huge') return '60px';
  return '15px';
});
const borderWidth = computed(() => (props.size === 'small' ? '2px' : '3px'));
const borderRadius = computed(() => `calc(${size.value} * 0.3)`);

function wc_hex_is_light(color) {
  if (!color) return true;
  const hex = color.replace('#', '');
  const c_r = parseInt(hex.substring(0, 2), 16);
  const c_g = parseInt(hex.substring(2, 4), 16);
  const c_b = parseInt(hex.substring(4, 6), 16);
  const brightness = (c_r * 299 + c_g * 587 + c_b * 114) / 1000;
  return brightness > 150;
}

const color = computed(() =>
  props.buttonProps.textColor
    ? convertThemeToColor(props.buttonProps.textColor, props.buttonProps.darknessTextColor)
    : props.sameButtonColor
      ? !wc_hex_is_light(value?.value)
        ? 'white'
        : 'black'
      : convertThemeToTextColor(props.buttonProps.theme ?? 'white', props.buttonProps.darknessTheme),
);
const textSize = computed(() => {
  switch (props.size) {
    case 'small':
      return '12px';
    case 'large':
      return '20px';
    case 'huge':
      return '28px';
  }
  return '16px';
});
const buttonPadding = computed(() => {
  if (props.buttonProps.padding) return props.buttonProps.padding;
  switch (props.size) {
    case 'small':
      return '0.5rem';
    case 'large':
      return '1.2rem';
    case 'huge':
      return '1.8rem';
  }
  return '0.75rem';
});
</script>

<template>
  <div class="container">
    <Button
      v-if="buttonProps"
      v-bind="buttonProps"
      :color="sameButtonColor ? value : null"
      :class="{
        disabledButton: disabled,
      }"
      :disabled="disabled"
    >
      <label
        for="inputColor"
        :style="`padding: ${buttonPadding}; color: ${color}; font-size: ${textSize}`"
        :class="[
          'text',
          {
            bold: buttonProps?.textStyle === 'bold',
            italic: buttonProps?.textStyle === 'italic',
          },
        ]"
        >{{ buttonProps?.label ?? 'Button' }}</label
      >
    </Button>
    <input
      type="color"
      id="inputColor"
      v-model="value"
      :disabled="disabled"
      :class="{
        noVisible: buttonProps,
        disabled,
      }"
    />
  </div>
</template>

<style scoped>
.container {
  position: relative;
}
.noVisible {
  opacity: 0;
  position: absolute;
  bottom: 0;
  left: 0;
}
.text {
  border-radius: 5px;
}
.disabledButton {
  * {
    background-color: #e1e7f1 !important;
    color: #62708c !important;
  }
  pointer-events: none;
}
.disabled {
  border-color: #62708c !important;
  cursor: auto;
}
input {
  position: absolute;
  z-index: -1;
  -webkit-appearance: none;
  appearance: none;
  border: v-bind(borderWidth) solid black;
  border-radius: v-bind(borderRadius);
  outline: none;
  width: v-bind(size);
  height: v-bind(size);
  padding: 0;
  background-color: transparent;
  cursor: pointer;
}
input[type='color']::-webkit-color-swatch-wrapper {
  padding: 0;
}

input[type='color']::-webkit-color-swatch {
  border: none;
}
</style>