moulify 0.0.1 → 0.0.2

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.
Files changed (29) hide show
  1. package/README.md +25 -4
  2. package/dist/chunks/package.mjs +1 -1
  3. package/dist/module.d.mts +64 -33
  4. package/dist/module.json +1 -1
  5. package/dist/module.mjs +137 -3
  6. package/dist/runtime/app/components/Button/index.d.vue.ts +2 -0
  7. package/dist/runtime/app/components/Button/index.vue +51 -8
  8. package/dist/runtime/app/components/Button/index.vue.d.ts +2 -0
  9. package/dist/runtime/app/components/Button/types.d.ts +6 -0
  10. package/dist/runtime/app/components/Footer/index.d.vue.ts +7 -0
  11. package/dist/runtime/app/components/Footer/index.vue +75 -0
  12. package/dist/runtime/app/components/Footer/index.vue.d.ts +7 -0
  13. package/dist/runtime/app/components/Footer/types.d.ts +7 -0
  14. package/dist/runtime/app/components/Footer/types.js +0 -0
  15. package/dist/runtime/app/components/Header/index.d.vue.ts +6 -1
  16. package/dist/runtime/app/components/Header/index.vue +81 -2
  17. package/dist/runtime/app/components/Header/index.vue.d.ts +6 -1
  18. package/dist/runtime/app/components/Header/types.d.ts +10 -0
  19. package/dist/runtime/app/components/Header/types.js +0 -0
  20. package/dist/runtime/app/components/Icon/index.vue +2 -2
  21. package/dist/runtime/app/components/Icon/types.d.ts +3 -3
  22. package/dist/runtime/app/components/SocialLinks/index.d.vue.ts +4 -0
  23. package/dist/runtime/app/components/SocialLinks/index.vue +39 -0
  24. package/dist/runtime/app/components/SocialLinks/index.vue.d.ts +4 -0
  25. package/dist/runtime/app/components/SocialLinks/types.d.ts +24 -0
  26. package/dist/runtime/app/components/SocialLinks/types.js +38 -0
  27. package/dist/runtime/plugin.d.ts +7 -2
  28. package/dist/runtime/plugin.js +31 -4
  29. package/package.json +3 -1
package/README.md CHANGED
@@ -87,16 +87,37 @@ Module options are available under the `moulify` key in your `nuxt.config`:
87
87
  interface ModuleOptions {
88
88
  prefix?: string
89
89
  colors?: {
90
- primary?: string
91
- secondary?: string
92
- tertiary?: string
90
+ primary?: string | ColorPalette
91
+ secondary?: string | ColorPalette
92
+ tertiary?: string | ColorPalette
93
93
  }
94
94
  }
95
+
96
+ // Full palette (all shades from 50 to 900)
97
+ interface ColorPalette {
98
+ 50: string
99
+ 100: string
100
+ 200: string
101
+ 300: string
102
+ 400: string
103
+ 500: string
104
+ 600: string
105
+ 700: string
106
+ 800: string
107
+ 900: string
108
+ }
95
109
  ```
96
110
 
97
111
  - `prefix` (default: `'moulify'`): controls the tag names of auto-imported components.
98
112
  - For example, with `prefix: 'nc'` you will use `<nc-button>` instead of `<moulify-button>`.
99
- - `colors`: simple color tokens you can use in your own styling or future theme extensions.
113
+ - `colors`: primary, secondary, and tertiary can be:
114
+ - **A hex string** (e.g. `'#0076ff'`): a full palette with shades 50, 100, 200, … 900 is generated automatically (500 = your base color).
115
+ - **A full palette object**: provide your own `{ 50: '#…', 100: '#…', …, 900: '#…' }` for full control.
116
+
117
+ The resolved palettes are exposed as **CSS custom properties** on `:root` and via **runtime config**:
118
+
119
+ - **CSS variables**: `--moulify-primary-50` … `--moulify-primary-900`, and the same for `--moulify-secondary-*` and `--moulify-tertiary-*`. Use them in your styles, e.g. `background: var(--moulify-primary-500);`.
120
+ - **In app**: `useRuntimeConfig().public.moulify.colors` and the plugin provides `$moulifyColors` (e.g. `const { $moulifyColors } = useNuxtApp()`).
100
121
 
101
122
  ## Components
102
123
 
@@ -1,3 +1,3 @@
1
- const version = "0.0.1";
1
+ const version = "0.0.2";
2
2
 
3
3
  export { version };
package/dist/module.d.mts CHANGED
@@ -1,50 +1,81 @@
1
- import * as _nuxt_schema from '@nuxt/schema';
1
+ import * as nuxt_schema from 'nuxt/schema';
2
2
 
3
+ /**
4
+ * Global types for the Moulify Nuxt module (module options, runtime config, colors).
5
+ * Component-level types live in each component's folder (e.g. Button/types.ts).
6
+ */
3
7
  interface ModuleOptions {
4
8
  /**
5
- * Prefix used for auto-registered components.
9
+ * Colors used for the module. Each of primary, secondary, and tertiary can be:
10
+ * - A hex string (e.g. '#0076ff'): a full palette (50, 100, … 900) is generated from it.
11
+ * - A full palette object with keys 50, 100, 200, 300, 400, 500, 600, 700, 800, 900.
6
12
  *
7
13
  * Example:
8
- * - prefix: 'nc' -> <nc-button>, <nc-header>
9
- * - prefix: 'moulify' (default) -> <moulify-button>, <moulify-header>
10
- */
11
- prefix?: string;
12
- /**
13
- * Colors used for the module.
14
- *
15
- * Example:
16
- * - colors: { primary: '#000', secondary: '#fff', tertiary: '#000' }
17
- * - colors: { primary: '#000', secondary: '#fff', tertiary: '#000' }
14
+ * - colors: { primary: '#0076ff', secondary: '#000', tertiary: '#888' }
15
+ * - colors: { primary: { 50: '#eff6ff', 100: '#dbeafe', ..., 900: '#1e3a8a' } }
18
16
  */
19
17
  colors?: MoulifyModuleColors;
20
- }
21
- interface MoulifyModuleColors {
22
18
  /**
23
- * Primary color used for the module.
24
- *
25
- * Example:
26
- * - primary: '#000'
27
- * - primary: '#000'
19
+ * Social links: optional URLs per platform (twitter, github, instagram, facebook, x).
28
20
  */
29
- primary?: string;
21
+ socialLinks?: Partial<Record<'twitter' | 'github' | 'instagram' | 'facebook' | 'x', string>>;
30
22
  /**
31
- * Secondary color used for the module.
32
- *
33
- * Example:
34
- * - secondary: '#fff'
35
- * - secondary: '#fff'
23
+ * Header navigation: left, center, and right link groups.
36
24
  */
37
- secondary?: string;
25
+ header?: {
26
+ left?: Array<{
27
+ name: string;
28
+ url: string;
29
+ }>;
30
+ center?: Array<{
31
+ name: string;
32
+ url: string;
33
+ }>;
34
+ right?: Array<{
35
+ name: string;
36
+ url: string;
37
+ }>;
38
+ };
38
39
  /**
39
- * Tertiary color used for the module.
40
- *
41
- * Example:
42
- * - tertiary: '#000'
43
- * - tertiary: '#000'
40
+ * Footer config: copyright text and optional links.
44
41
  */
45
- tertiary?: string;
42
+ footer?: {
43
+ copyrightText?: string;
44
+ hasLinks?: Array<{
45
+ name: string;
46
+ url: string;
47
+ }>;
48
+ };
49
+ }
50
+ /**
51
+ * A color palette with shades from 50 (lightest) to 900 (darkest).
52
+ * 500 is typically the base color.
53
+ */
54
+ interface ColorPalette {
55
+ 50: string;
56
+ 100: string;
57
+ 200: string;
58
+ 300: string;
59
+ 400: string;
60
+ 500: string;
61
+ 600: string;
62
+ 700: string;
63
+ 800: string;
64
+ 900: string;
65
+ }
66
+ /**
67
+ * Config for a single color: either a base hex (palette auto-generated) or a full palette.
68
+ */
69
+ type ColorConfig = string | ColorPalette;
70
+ interface MoulifyModuleColors {
71
+ /** Primary color: hex string or full palette (50–900). */
72
+ primary?: ColorConfig;
73
+ /** Secondary color: hex string or full palette (50–900). */
74
+ secondary?: ColorConfig;
75
+ /** Tertiary color: hex string or full palette (50–900). */
76
+ tertiary?: ColorConfig;
46
77
  }
47
78
 
48
- declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
79
+ declare const _default: nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
49
80
 
50
81
  export { _default as default };
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "moulify",
3
3
  "configKey": "moulify",
4
- "version": "0.0.1",
4
+ "version": "0.0.2",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "unknown"
package/dist/module.mjs CHANGED
@@ -1,5 +1,121 @@
1
1
  import { defineNuxtModule, createResolver, addPlugin, addComponentsDir } from '@nuxt/kit';
2
2
 
3
+ const PALETTE_SHADES = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900];
4
+ const hexToRgb = (hex) => {
5
+ const match = hex.replace(/^#/, "").match(/^(?:([a-f\d])([a-f\d])([a-f\d])|([a-f\d]{2})([a-f\d]{2})([a-f\d]{2}))$/i);
6
+ if (!match) {
7
+ return null;
8
+ }
9
+ const [_, r1, g1, b1, r2, g2, b2] = match;
10
+ if (r2 !== void 0) {
11
+ return {
12
+ r: Number.parseInt(r2, 16),
13
+ g: Number.parseInt(g2, 16),
14
+ b: Number.parseInt(b2, 16)
15
+ };
16
+ }
17
+ return {
18
+ r: Number.parseInt(r1 + r1, 16),
19
+ g: Number.parseInt(g1 + g1, 16),
20
+ b: Number.parseInt(b1 + b1, 16)
21
+ };
22
+ };
23
+ const rgbToHsl = (r, g, b) => {
24
+ r /= 255;
25
+ g /= 255;
26
+ b /= 255;
27
+ const max = Math.max(r, g, b);
28
+ const min = Math.min(r, g, b);
29
+ let h = 0;
30
+ let s = 0;
31
+ const l = (max + min) / 2;
32
+ if (max !== min) {
33
+ const d = max - min;
34
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
35
+ switch (max) {
36
+ case r:
37
+ h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
38
+ break;
39
+ case g:
40
+ h = ((b - r) / d + 2) / 6;
41
+ break;
42
+ case b:
43
+ h = ((r - g) / d + 4) / 6;
44
+ break;
45
+ }
46
+ }
47
+ return {
48
+ h: Math.round(h * 360),
49
+ s: Math.round(s * 100),
50
+ l: Math.round(l * 100)
51
+ };
52
+ };
53
+ const hslToHex = (h, s, l) => {
54
+ s /= 100;
55
+ l /= 100;
56
+ const a = s * Math.min(l, 1 - l);
57
+ const f = (n) => {
58
+ const k = (n + h / 30) % 12;
59
+ const x = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
60
+ return Math.round(255 * x).toString(16).padStart(2, "0");
61
+ };
62
+ return `#${f(0)}${f(8)}${f(4)}`;
63
+ };
64
+ const SHADE_LIGHTNESS = {
65
+ 50: 97,
66
+ 100: 94,
67
+ 200: 89,
68
+ 300: 80,
69
+ 400: 71,
70
+ 500: 62,
71
+ // base — replaced by input
72
+ 600: 53,
73
+ 700: 45,
74
+ 800: 37,
75
+ 900: 26
76
+ };
77
+ const generateColorPalette = (baseHex) => {
78
+ const rgb = hexToRgb(baseHex.trim());
79
+ if (!rgb) {
80
+ return PALETTE_SHADES.reduce(
81
+ (acc, shade) => {
82
+ const l = SHADE_LIGHTNESS[shade];
83
+ acc[shade] = hslToHex(0, 0, l);
84
+ return acc;
85
+ },
86
+ {}
87
+ );
88
+ }
89
+ const { h, s, l: baseL } = rgbToHsl(rgb.r, rgb.g, rgb.b);
90
+ const scale = baseL / SHADE_LIGHTNESS[500];
91
+ const palette = {};
92
+ for (const shade of PALETTE_SHADES) {
93
+ let targetL;
94
+ if (shade === 500) {
95
+ targetL = baseL;
96
+ } else {
97
+ targetL = Math.round(SHADE_LIGHTNESS[shade] * scale);
98
+ targetL = Math.max(1, Math.min(99, targetL));
99
+ }
100
+ const saturation = s;
101
+ palette[shade] = hslToHex(h, saturation, targetL);
102
+ }
103
+ return palette;
104
+ };
105
+ const normalizeToPalette = (value) => {
106
+ if (value === void 0 || value === null) {
107
+ return void 0;
108
+ }
109
+ if (typeof value === "string") {
110
+ return generateColorPalette(value);
111
+ }
112
+ const keys = Object.keys(value);
113
+ if (keys.includes("50") && keys.includes("500") && keys.includes("900")) {
114
+ return value;
115
+ }
116
+ return void 0;
117
+ };
118
+
3
119
  const versionFromPackageJson = (await import('./chunks/package.mjs')).version;
4
120
  const module$1 = defineNuxtModule({
5
121
  meta: {
@@ -8,7 +124,6 @@ const module$1 = defineNuxtModule({
8
124
  version: versionFromPackageJson
9
125
  },
10
126
  defaults: {
11
- prefix: "moulify",
12
127
  colors: {
13
128
  primary: "#0076ff",
14
129
  // little element plus default blue
@@ -18,16 +133,35 @@ const module$1 = defineNuxtModule({
18
133
  // grey
19
134
  }
20
135
  },
21
- setup(options, _nuxt) {
136
+ setup(options, nuxt) {
22
137
  const resolver = createResolver(import.meta.url);
138
+ const resolvedColors = {
139
+ primary: normalizeToPalette(options.colors?.primary),
140
+ secondary: normalizeToPalette(options.colors?.secondary),
141
+ tertiary: normalizeToPalette(options.colors?.tertiary)
142
+ };
143
+ const existingMoulify = nuxt.options.runtimeConfig.public?.moulify;
144
+ const publicMoulify = {
145
+ ...existingMoulify,
146
+ colors: resolvedColors,
147
+ socialLinks: options.socialLinks ?? existingMoulify?.socialLinks,
148
+ header: options.header ?? existingMoulify?.header,
149
+ footer: options.footer ?? existingMoulify?.footer
150
+ };
151
+ nuxt.options.runtimeConfig.public = {
152
+ ...nuxt.options.runtimeConfig.public,
153
+ moulify: publicMoulify
154
+ };
23
155
  addPlugin(resolver.resolve("./runtime/plugin"));
156
+ nuxt.options.css = nuxt.options.css ?? [];
157
+ nuxt.options.css.push(resolver.resolve("./assets/generated/fonts/icons.css"));
24
158
  addComponentsDir({
25
159
  // points to src/runtime/app/components
26
160
  path: resolver.resolve("runtime/app/components"),
27
161
  // optional: control naming
28
162
  pathPrefix: false,
29
163
  // prefix controls auto-imported component names, e.g. <nc-button>, <moulify-button>
30
- prefix: options.prefix,
164
+ prefix: "moulify",
31
165
  // optional: restrict to .vue files (types.ts will be ignored anyway)
32
166
  extensions: [".vue"]
33
167
  });
@@ -6,6 +6,8 @@ type __VLS_Slots = {} & {
6
6
  declare const __VLS_base: import("vue").DefineComponent<ButtonProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<ButtonProps> & Readonly<{}>, {
7
7
  iconLeft: import("../../../../assets/generated/fonts/icons.js").IconsId;
8
8
  iconRight: import("../../../../assets/generated/fonts/icons.js").IconsId;
9
+ size: import("./types.js").ButtonSize;
10
+ variant: import("./types.js").ButtonVariant;
9
11
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
10
12
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
11
13
  declare const _default: typeof __VLS_export;
@@ -1,5 +1,6 @@
1
1
  <template>
2
- <button class="moulify-btn">
2
+ <button type="button" class="moulify-btn"
3
+ :class="[`moulify-btn--${effectiveSize}`, `moulify-btn--${effectiveVariant}`]">
3
4
  <moulify-icon v-if="props.iconLeft" :name="props.iconLeft" />
4
5
  <slot />
5
6
  <moulify-icon v-if="props.iconRight" :name="props.iconRight" />
@@ -7,22 +8,64 @@
7
8
  </template>
8
9
 
9
10
  <script setup>
11
+ import { computed } from "vue";
10
12
  const props = defineProps({
11
13
  iconLeft: { type: String, required: false, default: void 0 },
12
- iconRight: { type: String, required: false, default: void 0 }
14
+ iconRight: { type: String, required: false, default: void 0 },
15
+ size: { type: String, required: false, default: "md" },
16
+ variant: { type: String, required: false, default: "primary" }
13
17
  });
18
+ const effectiveSize = computed(() => props.size);
19
+ const effectiveVariant = computed(() => props.variant);
14
20
  </script>
15
21
 
16
22
  <style>
17
23
  .moulify-btn {
18
- background-color: black;
19
- align-items: center;
20
- color: #fff;
21
- padding: 10px;
22
- border-radius: 5px;
23
- display: flex;
24
+ display: inline-flex;
24
25
  align-items: center;
25
26
  justify-content: center;
26
27
  gap: 8px;
28
+ font-family: inherit;
29
+ font-weight: 500;
30
+ border-radius: 6px;
31
+ cursor: pointer;
32
+ transition: background-color 0.15s, border-color 0.15s, color 0.15s;
33
+ }
34
+ .moulify-btn--sm {
35
+ padding: 6px 12px;
36
+ font-size: 13px;
37
+ }
38
+ .moulify-btn--md {
39
+ padding: 10px 18px;
40
+ font-size: 14px;
41
+ }
42
+ .moulify-btn--lg {
43
+ padding: 12px 24px;
44
+ font-size: 16px;
45
+ }
46
+ .moulify-btn--primary {
47
+ background-color: var(--moulify-primary-500, #0076ff);
48
+ color: #fff;
49
+ border: 1px solid transparent;
50
+ }
51
+ .moulify-btn--primary:hover {
52
+ background-color: var(--moulify-primary-600, #0066dd);
53
+ }
54
+ .moulify-btn--outlined {
55
+ background-color: transparent;
56
+ color: var(--moulify-primary-500, #0076ff);
57
+ border: 1px solid var(--moulify-primary-500, #0076ff);
58
+ }
59
+ .moulify-btn--outlined:hover {
60
+ background-color: var(--moulify-primary-50, rgba(0, 118, 255, 0.08));
61
+ }
62
+ .moulify-btn--link {
63
+ background-color: transparent;
64
+ color: var(--moulify-primary-500, #0076ff);
65
+ border: none;
66
+ border-radius: 0;
67
+ }
68
+ .moulify-btn--link:hover {
69
+ text-decoration: underline;
27
70
  }
28
71
  </style>
@@ -6,6 +6,8 @@ type __VLS_Slots = {} & {
6
6
  declare const __VLS_base: import("vue").DefineComponent<ButtonProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<ButtonProps> & Readonly<{}>, {
7
7
  iconLeft: import("../../../../assets/generated/fonts/icons.js").IconsId;
8
8
  iconRight: import("../../../../assets/generated/fonts/icons.js").IconsId;
9
+ size: import("./types.js").ButtonSize;
10
+ variant: import("./types.js").ButtonVariant;
9
11
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
10
12
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
11
13
  declare const _default: typeof __VLS_export;
@@ -1,5 +1,11 @@
1
1
  import type { IconsId } from '../../../../assets/generated/fonts/icons.js';
2
+ export type ButtonSize = 'sm' | 'md' | 'lg';
3
+ export type ButtonVariant = 'primary' | 'outlined' | 'link';
2
4
  export interface ButtonProps {
3
5
  iconLeft?: IconsId;
4
6
  iconRight?: IconsId;
7
+ /** @default 'md' or from nuxt config */
8
+ size?: ButtonSize;
9
+ /** @default 'primary' or from nuxt config */
10
+ variant?: ButtonVariant;
5
11
  }
@@ -0,0 +1,7 @@
1
+ import type { FooterProps } from './types.js';
2
+ declare const __VLS_export: import("vue").DefineComponent<FooterProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<FooterProps> & Readonly<{}>, {
3
+ copyrightText: string;
4
+ hasLinks: import("../Header/types.js").NavLink[];
5
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
6
+ declare const _default: typeof __VLS_export;
7
+ export default _default;
@@ -0,0 +1,75 @@
1
+ <template>
2
+ <footer class="moulify-footer">
3
+ <div class="moulify-footer__inner">
4
+ <p v-if="effectiveCopyright" class="moulify-footer__copyright">
5
+ {{ effectiveCopyright }}
6
+ </p>
7
+ <nav v-if="effectiveLinks.length" class="moulify-footer__links" aria-label="Footer links">
8
+ <template v-for="(link, i) in effectiveLinks" :key="i">
9
+ <a class="moulify-footer__link" :href="link.url" target="_blank" rel="noopener noreferrer">
10
+ {{ link.name }}
11
+ </a>
12
+ <span v-if="i < effectiveLinks.length - 1" class="moulify-footer__separator" aria-hidden="true">
13
+ {{ separator }}
14
+ </span>
15
+ </template>
16
+ </nav>
17
+ </div>
18
+ </footer>
19
+ </template>
20
+
21
+ <script setup>
22
+ import { computed, inject } from "vue";
23
+ const props = defineProps({
24
+ copyrightText: { type: String, required: false, default: void 0 },
25
+ hasLinks: { type: Array, required: false, default: void 0 }
26
+ });
27
+ const config = inject("moulifyConfig", {});
28
+ const effectiveCopyright = computed(() => props.copyrightText ?? config.footer?.copyrightText ?? "");
29
+ const effectiveLinks = computed(() => props.hasLinks ?? config.footer?.hasLinks ?? []);
30
+ const separator = " \xB7 ";
31
+ </script>
32
+
33
+ <style>
34
+ .moulify-footer {
35
+ padding: 24px 16px;
36
+ border-top: 1px solid var(--moulify-tertiary-200, #ddd);
37
+ }
38
+
39
+ .moulify-footer__inner {
40
+ max-width: 1200px;
41
+ margin: 0 auto;
42
+ display: flex;
43
+ flex-wrap: wrap;
44
+ align-items: center;
45
+ justify-content: space-between;
46
+ gap: 12px 24px;
47
+ }
48
+
49
+ .moulify-footer__copyright {
50
+ margin: 0;
51
+ font-size: 14px;
52
+ }
53
+
54
+ .moulify-footer__links {
55
+ display: flex;
56
+ align-items: center;
57
+ flex-wrap: wrap;
58
+ gap: 4px 0;
59
+ }
60
+
61
+ .moulify-footer__link {
62
+ color: var(--moulify-primary-600, #0066dd);
63
+ text-decoration: none;
64
+ font-size: 14px;
65
+ }
66
+ .moulify-footer__link:hover {
67
+ text-decoration: underline;
68
+ }
69
+
70
+ .moulify-footer__separator {
71
+ margin: 0 8px;
72
+ color: var(--moulify-tertiary-500, #888);
73
+ user-select: none;
74
+ }
75
+ </style>
@@ -0,0 +1,7 @@
1
+ import type { FooterProps } from './types.js';
2
+ declare const __VLS_export: import("vue").DefineComponent<FooterProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<FooterProps> & Readonly<{}>, {
3
+ copyrightText: string;
4
+ hasLinks: import("../Header/types.js").NavLink[];
5
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
6
+ declare const _default: typeof __VLS_export;
7
+ export default _default;
@@ -0,0 +1,7 @@
1
+ import type { NavLink } from '../Header/types.js';
2
+ export interface FooterProps {
3
+ /** Copyright text (e.g. "© 2025 Company") */
4
+ copyrightText?: string;
5
+ /** Links to show in the footer (e.g. Privacy, Terms) */
6
+ hasLinks?: NavLink[];
7
+ }
File without changes
@@ -1,3 +1,8 @@
1
- declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
1
+ import type { HeaderProps } from './types.js';
2
+ declare const __VLS_export: import("vue").DefineComponent<HeaderProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<HeaderProps> & Readonly<{}>, {
3
+ left: import("./types.js").NavLink[];
4
+ center: import("./types.js").NavLink[];
5
+ right: import("./types.js").NavLink[];
6
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
2
7
  declare const _default: typeof __VLS_export;
3
8
  export default _default;
@@ -1,9 +1,88 @@
1
1
  <template>
2
2
  <header class="moulify-header">
3
- Header component
3
+ <nav class="moulify-header__nav moulify-header__nav--left" aria-label="Left navigation">
4
+ <template v-for="(link, i) in effectiveLeft" :key="`l-${i}`">
5
+ <a v-if="isExternal(link.url)" class="moulify-header__link" :href="link.url" target="_blank"
6
+ rel="noopener noreferrer">
7
+ {{ link.name }}
8
+ </a>
9
+ <NuxtLink v-else class="moulify-header__link" :to="link.url">
10
+ {{ link.name }}
11
+ </NuxtLink>
12
+ </template>
13
+ </nav>
14
+ <nav class="moulify-header__nav moulify-header__nav--center" aria-label="Center navigation">
15
+ <template v-for="(link, i) in effectiveCenter" :key="`c-${i}`">
16
+ <a v-if="isExternal(link.url)" class="moulify-header__link" :href="link.url" target="_blank"
17
+ rel="noopener noreferrer">
18
+ {{ link.name }}
19
+ </a>
20
+ <NuxtLink v-else class="moulify-header__link" :to="link.url">
21
+ {{ link.name }}
22
+ </NuxtLink>
23
+ </template>
24
+ </nav>
25
+ <nav class="moulify-header__nav moulify-header__nav--right" aria-label="Right navigation">
26
+ <template v-for="(link, i) in effectiveRight" :key="`r-${i}`">
27
+ <a v-if="isExternal(link.url)" class="moulify-header__link" :href="link.url" target="_blank"
28
+ rel="noopener noreferrer">
29
+ {{ link.name }}
30
+ </a>
31
+ <NuxtLink v-else class="moulify-header__link" :to="link.url">
32
+ {{ link.name }}
33
+ </NuxtLink>
34
+ </template>
35
+ </nav>
4
36
  </header>
5
37
  </template>
6
38
 
39
+ <script setup>
40
+ import { computed, inject } from "vue";
41
+ const props = defineProps({
42
+ left: { type: Array, required: false, default: void 0 },
43
+ center: { type: Array, required: false, default: void 0 },
44
+ right: { type: Array, required: false, default: void 0 }
45
+ });
46
+ const config = inject("moulifyConfig", {});
47
+ const effectiveLeft = computed(() => props.left ?? config.header?.left ?? []);
48
+ const effectiveCenter = computed(() => props.center ?? config.header?.center ?? []);
49
+ const effectiveRight = computed(() => props.right ?? config.header?.right ?? []);
50
+ const isExternal = (url) => /^https?:\/\//i.test(url) || url.startsWith("//");
51
+ </script>
52
+
7
53
  <style>
8
- .moulify-header{background-color:#000;border-radius:5px;color:#fff;padding:10px}
54
+ .moulify-header {
55
+ display: flex;
56
+ align-items: center;
57
+ justify-content: space-between;
58
+ gap: 24px;
59
+ padding: 12px 24px;
60
+ background-color: var(--moulify-secondary-900, #000);
61
+ color: #fff;
62
+ border-radius: 0;
63
+ }
64
+ .moulify-header__nav {
65
+ display: flex;
66
+ align-items: center;
67
+ gap: 8px 24px;
68
+ }
69
+ .moulify-header__nav--left {
70
+ flex: 0 0 auto;
71
+ }
72
+ .moulify-header__nav--center {
73
+ flex: 1;
74
+ justify-content: center;
75
+ }
76
+ .moulify-header__nav--right {
77
+ flex: 0 0 auto;
78
+ justify-content: flex-end;
79
+ }
80
+ .moulify-header__link {
81
+ color: inherit;
82
+ text-decoration: none;
83
+ font-size: 14px;
84
+ }
85
+ .moulify-header__link:hover {
86
+ text-decoration: underline;
87
+ }
9
88
  </style>
@@ -1,3 +1,8 @@
1
- declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
1
+ import type { HeaderProps } from './types.js';
2
+ declare const __VLS_export: import("vue").DefineComponent<HeaderProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<HeaderProps> & Readonly<{}>, {
3
+ left: import("./types.js").NavLink[];
4
+ center: import("./types.js").NavLink[];
5
+ right: import("./types.js").NavLink[];
6
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
2
7
  declare const _default: typeof __VLS_export;
3
8
  export default _default;
@@ -0,0 +1,10 @@
1
+ /** A single link item (name + url) used in Header and Footer. */
2
+ export interface NavLink {
3
+ name: string;
4
+ url: string;
5
+ }
6
+ export interface HeaderProps {
7
+ left?: NavLink[];
8
+ center?: NavLink[];
9
+ right?: NavLink[];
10
+ }
File without changes
@@ -8,11 +8,11 @@
8
8
  <script setup>
9
9
  import { computed } from "vue";
10
10
  import { ALL_ICONS } from "../../../../utils/constants";
11
- defineOptions({ name: "NcIcon" });
11
+ defineOptions({ name: "MoulifyIcon" });
12
12
  const props = defineProps({
13
13
  name: { type: String, required: true },
14
14
  size: { type: Number, required: false, default: 24 },
15
- color: { type: String, required: false, default: "white" }
15
+ color: { type: String, required: false, default: "black" }
16
16
  });
17
17
  const isIconValid = computed(() => ALL_ICONS.includes(props.name));
18
18
  const iconStyle = computed(() => ({
@@ -1,4 +1,4 @@
1
- import type { IconsId } from '~/src/assets/generated/fonts/icons';
1
+ import type { IconsId } from '../../../../assets/generated/fonts/icons.js';
2
2
  /**
3
3
  * Props for the Icon component.
4
4
  *
@@ -9,13 +9,13 @@ export interface IconProps {
9
9
  /** Icon name from the Fantasticon-generated font family. */
10
10
  name: IconsId;
11
11
  /**
12
- * Font size for the icon.
12
+ * Font size for the icon (px).
13
13
  *
14
14
  * @default 24
15
15
  */
16
16
  size?: number;
17
17
  /**
18
- * Color of the icon.
18
+ * Color of the icon (CSS color value).
19
19
  *
20
20
  * @default "currentColor" - Depending on SVG color.
21
21
  */
@@ -0,0 +1,4 @@
1
+ import { type SocialLinksProps } from './types.js';
2
+ declare const __VLS_export: import("vue").DefineComponent<SocialLinksProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<SocialLinksProps> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
3
+ declare const _default: typeof __VLS_export;
4
+ export default _default;
@@ -0,0 +1,39 @@
1
+ <template>
2
+ <nav class="moulify-social-links" aria-label="Social links">
3
+ <template v-if="effectiveLinks.length">
4
+ <a v-for="item in effectiveLinks" :key="item.id" class="moulify-social-links__item" :href="item.link"
5
+ target="_blank" rel="noopener noreferrer">
6
+ {{ item.name }}
7
+ </a>
8
+ </template>
9
+ </nav>
10
+ </template>
11
+
12
+ <script setup>
13
+ import { computed, inject } from "vue";
14
+ import { SocialPlatform } from "./types";
15
+ const props = defineProps({
16
+ links: { type: Object, required: false }
17
+ });
18
+ const config = inject("moulifyConfig", {});
19
+ const effectiveLinks = computed(
20
+ () => SocialPlatform.fromConfigs(config.socialLinks, props.links)
21
+ );
22
+ </script>
23
+
24
+ <style>
25
+ .moulify-social-links {
26
+ display: flex;
27
+ flex-wrap: wrap;
28
+ align-items: center;
29
+ gap: 12px 24px;
30
+ }
31
+ .moulify-social-links__item {
32
+ color: var(--moulify-primary-500, #0076ff);
33
+ text-decoration: none;
34
+ font-size: 14px;
35
+ }
36
+ .moulify-social-links__item:hover {
37
+ text-decoration: underline;
38
+ }
39
+ </style>
@@ -0,0 +1,4 @@
1
+ import { type SocialLinksProps } from './types.js';
2
+ declare const __VLS_export: import("vue").DefineComponent<SocialLinksProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<SocialLinksProps> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
3
+ declare const _default: typeof __VLS_export;
4
+ export default _default;
@@ -0,0 +1,24 @@
1
+ /** CamelCase platform key (twitter, github, instagram, facebook, x). */
2
+ export type SocialPlatformId = 'twitter' | 'github' | 'instagram' | 'facebook' | 'x';
3
+ /** Config: optional URL per platform. Only listed platforms are shown. */
4
+ export type SocialLinksConfig = Partial<Record<SocialPlatformId, string>>;
5
+ export declare class SocialPlatform {
6
+ readonly id: SocialPlatformId;
7
+ readonly name: string;
8
+ readonly link?: string | undefined;
9
+ constructor(id: SocialPlatformId, name: string, link?: string | undefined);
10
+ static Facebook: SocialPlatform;
11
+ static GitHub: SocialPlatform;
12
+ static Instagram: SocialPlatform;
13
+ static Twitter: SocialPlatform;
14
+ static X: SocialPlatform;
15
+ static all: SocialPlatform[];
16
+ /** Build list with links from config (only platforms present in config, with their URL). */
17
+ static fromConfig(config: SocialLinksConfig): SocialPlatform[];
18
+ /** Merge configs (later overrides earlier) and return SocialPlatform[] with links set. */
19
+ static fromConfigs(...configs: (SocialLinksConfig | undefined)[]): SocialPlatform[];
20
+ }
21
+ export interface SocialLinksProps {
22
+ /** Override or set links per platform (optional). Merged with module config. */
23
+ links?: SocialLinksConfig;
24
+ }
@@ -0,0 +1,38 @@
1
+ export class SocialPlatform {
2
+ constructor(id, name, link) {
3
+ this.id = id;
4
+ this.name = name;
5
+ this.link = link;
6
+ }
7
+ static Facebook = new SocialPlatform("facebook", "Facebook");
8
+ static GitHub = new SocialPlatform("github", "GitHub");
9
+ static Instagram = new SocialPlatform("instagram", "Instagram");
10
+ static Twitter = new SocialPlatform("twitter", "Twitter");
11
+ static X = new SocialPlatform("x", "X");
12
+ static all = [
13
+ this.Facebook,
14
+ this.GitHub,
15
+ this.Instagram,
16
+ this.Twitter,
17
+ this.X
18
+ ];
19
+ /** Build list with links from config (only platforms present in config, with their URL). */
20
+ static fromConfig(config) {
21
+ return this.all.filter((p) => config[p.id]).map((p) => new SocialPlatform(p.id, p.name, config[p.id]));
22
+ }
23
+ /** Merge configs (later overrides earlier) and return SocialPlatform[] with links set. */
24
+ static fromConfigs(...configs) {
25
+ const merged = {};
26
+ for (const config of configs) {
27
+ if (!config) {
28
+ continue;
29
+ }
30
+ for (const p of this.all) {
31
+ if (config[p.id] !== void 0) {
32
+ merged[p.id] = config[p.id];
33
+ }
34
+ }
35
+ }
36
+ return this.fromConfig(merged);
37
+ }
38
+ }
@@ -1,3 +1,8 @@
1
- import '../assets/generated/fonts/icons.css.js';
2
- declare const _default: import("#app").Plugin<Record<string, unknown>> & import("#app").ObjectPlugin<Record<string, unknown>>;
1
+ import type { MoulifyPublicConfig } from '../types.js';
2
+ declare module 'nuxt/schema' {
3
+ interface PublicRuntimeConfig {
4
+ moulify?: MoulifyPublicConfig;
5
+ }
6
+ }
7
+ declare const _default: import("nuxt/app").Plugin<Record<string, unknown>> & import("nuxt/app").ObjectPlugin<Record<string, unknown>>;
3
8
  export default _default;
@@ -1,5 +1,32 @@
1
- import { defineNuxtPlugin } from "#app";
2
- import "../assets/generated/fonts/icons.css";
3
- export default defineNuxtPlugin((_nuxtApp) => {
4
- console.log("Plugin injected by moulify!");
1
+ import { defineNuxtPlugin, useRuntimeConfig } from "nuxt/app";
2
+ const injectColorPaletteVars = (name, palette, target) => {
3
+ const prefix = `--moulify-${name}`;
4
+ target.setProperty(`${prefix}-50`, palette[50]);
5
+ target.setProperty(`${prefix}-100`, palette[100]);
6
+ target.setProperty(`${prefix}-200`, palette[200]);
7
+ target.setProperty(`${prefix}-300`, palette[300]);
8
+ target.setProperty(`${prefix}-400`, palette[400]);
9
+ target.setProperty(`${prefix}-500`, palette[500]);
10
+ target.setProperty(`${prefix}-600`, palette[600]);
11
+ target.setProperty(`${prefix}-700`, palette[700]);
12
+ target.setProperty(`${prefix}-800`, palette[800]);
13
+ target.setProperty(`${prefix}-900`, palette[900]);
14
+ };
15
+ export default defineNuxtPlugin((nuxtApp) => {
16
+ const config = useRuntimeConfig();
17
+ const colors = config.public.moulify?.colors;
18
+ if (colors && typeof document !== "undefined") {
19
+ const root = document.documentElement;
20
+ if (colors.primary) {
21
+ injectColorPaletteVars("primary", colors.primary, root.style);
22
+ }
23
+ if (colors.secondary) {
24
+ injectColorPaletteVars("secondary", colors.secondary, root.style);
25
+ }
26
+ if (colors.tertiary) {
27
+ injectColorPaletteVars("tertiary", colors.tertiary, root.style);
28
+ }
29
+ }
30
+ nuxtApp.provide("moulifyColors", colors ?? {});
31
+ nuxtApp.provide("moulifyConfig", config.public.moulify ?? {});
5
32
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "moulify",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
4
4
  "description": "My new Nuxt module",
5
5
  "repository": "https://github.com/moulibheemaneti/moulify",
6
6
  "license": "MIT",
@@ -33,8 +33,10 @@
33
33
  "clean-install": "bun run clean && bun i",
34
34
  "dev": "bun run dev:prepare && nuxt dev playground",
35
35
  "dev:clean": "bun run clean-install && bun run dev",
36
+ "landing:dev": "bun run landing:prepare && cd landing-page && bun run dev",
36
37
  "dev:build": "nuxt build playground",
37
38
  "dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxt prepare playground",
39
+ "landing:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxt prepare landing-page",
38
40
  "release": "npm run lint && npm run test && npm run prepack && changelogen --release && npm publish && git push --follow-tags",
39
41
  "lint": "eslint .",
40
42
  "test": "vitest run",