Loading src/common/interfaces/componentsProps.ts +7 −0 Original line number Diff line number Diff line Loading @@ -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[]; Loading src/components/Carousel/Carousel.stories.ts +1 −1 Original line number Diff line number Diff line Loading @@ -9,7 +9,7 @@ const meta: Meta = { parameters: { docs: { description: { component: 'A component to define number inputs with a dial.', component: 'A content slider.', }, }, }, Loading src/components/ColorPicker/Button.vue 0 → 100644 +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> src/components/ColorPicker/ColorPicker.stories.ts 0 → 100644 +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, }, }; src/components/ColorPicker/ColorPicker.vue 0 → 100644 +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> Loading
src/common/interfaces/componentsProps.ts +7 −0 Original line number Diff line number Diff line Loading @@ -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[]; Loading
src/components/Carousel/Carousel.stories.ts +1 −1 Original line number Diff line number Diff line Loading @@ -9,7 +9,7 @@ const meta: Meta = { parameters: { docs: { description: { component: 'A component to define number inputs with a dial.', component: 'A content slider.', }, }, }, Loading
src/components/ColorPicker/Button.vue 0 → 100644 +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>
src/components/ColorPicker/ColorPicker.stories.ts 0 → 100644 +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, }, };
src/components/ColorPicker/ColorPicker.vue 0 → 100644 +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>