Skip to content

Commit 8a6513c

Browse files
committed
feat: add my first post
1 parent d3dc412 commit 8a6513c

21 files changed

+6442
-2159
lines changed
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
<template>
2+
<div v-if="isMermaid" class="mermaid-wrapper my-4">
3+
<div
4+
class="mermaid-container group relative bg-stone-50 border border-stone-200 rounded-lg p-4"
5+
>
6+
<div ref="mermaidRef" class="mermaid"></div>
7+
<Icon
8+
name="lucide:expand"
9+
class="w-10 h-10 p-2 absolute top-3 right-3 z-50 cursor-pointer rounded-lg bg-stone-800 border-2 border-stone-600 text-white shadow-lg hover:bg-stone-900 opacity-0 group-hover:opacity-100 transition-all"
10+
title="Ver em tela cheia"
11+
@click="isModalOpen = true"
12+
/>
13+
</div>
14+
15+
<Dialog v-model:open="isModalOpen">
16+
<DialogScrollContent class="max-w-[95vw] h-[90vh] p-6">
17+
<DialogTitle class="sr-only">Diagrama Mermaid</DialogTitle>
18+
<div
19+
class="modal-diagram w-full h-full flex items-center justify-center"
20+
v-html="svgContent"
21+
></div>
22+
</DialogScrollContent>
23+
</Dialog>
24+
</div>
25+
<pre v-else :class="$attrs.class"><slot /></pre>
26+
</template>
27+
28+
<script setup lang="ts">
29+
import {
30+
Dialog,
31+
DialogScrollContent,
32+
DialogTitle,
33+
} from "@/components/ui/dialog";
34+
import { computed, onMounted, ref, useSlots, type VNode } from "vue";
35+
36+
type VNodeChild = VNode | string | number | boolean | null | undefined;
37+
const props = defineProps<{
38+
language?: string;
39+
code?: string;
40+
}>();
41+
42+
const slots = useSlots();
43+
const mermaidRef = ref<HTMLElement | null>(null);
44+
const isModalOpen = ref(false);
45+
const svgContent = ref("");
46+
47+
const isMermaid = computed(() => props.language === "mermaid");
48+
49+
const getMermaidCode = (): string => {
50+
if (props.code) return props.code;
51+
52+
const slot = slots.default?.();
53+
if (!slot) return "";
54+
55+
const extractText = (node: VNodeChild): string => {
56+
if (node == null || typeof node === "boolean") return "";
57+
if (typeof node === "string") return node;
58+
if (typeof node === "number") return String(node);
59+
60+
const vnode = node as VNode;
61+
if (vnode.children) {
62+
if (typeof vnode.children === "string") return vnode.children;
63+
if (Array.isArray(vnode.children)) {
64+
return (vnode.children as VNodeChild[]).map(extractText).join("");
65+
}
66+
}
67+
return "";
68+
};
69+
70+
return slot.map(extractText).join("");
71+
};
72+
73+
onMounted(async () => {
74+
if (isMermaid.value && mermaidRef.value) {
75+
const mermaid = (await import("mermaid")).default;
76+
mermaid.initialize({
77+
startOnLoad: false,
78+
theme: "neutral",
79+
securityLevel: "loose",
80+
});
81+
82+
const code = getMermaidCode();
83+
const { svg } = await mermaid.render(
84+
`mermaid-${Math.random().toString(36).substr(2, 9)}`,
85+
code
86+
);
87+
mermaidRef.value.innerHTML = svg;
88+
svgContent.value = svg;
89+
}
90+
});
91+
</script>
92+
93+
<style scoped>
94+
.mermaid-wrapper {
95+
position: relative;
96+
}
97+
98+
.modal-diagram :deep(svg) {
99+
width: 100% !important;
100+
height: auto !important;
101+
max-height: 100%;
102+
}
103+
</style>

app/components/sidebar/Sidebar.vue

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
<template>
22
<aside class="w-[180px]">
33
<div>
4-
<!-- Avatar -->
54
<div class="mb-8 pl-1">
65
<NuxtLink to="/" class="block">
76
<div
@@ -16,7 +15,6 @@
1615
</NuxtLink>
1716
</div>
1817

19-
<!-- Navigation Menu -->
2018
<nav class="space-y-0.5">
2119
<NuxtLink
2220
v-for="item in menuItems"
@@ -40,9 +38,10 @@
4038
const route = useRoute();
4139
4240
const menuItems = [
43-
{ label: "Home", to: "/" },
41+
{ label: "Resumo", to: "/" },
4442
{ label: "Sobre mim", to: "/sobre" },
4543
{ label: "Meus projetos", to: "/projetos" },
44+
{ label: "Blog", to: "/blog" },
4645
{ label: "Contato", to: "/contato" },
4746
];
4847
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<script setup lang="ts">
2+
import type { DialogRootEmits, DialogRootProps } from "reka-ui"
3+
import { DialogRoot, useForwardPropsEmits } from "reka-ui"
4+
5+
const props = defineProps<DialogRootProps>()
6+
const emits = defineEmits<DialogRootEmits>()
7+
8+
const forwarded = useForwardPropsEmits(props, emits)
9+
</script>
10+
11+
<template>
12+
<DialogRoot
13+
v-slot="slotProps"
14+
data-slot="dialog"
15+
v-bind="forwarded"
16+
>
17+
<slot v-bind="slotProps" />
18+
</DialogRoot>
19+
</template>
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<script setup lang="ts">
2+
import type { DialogCloseProps } from "reka-ui"
3+
import { DialogClose } from "reka-ui"
4+
5+
const props = defineProps<DialogCloseProps>()
6+
</script>
7+
8+
<template>
9+
<DialogClose
10+
data-slot="dialog-close"
11+
v-bind="props"
12+
>
13+
<slot />
14+
</DialogClose>
15+
</template>
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<script setup lang="ts">
2+
import type { DialogContentEmits, DialogContentProps } from "reka-ui"
3+
import type { HTMLAttributes } from "vue"
4+
import { reactiveOmit } from "@vueuse/core"
5+
import { X } from "lucide-vue-next"
6+
import {
7+
DialogClose,
8+
DialogContent,
9+
DialogPortal,
10+
useForwardPropsEmits,
11+
} from "reka-ui"
12+
import { cn } from "@/lib/utils"
13+
import DialogOverlay from "./DialogOverlay.vue"
14+
15+
defineOptions({
16+
inheritAttrs: false,
17+
})
18+
19+
const props = withDefaults(defineProps<DialogContentProps & { class?: HTMLAttributes["class"], showCloseButton?: boolean }>(), {
20+
showCloseButton: true,
21+
})
22+
const emits = defineEmits<DialogContentEmits>()
23+
24+
const delegatedProps = reactiveOmit(props, "class")
25+
26+
const forwarded = useForwardPropsEmits(delegatedProps, emits)
27+
</script>
28+
29+
<template>
30+
<DialogPortal>
31+
<DialogOverlay />
32+
<DialogContent
33+
data-slot="dialog-content"
34+
v-bind="{ ...$attrs, ...forwarded }"
35+
:class="
36+
cn(
37+
'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg',
38+
props.class,
39+
)"
40+
>
41+
<slot />
42+
43+
<DialogClose
44+
v-if="showCloseButton"
45+
data-slot="dialog-close"
46+
class="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
47+
>
48+
<X />
49+
<span class="sr-only">Close</span>
50+
</DialogClose>
51+
</DialogContent>
52+
</DialogPortal>
53+
</template>
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<script setup lang="ts">
2+
import type { DialogDescriptionProps } from "reka-ui"
3+
import type { HTMLAttributes } from "vue"
4+
import { reactiveOmit } from "@vueuse/core"
5+
import { DialogDescription, useForwardProps } from "reka-ui"
6+
import { cn } from "@/lib/utils"
7+
8+
const props = defineProps<DialogDescriptionProps & { class?: HTMLAttributes["class"] }>()
9+
10+
const delegatedProps = reactiveOmit(props, "class")
11+
12+
const forwardedProps = useForwardProps(delegatedProps)
13+
</script>
14+
15+
<template>
16+
<DialogDescription
17+
data-slot="dialog-description"
18+
v-bind="forwardedProps"
19+
:class="cn('text-muted-foreground text-sm', props.class)"
20+
>
21+
<slot />
22+
</DialogDescription>
23+
</template>
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<script setup lang="ts">
2+
import type { HTMLAttributes } from "vue"
3+
import { cn } from "@/lib/utils"
4+
5+
const props = defineProps<{ class?: HTMLAttributes["class"] }>()
6+
</script>
7+
8+
<template>
9+
<div
10+
data-slot="dialog-footer"
11+
:class="cn('flex flex-col-reverse gap-2 sm:flex-row sm:justify-end', props.class)"
12+
>
13+
<slot />
14+
</div>
15+
</template>
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<script setup lang="ts">
2+
import type { HTMLAttributes } from "vue"
3+
import { cn } from "@/lib/utils"
4+
5+
const props = defineProps<{
6+
class?: HTMLAttributes["class"]
7+
}>()
8+
</script>
9+
10+
<template>
11+
<div
12+
data-slot="dialog-header"
13+
:class="cn('flex flex-col gap-2 text-center sm:text-left', props.class)"
14+
>
15+
<slot />
16+
</div>
17+
</template>
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<script setup lang="ts">
2+
import type { DialogOverlayProps } from "reka-ui"
3+
import type { HTMLAttributes } from "vue"
4+
import { reactiveOmit } from "@vueuse/core"
5+
import { DialogOverlay } from "reka-ui"
6+
import { cn } from "@/lib/utils"
7+
8+
const props = defineProps<DialogOverlayProps & { class?: HTMLAttributes["class"] }>()
9+
10+
const delegatedProps = reactiveOmit(props, "class")
11+
</script>
12+
13+
<template>
14+
<DialogOverlay
15+
data-slot="dialog-overlay"
16+
v-bind="delegatedProps"
17+
:class="cn('data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/80', props.class)"
18+
>
19+
<slot />
20+
</DialogOverlay>
21+
</template>
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<script setup lang="ts">
2+
import type { DialogContentEmits, DialogContentProps } from "reka-ui"
3+
import type { HTMLAttributes } from "vue"
4+
import { reactiveOmit } from "@vueuse/core"
5+
import { X } from "lucide-vue-next"
6+
import {
7+
DialogClose,
8+
DialogContent,
9+
DialogOverlay,
10+
DialogPortal,
11+
useForwardPropsEmits,
12+
} from "reka-ui"
13+
import { cn } from "@/lib/utils"
14+
15+
defineOptions({
16+
inheritAttrs: false,
17+
})
18+
19+
const props = defineProps<DialogContentProps & { class?: HTMLAttributes["class"] }>()
20+
const emits = defineEmits<DialogContentEmits>()
21+
22+
const delegatedProps = reactiveOmit(props, "class")
23+
24+
const forwarded = useForwardPropsEmits(delegatedProps, emits)
25+
</script>
26+
27+
<template>
28+
<DialogPortal>
29+
<DialogOverlay
30+
class="fixed inset-0 z-50 grid place-items-center overflow-y-auto bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0"
31+
>
32+
<DialogContent
33+
:class="
34+
cn(
35+
'relative z-50 grid w-full max-w-lg my-8 gap-4 border border-border bg-background p-6 shadow-lg duration-200 sm:rounded-lg md:w-full',
36+
props.class,
37+
)
38+
"
39+
v-bind="{ ...$attrs, ...forwarded }"
40+
@pointer-down-outside="(event) => {
41+
const originalEvent = event.detail.originalEvent;
42+
const target = originalEvent.target as HTMLElement;
43+
if (originalEvent.offsetX > target.clientWidth || originalEvent.offsetY > target.clientHeight) {
44+
event.preventDefault();
45+
}
46+
}"
47+
>
48+
<slot />
49+
50+
<DialogClose
51+
class="absolute top-4 right-4 p-0.5 transition-colors rounded-md hover:bg-secondary"
52+
>
53+
<X class="w-4 h-4" />
54+
<span class="sr-only">Close</span>
55+
</DialogClose>
56+
</DialogContent>
57+
</DialogOverlay>
58+
</DialogPortal>
59+
</template>

0 commit comments

Comments
 (0)