Display: Add feature to customize colors of chamilo - refs #4745
* Minor: remove unused styles * Use css variables to set colors in tailwind * Minor: add missing icon * Add BaseColorPicker component * Adapt base components to use colors from tailwindcss * Create composable to retrieve css variables * Add component and route to modify chamilo colors --------- Co-authored-by: Yannick Warnier <ywarnier@users.noreply.github.com> Co-authored-by: Angel Fernando Quiroz Campos <angelfqc.18@gmail.com>pull/5070/head^2^2
parent
df76d92d1d
commit
7d6ddd0f28
@ -0,0 +1,27 @@ |
||||
<template> |
||||
<div class="flex flex-col justify-center"> |
||||
<span v-if="label">{{ label }}</span> |
||||
<ColorPicker |
||||
format="rgb" |
||||
:model-value="modelValue" |
||||
@update:model-value="emit('update:modelValue', $event)" |
||||
/> |
||||
</div> |
||||
</template> |
||||
|
||||
<script setup> |
||||
import ColorPicker from 'primevue/colorpicker' |
||||
|
||||
defineProps({ |
||||
modelValue: { |
||||
type: Object, |
||||
required: true, |
||||
}, |
||||
label: { |
||||
type: String, |
||||
default: '', |
||||
} |
||||
}) |
||||
|
||||
const emit = defineEmits(["update:modelValue"]) |
||||
</script> |
@ -0,0 +1,55 @@ |
||||
import {onMounted, ref, watch} from "vue"; |
||||
|
||||
export const useTheme = () => { |
||||
|
||||
let colors = {} |
||||
|
||||
onMounted(() => { |
||||
// getCssVariableValue return empty if called too early, refresh colors when html is mounted
|
||||
// to ensure all values are set correctly
|
||||
for (const [key, value] of Object.entries(colors)) { |
||||
value.value = getCssVariableValue(key) |
||||
} |
||||
}) |
||||
|
||||
const getColorTheme = (variableName) => { |
||||
if (Object.hasOwn(colors, variableName)) { |
||||
return colors[variableName] |
||||
} |
||||
const colorRef = ref(getCssVariableValue(variableName)) |
||||
watch(colorRef, (newColor) => { |
||||
setCssVariableValue(variableName, newColor) |
||||
}) |
||||
colors[variableName] = colorRef |
||||
return colorRef |
||||
} |
||||
|
||||
const getCssVariableValue = (variableName) => { |
||||
const colorVariable = getComputedStyle(document.body) |
||||
.getPropertyValue(variableName) |
||||
.split(", ") |
||||
return { |
||||
r: parseInt(colorVariable[0]), |
||||
g: parseInt(colorVariable[1]), |
||||
b: parseInt(colorVariable[2]), |
||||
} |
||||
} |
||||
|
||||
const setCssVariableValue = (variableName, color) => { |
||||
document.documentElement.style |
||||
.setProperty(variableName, `${color.r}, ${color.g}, ${color.b}`) |
||||
} |
||||
|
||||
const getColors = () => { |
||||
let colorsPlainObject = {} |
||||
for (const [key, value] of Object.entries(colors)) { |
||||
colorsPlainObject[key] = `${value.value.r} ${value.value.g} ${value.value.b}` |
||||
} |
||||
return colorsPlainObject |
||||
} |
||||
|
||||
return { |
||||
getColorTheme, |
||||
getColors, |
||||
} |
||||
} |
@ -0,0 +1,136 @@ |
||||
<template class="personal-theme"> |
||||
<h4 class="mb-4">{{ t('Configure chamilo colors') }}</h4> |
||||
|
||||
<div class="grid grid-cols-2 gap-2 mb-8"> |
||||
<BaseColorPicker v-model="primaryColor" :label="t('Pick primary color')"/> |
||||
<BaseColorPicker v-model="primaryColorGradient" :label="t('Pick primary color gradient')"/> |
||||
<BaseColorPicker v-model="secondaryColor" :label="t('Pick secondary color')"/> |
||||
<BaseColorPicker v-model="secondaryColorGradient" :label="t('Pick secondary color gradient')"/> |
||||
<BaseColorPicker v-model="tertiaryColor" :label="t('Pick tertiary color')"/> |
||||
<BaseColorPicker v-model="tertiaryColorGradient" :label="t('Pick tertiary color gradient')"/> |
||||
<BaseColorPicker v-model="successColor" :label="t('Pick success color')"/> |
||||
<BaseColorPicker v-model="successColorGradient" :label="t('Pick success color gradient')"/> |
||||
<BaseColorPicker v-model="dangerColor" :label="t('Pick danger color')"/> |
||||
</div> |
||||
|
||||
<div class="flex flex-wrap mb-4"> |
||||
<BaseButton type="primary" icon="send" :label="t('Save')" @click="saveColors"/> |
||||
</div> |
||||
|
||||
<hr> |
||||
<h5 class="mb-4">{{ t('You can see examples of how chamilo will look here') }}</h5> |
||||
|
||||
<div class="mb-4"> |
||||
<p class="mb-3 text-lg">{{ t('Buttons') }}</p> |
||||
<div class="flex flex-row flex-wrap"> |
||||
<BaseButton class="mr-2 mb-2" :label="t('Button')" type="primary" icon="eye-on"/> |
||||
<BaseButton class="mr-2 mb-2" :label="t('Disabled')" type="primary" icon="eye-on" disabled/> |
||||
<BaseButton class="mr-2 mb-2" :label="t('Secondary')" type="secondary" icon="eye-on"/> |
||||
<BaseButton class="mr-2 mb-2" :label="t('Tertiary')" type="black" icon="eye-on"/> |
||||
<BaseButton class="mr-2 mb-2" type="primary" icon="cog" only-icon/> |
||||
<BaseButton class="mr-2 mb-2" :label="t('Success')" type="success" icon="send"/> |
||||
<BaseButton class="mr-2 mb-2" :label="t('Danger')" type="danger" icon="delete"/> |
||||
</div> |
||||
</div> |
||||
|
||||
<div class="mb-4"> |
||||
<p class="mb-3 text-lg">{{ t('Menu on button pressed') }}</p> |
||||
<BaseButton |
||||
class="mr-2 mb-2" |
||||
type="primary" |
||||
icon="cog" |
||||
popup-identifier="menu" |
||||
only-icon |
||||
@click="toggle" |
||||
/> |
||||
<BaseMenu id="menu" ref="menu" :model="menuItems"></BaseMenu> |
||||
</div> |
||||
|
||||
<div class="mb-4"> |
||||
<p class="mb-3 text-lg">{{ t('Checkbox and radio buttons') }}</p> |
||||
<BaseCheckbox id="check1" v-model="checkbox1" :label="t('Checkbox 1')" name="checkbox1"/> |
||||
<BaseCheckbox id="check2" v-model="checkbox2" :label="t('Checkbox 2')" name="checkbox2"/> |
||||
<div class="mb-2"></div> |
||||
<BaseRadioButtons |
||||
v-model="radioValue" |
||||
:options="radioButtons" |
||||
:initial-value="radioValue" |
||||
name="radio" |
||||
/> |
||||
</div> |
||||
|
||||
<div class="mb-4"> |
||||
<p class="mb-3 text-lg">Forms</p> |
||||
<BaseInputText |
||||
v-model="inputText" |
||||
:label="t('This is a text example')" |
||||
/> |
||||
</div> |
||||
|
||||
<div class="mb-4"> |
||||
<p class="mb-3 text-lg">Dialogs</p> |
||||
<BaseButton :label="t('Show dialog')" type="black" icon="eye-on" @click="isDialogVisible = true"/> |
||||
<BaseDialogConfirmCancel |
||||
:title="t('Dialog example')" |
||||
:is-visible="isDialogVisible" |
||||
@confirm-clicked="isDialogVisible = false" |
||||
@cancel-clicked="isDialogVisible = false" |
||||
/> |
||||
</div> |
||||
</template> |
||||
|
||||
<script setup> |
||||
import BaseButton from "../../components/basecomponents/BaseButton.vue" |
||||
import {useI18n} from "vue-i18n" |
||||
import BaseMenu from "../../components/basecomponents/BaseMenu.vue" |
||||
import {ref} from "vue" |
||||
import BaseCheckbox from "../../components/basecomponents/BaseCheckbox.vue" |
||||
import BaseRadioButtons from "../../components/basecomponents/BaseRadioButtons.vue" |
||||
import BaseDialogConfirmCancel from "../../components/basecomponents/BaseDialogConfirmCancel.vue" |
||||
import BaseInputText from "../../components/basecomponents/BaseInputText.vue" |
||||
import BaseColorPicker from "../../components/basecomponents/BaseColorPicker.vue" |
||||
import {useTheme} from "../../composables/theme" |
||||
|
||||
const {t} = useI18n() |
||||
const {getColorTheme, getColors} = useTheme() |
||||
|
||||
let primaryColor = getColorTheme('--color-primary-base') |
||||
let primaryColorGradient = getColorTheme('--color-primary-gradient') |
||||
let secondaryColor = getColorTheme('--color-secondary-base') |
||||
let secondaryColorGradient = getColorTheme('--color-secondary-gradient') |
||||
let tertiaryColor = getColorTheme('--color-tertiary-base') |
||||
let tertiaryColorGradient = getColorTheme('--color-tertiary-gradient') |
||||
let successColor = getColorTheme('--color-success-base') |
||||
let successColorGradient = getColorTheme('--color-success-gradient') |
||||
let dangerColor = getColorTheme('--color-danger-base') |
||||
|
||||
const saveColors = () => { |
||||
let colors = getColors() |
||||
// TODO send colors to backend, then notify if was correct or incorrect |
||||
console.log(colors) |
||||
} |
||||
|
||||
const menu = ref('menu') |
||||
const menuItems = [ |
||||
{label: t('Item 1')}, |
||||
{label: t('Item 2')}, |
||||
{label: t('Item 3')}, |
||||
] |
||||
const toggle = (event) => { |
||||
menu.value.toggle(event) |
||||
} |
||||
|
||||
const checkbox1 = ref(true) |
||||
const checkbox2 = ref(false) |
||||
|
||||
const radioButtons = [ |
||||
{label: t('Value 1'), value: 'value1'}, |
||||
{label: t('Value 2'), value: 'value2'}, |
||||
{label: t('Value 3'), value: 'value3'}, |
||||
] |
||||
const radioValue = ref('value1') |
||||
|
||||
const isDialogVisible = ref(false) |
||||
|
||||
const inputText = ref('') |
||||
</script> |
Loading…
Reference in new issue