aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--components.d.ts5
-rw-r--r--src/config.ts4
-rw-r--r--src/router.ts2
-rw-r--r--src/ui/c-button/c-button.demo.vue29
-rw-r--r--src/ui/c-button/c-button.theme.ts292
-rw-r--r--src/ui/c-button/c-button.vue5
-rw-r--r--src/ui/c-card/c-card.demo.vue13
-rw-r--r--src/ui/c-link/c-link.demo.vue12
-rw-r--r--src/ui/c-link/c-link.vue10
-rw-r--r--src/ui/color/color.models.test.ts40
-rw-r--r--src/ui/color/color.models.ts33
-rw-r--r--src/ui/demo/demo-wrapper.vue33
-rw-r--r--src/ui/demo/demo.routes.ts25
-rw-r--r--src/ui/theme/themes.ts23
14 files changed, 286 insertions, 240 deletions
diff --git a/components.d.ts b/components.d.ts
index ae1710f..2750ce6 100644
--- a/components.d.ts
+++ b/components.d.ts
@@ -21,16 +21,21 @@ declare module '@vue/runtime-core' {
Bip39Generator: typeof import('./src/tools/bip39-generator/bip39-generator.vue')['default']
CaseConverter: typeof import('./src/tools/case-converter/case-converter.vue')['default']
CButton: typeof import('./src/ui/c-button/c-button.vue')['default']
+ 'CButton.demo': typeof import('./src/ui/c-button/c-button.demo.vue')['default']
CCard: typeof import('./src/ui/c-card/c-card.vue')['default']
+ 'CCard.demo': typeof import('./src/ui/c-card/c-card.demo.vue')['default']
ChmodCalculator: typeof import('./src/tools/chmod-calculator/chmod-calculator.vue')['default']
Chronometer: typeof import('./src/tools/chronometer/chronometer.vue')['default']
CLink: typeof import('./src/ui/c-link/c-link.vue')['default']
+ 'CLink.demo': typeof import('./src/ui/c-link/c-link.demo.vue')['default']
CollapsibleToolMenu: typeof import('./src/components/CollapsibleToolMenu.vue')['default']
ColorConverter: typeof import('./src/tools/color-converter/color-converter.vue')['default']
ColoredCard: typeof import('./src/components/ColoredCard.vue')['default']
CopyableIpLike: typeof import('./src/tools/ipv4-subnet-calculator/copyable-ip-like.vue')['default']
CrontabGenerator: typeof import('./src/tools/crontab-generator/crontab-generator.vue')['default']
DateTimeConverter: typeof import('./src/tools/date-time-converter/date-time-converter.vue')['default']
+ 'Demo.routes': typeof import('./src/ui/demo/demo.routes.vue')['default']
+ DemoWrapper: typeof import('./src/ui/demo/demo-wrapper.vue')['default']
DeviceInformation: typeof import('./src/tools/device-information/device-information.vue')['default']
DiffViewer: typeof import('./src/tools/json-diff/diff-viewer/diff-viewer.vue')['default']
DockerRunToDockerComposeConverter: typeof import('./src/tools/docker-run-to-docker-compose-converter/docker-run-to-docker-compose-converter.vue')['default']
diff --git a/src/config.ts b/src/config.ts
index c0545f6..2558d03 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -23,9 +23,9 @@ export const config = figue({
env: {
doc: 'Application current env',
format: 'enum',
- values: ['production', 'development', 'test'],
+ values: ['production', 'development', 'preview', 'test'],
default: 'development',
- env: 'MODE',
+ env: 'VITE_VERCEL_ENV',
},
},
plausible: {
diff --git a/src/router.ts b/src/router.ts
index f16b418..00c20c7 100644
--- a/src/router.ts
+++ b/src/router.ts
@@ -4,6 +4,7 @@ import HomePage from './pages/Home.page.vue';
import NotFound from './pages/404.page.vue';
import { tools } from './tools';
import { config } from './config';
+import { routes as demoRoutes } from './ui/demo/demo.routes';
const toolsRoutes = tools.map(({ path, name, component, ...config }) => ({
path,
@@ -32,6 +33,7 @@ const router = createRouter({
},
...toolsRoutes,
...toolsRedirectRoutes,
+ ...(config.app.env === 'development' ? demoRoutes : []),
{ path: '/:pathMatch(.*)*', name: 'NotFound', component: NotFound },
],
});
diff --git a/src/ui/c-button/c-button.demo.vue b/src/ui/c-button/c-button.demo.vue
new file mode 100644
index 0000000..988659f
--- /dev/null
+++ b/src/ui/c-button/c-button.demo.vue
@@ -0,0 +1,29 @@
+<template>
+ <div v-for="buttonVariant of buttonVariants" :key="buttonVariant">
+ <h2>{{ _.capitalize(buttonVariant) }}</h2>
+
+ <c-button v-for="buttonType of buttonTypes" :key="buttonType" :variant="buttonVariant" :type="buttonType" mx-1>
+ Button
+ </c-button>
+
+ <c-button
+ v-for="buttonType of buttonTypes"
+ :key="buttonType"
+ :variant="buttonVariant"
+ :type="buttonType"
+ circle
+ mx-1
+ >
+ A
+ </c-button>
+ </div>
+</template>
+
+<script lang="ts" setup>
+import _ from 'lodash';
+
+const buttonVariants = ['basic', 'text'];
+const buttonTypes = ['default', 'primary'];
+</script>
+
+<style lang="less" scoped></style>
diff --git a/src/ui/c-button/c-button.theme.ts b/src/ui/c-button/c-button.theme.ts
index 2f25e6f..87ad89f 100644
--- a/src/ui/c-button/c-button.theme.ts
+++ b/src/ui/c-button/c-button.theme.ts
@@ -1,240 +1,76 @@
+import { darken, lighten } from '../color/color.models';
import { defineThemes } from '../theme/theme.models';
import { appThemes } from '../theme/themes';
-export const { useTheme } = defineThemes({
- dark: {
- basic: {
- default: {
- textColor: appThemes.dark.text.baseColor,
- backgroundColor: 'rgba(255, 255, 255, 0.08)',
-
- hover: {
- textColor: appThemes.dark.text.baseColor,
- backgroundColor: 'rgba(255, 255, 255, 0.12)',
- },
-
- pressed: {
- textColor: appThemes.dark.text.baseColor,
- backgroundColor: 'rgba(255, 255, 255, 0.24)',
- },
-
- outline: {
- color: appThemes.dark.primary.color,
- },
- },
-
- primary: {
- textColor: appThemes.dark.text.baseColor,
- backgroundColor: appThemes.dark.primary.color,
-
- hover: {
- textColor: appThemes.dark.text.baseColor,
- backgroundColor: appThemes.dark.primary.colorHover,
- },
-
- pressed: {
- textColor: appThemes.dark.text.baseColor,
- backgroundColor: appThemes.dark.primary.colorPressed,
- },
-
- outline: {
- color: appThemes.dark.primary.color,
- },
- },
-
- warning: {
- textColor: appThemes.dark.text.baseColor,
- backgroundColor: appThemes.dark.warning.color,
-
- hover: {
- textColor: appThemes.dark.text.baseColor,
- backgroundColor: appThemes.dark.warning.colorHover,
- },
-
- pressed: {
- textColor: appThemes.dark.text.baseColor,
- backgroundColor: appThemes.dark.warning.colorPressed,
- },
-
- outline: {
- color: appThemes.dark.warning.color,
- },
- },
- },
-
- text: {
- default: {
- textColor: appThemes.dark.text.baseColor,
- backgroundColor: 'transparent',
-
- hover: {
- textColor: appThemes.dark.text.baseColor,
- backgroundColor: 'rgba(255, 255, 255, 0.12)',
- },
-
- pressed: {
- textColor: appThemes.dark.text.baseColor,
- backgroundColor: 'rgba(255, 255, 255, 0.82)',
- },
-
- outline: {
- color: appThemes.dark.primary.color,
- },
- },
-
- primary: {
- textColor: appThemes.dark.text.baseColor,
- backgroundColor: appThemes.dark.primary.color,
-
- hover: {
- textColor: appThemes.dark.text.baseColor,
- backgroundColor: appThemes.dark.primary.colorHover,
- },
-
- pressed: {
- textColor: appThemes.dark.text.baseColor,
- backgroundColor: appThemes.dark.primary.colorPressed,
- },
-
- outline: {
- color: appThemes.dark.primary.color,
- },
- },
-
- warning: {
- textColor: appThemes.dark.text.baseColor,
- backgroundColor: appThemes.dark.warning.color,
-
- hover: {
- textColor: appThemes.dark.text.baseColor,
- backgroundColor: appThemes.dark.warning.colorHover,
- },
+const createState = ({
+ textColor,
+ backgroundColor,
+ hoverBackground,
+ hoveredTextColor = textColor,
+ pressedBackground,
+ pressedTextColor = textColor,
+}: {
+ textColor: string;
+ backgroundColor: string;
+ hoverBackground: string;
+ hoveredTextColor?: string;
+ pressedBackground: string;
+ pressedTextColor?: string;
+}) => ({
+ textColor,
+ backgroundColor,
+ hover: { textColor: hoveredTextColor, backgroundColor: hoverBackground },
+ pressed: { textColor: pressedTextColor, backgroundColor: pressedBackground },
+});
- pressed: {
- textColor: appThemes.dark.text.baseColor,
- backgroundColor: appThemes.dark.warning.colorPressed,
- },
+const createTheme = ({ style }: { style: 'light' | 'dark' }) => {
+ const theme = appThemes[style];
- outline: {
- color: appThemes.dark.warning.color,
- },
- },
- },
- },
- light: {
+ return {
basic: {
- default: {
- textColor: appThemes.light.text.baseColor,
- backgroundColor: 'rgba(46, 51, 56, 0.05)',
-
- hover: {
- textColor: appThemes.light.text.baseColor,
- backgroundColor: 'rgba(46, 51, 56, 0.09)',
- },
-
- pressed: {
- textColor: appThemes.light.text.baseColor,
- backgroundColor: 'rgba(46, 51, 56, 0.22)',
- },
-
- outline: {
- color: appThemes.light.primary.color,
- },
- },
-
- primary: {
- textColor: appThemes.dark.text.baseColor,
- backgroundColor: appThemes.light.primary.color,
-
- hover: {
- textColor: appThemes.dark.text.baseColor,
- backgroundColor: appThemes.light.primary.colorHover,
- },
-
- pressed: {
- textColor: appThemes.dark.text.baseColor,
- backgroundColor: appThemes.light.primary.colorPressed,
- },
-
- outline: {
- color: appThemes.light.primary.color,
- },
- },
-
- warning: {
- textColor: appThemes.dark.text.baseColor,
- backgroundColor: appThemes.light.warning.color,
-
- hover: {
- textColor: appThemes.dark.text.baseColor,
- backgroundColor: appThemes.light.warning.colorHover,
- },
-
- pressed: {
- textColor: appThemes.dark.text.baseColor,
- backgroundColor: appThemes.light.warning.colorPressed,
- },
-
- outline: {
- color: appThemes.light.warning.color,
- },
- },
+ default: createState({
+ textColor: theme.text.baseColor,
+ backgroundColor: theme.default.color,
+ hoverBackground: theme.default.colorHover,
+ pressedBackground: theme.default.colorPressed,
+ }),
+ primary: createState({
+ textColor: theme.primary.color,
+ backgroundColor: theme.primary.colorFaded,
+ hoverBackground: lighten(theme.primary.colorFaded, 30),
+ pressedBackground: darken(theme.primary.colorFaded, 30),
+ }),
+ warning: createState({
+ textColor: theme.text.baseColor,
+ backgroundColor: theme.warning.color,
+ hoverBackground: theme.warning.colorHover,
+ pressedBackground: theme.warning.colorPressed,
+ }),
},
text: {
- default: {
- textColor: appThemes.light.text.baseColor,
+ default: createState({
+ textColor: theme.text.baseColor,
backgroundColor: 'transparent',
-
- hover: {
- textColor: appThemes.light.text.baseColor,
- backgroundColor: 'rgba(46, 51, 56, 0.09)',
- },
-
- pressed: {
- textColor: appThemes.light.text.baseColor,
- backgroundColor: 'rgba(46, 51, 56, 0.13)',
- },
-
- outline: {
- color: appThemes.light.primary.color,
- },
- },
- primary: {
- textColor: appThemes.light.primary.color,
- backgroundColor: 'transparent',
-
- hover: {
- textColor: appThemes.light.primary.colorHover,
- backgroundColor: 'rgba(46, 51, 56, 0.09)',
- },
-
- pressed: {
- textColor: appThemes.light.primary.colorPressed,
- backgroundColor: 'rgba(46, 51, 56, 0.13)',
- },
-
- outline: {
- color: appThemes.light.primary.color,
- },
- },
- warning: {
- textColor: appThemes.light.warning.color,
+ hoverBackground: theme.default.colorHover,
+ pressedBackground: theme.default.colorPressed,
+ }),
+ primary: createState({
+ textColor: theme.primary.color,
backgroundColor: 'transparent',
-
- hover: {
- textColor: appThemes.light.warning.colorHover,
- backgroundColor: 'rgba(46, 51, 56, 0.09)',
- },
-
- pressed: {
- textColor: appThemes.light.warning.colorPressed,
- backgroundColor: 'rgba(46, 51, 56, 0.13)',
- },
-
- outline: {
- color: appThemes.light.warning.color,
- },
- },
+ hoverBackground: theme.primary.colorFaded,
+ pressedBackground: darken(theme.primary.colorFaded, 30),
+ }),
+ warning: createState({
+ textColor: theme.text.baseColor,
+ backgroundColor: theme.warning.color,
+ hoverBackground: theme.warning.colorHover,
+ pressedBackground: theme.warning.colorPressed,
+ }),
},
- },
+ };
+};
+
+export const { useTheme } = defineThemes({
+ dark: createTheme({ style: 'dark' }),
+ light: createTheme({ style: 'light' }),
});
diff --git a/src/ui/c-button/c-button.vue b/src/ui/c-button/c-button.vue
index f52a069..2cbc4fd 100644
--- a/src/ui/c-button/c-button.vue
+++ b/src/ui/c-button/c-button.vue
@@ -14,6 +14,7 @@
<script lang="ts" setup>
import type { RouteLocationRaw } from 'vue-router';
import { useTheme } from './c-button.theme';
+import { useAppTheme } from '../theme/themes';
const props = withDefaults(
defineProps<{
@@ -56,11 +57,11 @@ const tag = computed(() => {
}
return 'button';
});
+const appTheme = useAppTheme();
</script>
<style lang="less" scoped>
.c-button {
- margin: 0;
line-height: 1;
font-family: inherit;
font-size: inherit;
@@ -103,7 +104,7 @@ const tag = computed(() => {
}
&:focus {
- outline: 2px solid v-bind('variantTheme.outline.color');
+ outline: 1px solid v-bind('appTheme.primary.color');
}
&.disabled {
diff --git a/src/ui/c-card/c-card.demo.vue b/src/ui/c-card/c-card.demo.vue
new file mode 100644
index 0000000..6d81ee6
--- /dev/null
+++ b/src/ui/c-card/c-card.demo.vue
@@ -0,0 +1,13 @@
+<template>
+ <div>
+ <h2>Default</h2>
+
+ <c-card title="Title">
+ Lorem ipsum, dolor sit amet consectetur adipisicing elit. Repudiandae ipsa reiciendis facilis officia nulla.
+ Laboriosam cumque molestias excepturi doloribus nulla nemo quod ratione rerum possimus. Excepturi nihil possimus
+ error itaque.
+ </c-card>
+ </div>
+</template>
+
+<script lang="ts" setup></script>
diff --git a/src/ui/c-link/c-link.demo.vue b/src/ui/c-link/c-link.demo.vue
new file mode 100644
index 0000000..a655f11
--- /dev/null
+++ b/src/ui/c-link/c-link.demo.vue
@@ -0,0 +1,12 @@
+<template>
+ <div>
+ <h2>Default</h2>
+ <c-link mx-1> Link </c-link>
+ </div>
+</template>
+
+<script lang="ts" setup>
+import CLink from './c-link.vue';
+</script>
+
+<style lang="less" scoped></style>
diff --git a/src/ui/c-link/c-link.vue b/src/ui/c-link/c-link.vue
index df10120..a7d1b83 100644
--- a/src/ui/c-link/c-link.vue
+++ b/src/ui/c-link/c-link.vue
@@ -16,7 +16,15 @@ const props = defineProps<{
const { href, to } = toRefs(props);
const theme = useTheme();
-const tag = computed(() => (href?.value ? 'a' : RouterLink));
+const tag = computed(() => {
+ if (href?.value) {
+ return 'a';
+ }
+ if (to?.value) {
+ return RouterLink;
+ }
+ return 'span';
+});
</script>
<style lang="less" scoped>
diff --git a/src/ui/color/color.models.test.ts b/src/ui/color/color.models.test.ts
new file mode 100644
index 0000000..dc59fa8
--- /dev/null
+++ b/src/ui/color/color.models.test.ts
@@ -0,0 +1,40 @@
+import { describe, test, expect } from 'vitest';
+import { darken, lighten, setOpacity } from './color.models';
+
+describe('color models', () => {
+ describe('lighten', () => {
+ test('lightens a color', () => {
+ expect(lighten('#000000', 10)).toBe('#0a0a0a');
+ expect(lighten('#000000', 20)).toBe('#141414');
+ expect(lighten('#ffffff', 30)).toBe('#ffffff');
+ });
+
+ test('lightens a color with alpha', () => {
+ expect(lighten('#00000080', 10)).toBe('#0a0a0a80');
+ expect(lighten('#00000080', 20)).toBe('#14141480');
+ expect(lighten('#ffffff80', 30)).toBe('#ffffff80');
+ });
+ });
+
+ describe('darken', () => {
+ test('darkens a color', () => {
+ expect(darken('#ffffff', 10)).toBe('#f5f5f5');
+ expect(darken('#ffffff', 20)).toBe('#ebebeb');
+ expect(darken('#000000', 30)).toBe('#000000');
+ });
+
+ test('darkens a color with alpha', () => {
+ expect(darken('#ffffff80', 10)).toBe('#f5f5f580');
+ });
+ });
+
+ describe('setOpacity', () => {
+ test('sets the opacity of a color', () => {
+ expect(setOpacity('#000000', 0.5)).toBe('#00000080');
+ });
+
+ test('sets the opacity of a color with alpha', () => {
+ expect(setOpacity('#00000000', 0.5)).toBe('#00000080');
+ });
+ });
+});
diff --git a/src/ui/color/color.models.ts b/src/ui/color/color.models.ts
new file mode 100644
index 0000000..5b4c79b
--- /dev/null
+++ b/src/ui/color/color.models.ts
@@ -0,0 +1,33 @@
+export { lighten, darken, setOpacity };
+
+const clampHex = (value: number) => Math.max(0, Math.min(255, Math.round(value)));
+
+function lighten(color: string, amount: number): string {
+ const alpha = color.length === 9 ? color.slice(7) : '';
+ const num = parseInt(color.slice(1, 7), 16);
+
+ const r = clampHex(((num >> 16) & 255) + amount);
+ const g = clampHex(((num >> 8) & 255) + amount);
+ const b = clampHex((num & 255) + amount);
+
+ return `#${((r << 16) | (g << 8) | b).toString(16).padStart(6, '0')}${alpha}`;
+}
+
+function darken(color: string, amount: number): string {
+ return lighten(color, -amount);
+}
+
+function setOpacity(color: string, opacity: number): string {
+ const alpha = clampHex(Math.round(opacity * 255))
+ .toString(16)
+ .padStart(2, '0');
+
+ if (color.length === 7) {
+ return `${color}${alpha}`;
+ }
+
+ if (color.length === 9) {
+ return `${color.slice(0, 7)}${alpha}`;
+ }
+ throw new Error('Invalid hex color');
+}
diff --git a/src/ui/demo/demo-wrapper.vue b/src/ui/demo/demo-wrapper.vue
new file mode 100644
index 0000000..cc16a00
--- /dev/null
+++ b/src/ui/demo/demo-wrapper.vue
@@ -0,0 +1,33 @@
+<template>
+ <div mt-2 w-full p-8>
+ <h1>c-lib components</h1>
+
+ <div flex>
+ <div w-30 b-r b-gray b-op-10 b-r-solid pr-4>
+ <c-button
+ v-for="{ name } of demoRoutes"
+ :key="name"
+ variant="text"
+ :to="{ name }"
+ w-full
+ important:justify-start
+ :type="route.name === name ? 'primary' : 'default'"
+ >
+ {{ name }}
+ </c-button>
+ </div>
+
+ <div flex-1 pl-4>
+ <router-view />
+ </div>
+ </div>
+ </div>
+</template>
+
+<script lang="ts" setup>
+import { demoRoutes } from './demo.routes';
+
+const route = useRoute();
+</script>
+
+<style lang="less" scoped></style>
diff --git a/src/ui/demo/demo.routes.ts b/src/ui/demo/demo.routes.ts
new file mode 100644
index 0000000..0e9a9e4
--- /dev/null
+++ b/src/ui/demo/demo.routes.ts
@@ -0,0 +1,25 @@
+import type { RouteRecordRaw } from 'vue-router';
+
+const demoPages = import.meta.glob('../*/*.demo.vue');
+
+export const demoRoutes = Object.keys(demoPages).map((path) => {
+ const [, , fileName] = path.split('/');
+ const name = fileName.split('.').shift();
+
+ console.log(path);
+
+ return {
+ path: name,
+ name,
+ component: () => import(/* @vite-ignore */ path),
+ } as RouteRecordRaw;
+});
+
+export const routes = [
+ {
+ path: '/c-lib',
+ name: 'c-lib',
+ children: demoRoutes,
+ component: () => import('./demo-wrapper.vue'),
+ },
+];
diff --git a/src/ui/theme/themes.ts b/src/ui/theme/themes.ts
index eca63db..18b2c8d 100644
--- a/src/ui/theme/themes.ts
+++ b/src/ui/theme/themes.ts
@@ -3,16 +3,20 @@ import { defineThemes } from './theme.models';
export const { themes: appThemes, useTheme: useAppTheme } = defineThemes({
light: {
text: {
- baseColor: 'rgb(51, 54, 57)',
- mutedColor: 'rgba(118, 124, 130)',
+ baseColor: '#333639',
+ mutedColor: '#767c82',
+ },
+ default: {
+ color: 'rgba(46, 51, 56, 0.05)',
+ colorHover: 'rgba(46, 51, 56, 0.09)',
+ colorPressed: 'rgba(46, 51, 56, 0.22)',
},
-
primary: {
color: '#18a058',
colorHover: '#1ea54c',
colorPressed: '#0C7A43',
+ colorFaded: '#18a0582f',
},
-
warning: {
color: '#f59e0b',
colorHover: '#f59e0b',
@@ -33,14 +37,19 @@ export const { themes: appThemes, useTheme: useAppTheme } = defineThemes({
},
dark: {
text: {
- baseColor: 'rgba(255, 255, 255, 0.82)',
- mutedColor: 'rgba(255, 255, 255, 0.5)',
+ baseColor: '#ffffffd1',
+ mutedColor: '#ffffff80',
+ },
+ default: {
+ color: 'rgba(255, 255, 255, 0.08)',
+ colorHover: 'rgba(255, 255, 255, 0.12)',
+ colorPressed: 'rgba(255, 255, 255, 0.24)',
},
-
primary: {
color: '#1ea54c',
colorHover: '#36AD6A',
colorPressed: '#0C7A43',
+ colorFaded: '#18a0582f',
},
warning: {
color: '#f59e0b',