@4verburga/alpine-spanishplus 1.6.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,123 @@
1
+ <script setup lang="ts">
2
+ import type { PropType } from 'vue'
3
+ import type { Field } from '../../types/contact'
4
+ const alpine = useAppConfig().alpine
5
+
6
+ const { FORMSPREE_URL } = useRuntimeConfig().public
7
+
8
+ if (!FORMSPREE_URL) {
9
+ console.warn('No FORMSPREE_URL provided')
10
+ }
11
+
12
+ const status = ref()
13
+
14
+ const props = defineProps({
15
+ submitButtonText: {
16
+ type: String,
17
+ default: 'Send message'
18
+ },
19
+ fields: {
20
+ type: Array as PropType<Field[]>,
21
+ default: () => [
22
+ {
23
+ type: 'text',
24
+ model: 'name',
25
+ name: 'Name',
26
+ placeholder: 'Your name',
27
+ required: true,
28
+ layout: 'default'
29
+ },
30
+ {
31
+ type: 'email',
32
+ model: 'email',
33
+ name: 'Email',
34
+ placeholder: 'Your email',
35
+ required: true,
36
+ layout: 'default'
37
+ },
38
+ {
39
+ type: 'text',
40
+ model: 'text',
41
+ name: 'Subject',
42
+ required: false,
43
+ layout: 'default'
44
+ },
45
+ {
46
+ type: 'textarea',
47
+ model: 'message',
48
+ name: 'Message',
49
+ placeholder: 'Your message',
50
+ required: true,
51
+ layout: 'big'
52
+ }
53
+ ]
54
+ }
55
+ })
56
+
57
+ const form = reactive(props.fields.map(v => ({ ...v, data: '' })))
58
+
59
+ const onSend = async (e: any) => {
60
+ e.preventDefault()
61
+ const data = new FormData(e.target)
62
+
63
+ status.value = 'Sending...'
64
+
65
+ fetch(e.target.action, {
66
+ method: e.target.method,
67
+ body: data,
68
+ headers: {
69
+ 'Accept': 'application/json'
70
+ }
71
+ }).then(response => {
72
+ if (response.ok) {
73
+ status.value = alpine.form.successMessage
74
+ e.target.reset()
75
+ } else {
76
+ // Handle errors from API
77
+ response.json().then(data => {
78
+ if (Object.hasOwn(data, 'errors')) {
79
+ status.value = data["errors"][0].message
80
+ console.error(data["errors"].map((error: any) => error["message"]).join(", "))
81
+ setTimeout(() => {
82
+ status.value = 'Send message'
83
+ }, 2000)
84
+ } else {
85
+ console.error("There was a problem submitting your form")
86
+ }
87
+ })
88
+ }
89
+ }).catch(() => {
90
+ // Catch all other errors
91
+ console.error("There was a problem submitting your form")
92
+ })
93
+ }
94
+
95
+ </script>
96
+
97
+ <template>
98
+ <form class="contact-form" method="POST" :action="FORMSPREE_URL" @submit="onSend">
99
+ <div class="inputs">
100
+ <Input v-for="(field, index) in form" :key="index" v-model="field.data" :field="field" />
101
+ </div>
102
+ <div>
103
+ <Button type="submit" :disabled="!FORMSPREE_URL">
104
+ {{ status ? status : submitButtonText }}
105
+ </Button>
106
+ </div>
107
+ </form>
108
+ </template>
109
+
110
+ <style scoped lang="ts">
111
+ css({
112
+ '.contact-form': {
113
+ '.inputs': {
114
+ display: 'grid',
115
+ gridAutoFlow: 'row',
116
+ gridTemplateColumns: 'repeat(1, minmax(0, 1fr))',
117
+ gap: '{space.8}',
118
+ marginBottom: '{space.8}',
119
+ maxWidth: '{size.md}'
120
+ }
121
+ }
122
+ })
123
+ </style>
@@ -0,0 +1,49 @@
1
+ <script setup lang="ts">
2
+ import type { PropType } from 'vue'
3
+
4
+ defineProps({
5
+ images: {
6
+ type: Array as PropType<string[]>,
7
+ default: () => []
8
+ }
9
+ })
10
+ </script>
11
+
12
+ <template>
13
+ <section class="gallery">
14
+ <div
15
+ class="layout"
16
+ >
17
+ <NuxtImg
18
+ v-for="(image, index) in images"
19
+ :key="index"
20
+ :src="image"
21
+ :width="16"
22
+ :height="9"
23
+ />
24
+ </div>
25
+ </section>
26
+ </template>
27
+
28
+ <style scoped lang="ts">
29
+ css({
30
+ '.gallery': {
31
+ '.layout': {
32
+ display: 'grid',
33
+ gap: '{space.8}',
34
+ my: '{space.16}',
35
+ '--cols': 1,
36
+ gridTemplateColumns: 'repeat(var(--cols), minmax(0, 1fr))',
37
+ '@md': {
38
+ '--cols': (props) => props.images.length < 2 ? props.images.length : 2
39
+ },
40
+ img: {
41
+ objectFit: 'cover',
42
+ width: '100%',
43
+ aspectRatio: '16 / 9',
44
+ borderRadius: '{radii.md}'
45
+ }
46
+ }
47
+ }
48
+ })
49
+ </style>
@@ -0,0 +1,77 @@
1
+ <script setup lang="ts">
2
+ defineProps({
3
+ image: {
4
+ type: String,
5
+ default: null
6
+ },
7
+ imageAlt: {
8
+ type: String,
9
+ default: 'Hero Image'
10
+ },
11
+ imagePosition: {
12
+ type: String,
13
+ default: 'right'
14
+ }
15
+ })
16
+ </script>
17
+
18
+ <template>
19
+ <section class="hero">
20
+ <div class="layout">
21
+ <div class="content">
22
+ <div class="title">
23
+ <ContentSlot :use="$slots.title" unwrap="p">
24
+ Hero title
25
+ </ContentSlot>
26
+ </div>
27
+ <div class="description">
28
+ <ContentSlot :use="$slots.description" unwrap="p">
29
+ Hero description
30
+ </ContentSlot>
31
+ </div>
32
+ </div>
33
+ <NuxtImg
34
+ v-if="image"
35
+ :class="imagePosition"
36
+ :src="image"
37
+ :alt="imageAlt"
38
+ :width="16"
39
+ :height="9"
40
+ />
41
+ </div>
42
+ </section>
43
+ </template>
44
+
45
+ <style scoped lang="ts">
46
+ css({
47
+ '.hero': {
48
+ '.layout': {
49
+ display: 'grid',
50
+ gridTemplateColumns: 'repeat(1, minmax(0, 1fr))',
51
+ gap: '{space.8}',
52
+ '@lg': {
53
+ gridTemplateColumns: 'repeat(2, minmax(0, 1fr))',
54
+ },
55
+ '.title': {
56
+ fontSize: '{text.4xl.fontSize}',
57
+ lineHeight: '{text.4xl.lineHeight}',
58
+ fontWeight: '{fontWeight.bold}',
59
+ },
60
+ '.description': {
61
+ marginTop: '{space.3}',
62
+ fontSize: '{text.xl.fontSize}',
63
+ lineHeight: '{text.xl.lineHeight}',
64
+ },
65
+ img: {
66
+ width: '100%',
67
+ aspectRatio: '16 / 9',
68
+ objectFit: 'cover',
69
+ borderRadius: '{radii.md}',
70
+ '&.left': {
71
+ order: -1
72
+ }
73
+ },
74
+ }
75
+ }
76
+ })
77
+ </style>
@@ -0,0 +1,87 @@
1
+ <script setup lang="ts">
2
+ import type { PropType } from 'vue'
3
+ import type { Field } from '../types/contact'
4
+
5
+ defineEmits(['update:modelValue'])
6
+
7
+ defineProps({
8
+ modelValue: {
9
+ type: String,
10
+ required: true
11
+ },
12
+ field: {
13
+ type: Object as PropType<Field>,
14
+ required: true,
15
+ validator: (value: Field) => {
16
+ if (!value.name) {
17
+ return false
18
+ }
19
+ return true
20
+ }
21
+ }
22
+ })
23
+
24
+ </script>
25
+
26
+ <template>
27
+ <div>
28
+ <label :for="field.name">
29
+ {{ field.label }}
30
+ </label>
31
+ <input
32
+ v-if="field.type !== 'textarea'"
33
+ :id="field.name"
34
+ :name="field.name"
35
+ :value="modelValue"
36
+ :type="field.type ? field.type : 'text'"
37
+ :placeholder="field.placeholder ? field.placeholder : ''"
38
+ @input="$emit('update:modelValue', $event.target.value)"
39
+ >
40
+ <textarea
41
+ v-else
42
+ :id="field.name"
43
+ :name="field.name"
44
+ :value="modelValue"
45
+ :type="field.type ? field.type : 'text'"
46
+ :placeholder="field.placeholder ? field.placeholder : ''"
47
+ @input="$emit('update:modelValue', $event.target.value)"
48
+ />
49
+ </div>
50
+ </template>
51
+
52
+ <style scoped lang="ts">
53
+ css({
54
+ div: {
55
+ label: {
56
+ display: 'block',
57
+ fontSize: '{text.base.fontSize}',
58
+ lineHeight: '{text.base.lineHeight}',
59
+ fontWeight: '{fontWeight.semibold}',
60
+ marginBottom: '{space.2}',
61
+ cursor: 'pointer',
62
+ },
63
+ 'input, textarea': {
64
+ backgroundColor: 'transparent',
65
+ outline: 'none',
66
+ border: '1px solid {color.gray.300}',
67
+ borderRadius: '{radii.sm}',
68
+ padding: '{space.2} {space.4}',
69
+ width: '100%',
70
+ caretColor: '{color.gray.500}',
71
+ '&:focus': {
72
+ borderColor: '{color.gray.700}'
73
+ },
74
+ '@dark': {
75
+ borderColor: '{color.gray.700}',
76
+ '&:focus': {
77
+ borderColor: '{color.gray.400}'
78
+ },
79
+ }
80
+ },
81
+ textarea: {
82
+ resize: 'none',
83
+ height: '{space.48}'
84
+ }
85
+ }
86
+ })
87
+ </style>
@@ -0,0 +1,8 @@
1
+ export const formatDate = (date: string) => {
2
+ return new Date(date).toLocaleDateString('es', {
3
+ year: 'numeric',
4
+ month: 'long',
5
+ day: 'numeric'
6
+ })
7
+ }
8
+
@@ -0,0 +1,131 @@
1
+ <template>
2
+ <article ref="article">
3
+ <!-- TODO: could be refactored as a transparent ButtonLink -->
4
+ <NuxtLink
5
+ :to="parentPath"
6
+ class="back"
7
+ >
8
+ <Icon name="ph:arrow-left" />
9
+ <span>
10
+ Back
11
+ </span>
12
+ </NuxtLink>
13
+ <header>
14
+ <h1
15
+ v-if="page?.title"
16
+ class="title"
17
+ >
18
+ {{ page.title }}
19
+ </h1>
20
+ <time
21
+ v-if="page?.date"
22
+ :datetime="page.date"
23
+ >
24
+ {{ formatDate(page.date) }}
25
+ </time>
26
+ </header>
27
+
28
+ <div class="prose">
29
+ <slot />
30
+ <div
31
+ v-if="alpine?.backToTop"
32
+ class="back-to-top"
33
+ >
34
+ <ProseA @click.prevent.stop="onBackToTop">
35
+ {{ alpine?.backToTop?.text || 'Back to top' }}
36
+ <Icon :name="alpine?.backToTop?.icon || 'material-symbols:arrow-upward'" />
37
+ </ProseA>
38
+ </div>
39
+ </div>
40
+ </article>
41
+ </template>
42
+
43
+ <script setup lang="ts">
44
+ const { page } = useContent()
45
+ const route = useRoute()
46
+ const alpine = useAppConfig().alpine
47
+
48
+ const article = ref<HTMLElement | null>(null)
49
+
50
+ if (page.value) {
51
+ const linkArray = []
52
+ const metaArray = []
53
+
54
+ if (page.value.cover) {
55
+ metaArray.push({ property: 'og:image', content: page.value.cover })
56
+ }
57
+ if (page.value.canonical) {
58
+ linkArray.push({ rel: 'canonical', href: page.value.canonical })
59
+ }
60
+ useHead({
61
+ meta: metaArray,
62
+ link: linkArray
63
+ })
64
+ }
65
+
66
+ const parentPath = computed(
67
+ () => {
68
+ const pathTabl = route.path.split('/')
69
+ pathTabl.pop()
70
+ return pathTabl.join('/')
71
+ }
72
+ )
73
+
74
+ const onBackToTop = () => {
75
+ article.value?.scrollIntoView({
76
+ behavior: 'smooth'
77
+ })
78
+ }
79
+ </script>
80
+
81
+ <style scoped lang="ts">
82
+ css({
83
+ article: {
84
+ maxWidth: '{alpine.readableLine}',
85
+ mx: 'auto',
86
+ py: '{space.4}',
87
+ '@sm': {
88
+ py: '{space.12}',
89
+ },
90
+ '.back': {
91
+ display: 'inline-flex',
92
+ alignItems: 'center',
93
+ fontSize: '{text.lg.fontSize}',
94
+ borderBottom: '1px solid {elements.border.secondary.static}',
95
+ '& :deep(svg)': {
96
+ width: '{size.16}',
97
+ height: '{size.16}',
98
+ marginRight: '{space.2}'
99
+ }
100
+ },
101
+ header: {
102
+ marginTop: '{space.16}',
103
+ marginBottom: '{space.12}',
104
+ },
105
+ '.title': {
106
+ fontSize: '{text.5xl.fontSize}',
107
+ lineHeight: '{text.5xl.lineHeight}',
108
+ fontWeight: '{fontWeight.semibold}',
109
+ marginBottom: '{space.4}'
110
+ },
111
+ time: {
112
+ color: '{elements.text.secondary.color.static}'
113
+ },
114
+ '.prose': {
115
+ '.back-to-top': {
116
+ display: 'flex',
117
+ justifyContent: 'flex-end',
118
+ alignItems: 'center',
119
+ width: '100%',
120
+ a: {
121
+ cursor: 'pointer',
122
+ fontSize: '{text.lg.fontSize}'
123
+ }
124
+ },
125
+ '& :deep(h1)': {
126
+ display: 'none'
127
+ },
128
+ }
129
+ }
130
+ })
131
+ </style>
@@ -0,0 +1,6 @@
1
+ <template>
2
+ <main>
3
+ <slot />
4
+ </main>
5
+ </template>
6
+
@@ -0,0 +1,5 @@
1
+ <template>
2
+ <main>
3
+ <slot />
4
+ </main>
5
+ </template>
package/nuxt.config.ts ADDED
@@ -0,0 +1,90 @@
1
+ import { createResolver, logger, defineNuxtModule } from '@nuxt/kit'
2
+ import { $fetch } from 'ofetch'
3
+ import { version } from './package.json'
4
+
5
+ const { resolve } = createResolver(import.meta.url)
6
+
7
+ // That allows to overwrite these dependencies paths via `.env` for local development
8
+ const envModules = {
9
+ tokens: process?.env?.THEME_DEV_TOKENS_PATH || '@nuxt-themes/tokens',
10
+ elements: process?.env?.THEME_DEV_ELEMENTS_PATH || '@nuxt-themes/elements',
11
+ studio: process?.env?.THEME_DEV_STUDIO_PATH || '@nuxthq/studio',
12
+ typography: process?.env?.THEME_DEV_TYPOGRAPHY_PATH || '@nuxt-themes/typography'
13
+ }
14
+
15
+ const updateModule = defineNuxtModule({
16
+ meta: {
17
+ name: '@nuxt-themes/alpine'
18
+ },
19
+ setup (_, nuxt) {
20
+ if (nuxt.options.dev) {
21
+ $fetch('https://registry.npmjs.org/@nuxt-themes/alpine/latest').then((release) => {
22
+ if (release.version > version) {
23
+ logger.info(`A new version of Alpine (v${release.version}) is available: https://github.com/nuxt-themes/alpine/releases/latest`)
24
+ }
25
+ }).catch(() => {})
26
+ }
27
+ }
28
+ })
29
+
30
+ // https://v3.nuxtjs.org/api/configuration/nuxt.config
31
+ export default defineNuxtConfig({
32
+ app: {
33
+ head: {
34
+ htmlAttrs: {
35
+ lang: 'en'
36
+ }
37
+ }
38
+ },
39
+ extends: [envModules.typography, envModules.elements],
40
+ runtimeConfig: {
41
+ public: {
42
+ FORMSPREE_URL: process.env.FORMSPREE_URL
43
+ }
44
+ },
45
+ pages: true,
46
+ modules: [
47
+ envModules.tokens,
48
+ envModules.studio,
49
+ '@nuxt/content',
50
+ updateModule as any
51
+ ],
52
+ components: [
53
+ { path: resolve('./components'), global: true },
54
+ { path: resolve('./components/content'), global: true },
55
+ { path: resolve('./components/data-entry'), global: true }
56
+ ],
57
+ css: [
58
+ resolve('./assets/main.css'),
59
+ ],
60
+ colorMode: {
61
+ classSuffix: ''
62
+ },
63
+ pinceau: {
64
+ studio: true
65
+ },
66
+ content: {
67
+ documentDriven: true,
68
+ navigation: {
69
+ fields: ['navTitle']
70
+ },
71
+ highlight: {
72
+ theme: {
73
+ default: 'github-light',
74
+ dark: 'github-dark'
75
+ },
76
+ preload: ['json', 'js', 'ts', 'html', 'css', 'vue', 'diff', 'shell', 'markdown', 'yaml', 'bash', 'ini', 'c', 'cpp']
77
+ }
78
+ },
79
+ experimental: {
80
+ inlineSSRStyles: false
81
+ },
82
+ typescript: {
83
+ includeWorkspace: true
84
+ },
85
+ nitro: {
86
+ prerender: {
87
+ ignore: ['/__pinceau_tokens_config.json', '/__pinceau_tokens_schema.json']
88
+ }
89
+ },
90
+ })