@finggujadhav/vue 0.9.0

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.
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@finggujadhav/vue",
3
+ "version": "0.9.0",
4
+ "description": "Vue adapter for FingguFlux UI library",
5
+ "main": "dist/index.js",
6
+ "module": "src/index.ts",
7
+ "types": "src/index.ts",
8
+ "scripts": {
9
+ "build": "echo 'SFCs do not require pre-build for this release'"
10
+ },
11
+ "files": [
12
+ "dist",
13
+ "src"
14
+ ],
15
+ "exports": {
16
+ ".": {
17
+ "types": "./src/index.ts",
18
+ "import": "./src/index.ts"
19
+ }
20
+ },
21
+ "dependencies": {
22
+ "@finggujadhav/core": "0.9.0",
23
+ "@finggujadhav/js-helper": "0.9.0"
24
+ },
25
+ "peerDependencies": {
26
+ "vue": ">=3.0.0"
27
+ },
28
+ "devDependencies": {
29
+ "vue": "^3.0.0",
30
+ "typescript": "^5.0.0"
31
+ }
32
+ }
package/src/Button.vue ADDED
@@ -0,0 +1,74 @@
1
+ <template>
2
+ <button :class="ffClasses" v-bind="$attrs">
3
+ <slot />
4
+ </button>
5
+ </template>
6
+
7
+ <script lang="ts">
8
+ import { defineComponent, computed, PropType } from 'vue';
9
+ import { useFinggu } from './composable';
10
+
11
+ const VARIANTS = {
12
+ primary: 'ff-btn-primary',
13
+ secondary: 'ff-btn-secondary',
14
+ ghost: 'ff-btn-ghost',
15
+ outline: 'ff-btn-outline'
16
+ };
17
+
18
+ const SIZES = {
19
+ sm: 'ff-btn-sm',
20
+ md: 'ff-btn-md',
21
+ lg: 'ff-btn-lg'
22
+ };
23
+
24
+ const MOTIONS = {
25
+ fade: 'ff-fade-in',
26
+ 'slide-up': 'ff-slide-up',
27
+ 'scale-in': 'ff-scale-in',
28
+ lift: 'ff-hover-lift'
29
+ };
30
+
31
+ export const __ffClasses_Button = [
32
+ 'ff-btn',
33
+ ...Object.values(VARIANTS),
34
+ ...Object.values(SIZES),
35
+ ...Object.values(MOTIONS),
36
+ 'ff-card-glass'
37
+ ];
38
+
39
+ export default defineComponent({
40
+ name: 'FingguButton',
41
+ inheritAttrs: false,
42
+ props: {
43
+ variant: {
44
+ type: String as PropType<keyof typeof VARIANTS>,
45
+ default: 'primary'
46
+ },
47
+ size: {
48
+ type: String as PropType<keyof typeof SIZES>,
49
+ default: 'md'
50
+ },
51
+ motion: {
52
+ type: String as PropType<keyof typeof MOTIONS>,
53
+ default: undefined
54
+ },
55
+ glass: {
56
+ type: Boolean,
57
+ default: false
58
+ }
59
+ },
60
+ setup(props) {
61
+ const { resolveAll } = useFinggu();
62
+
63
+ const ffClasses = computed(() => resolveAll([
64
+ 'ff-btn',
65
+ VARIANTS[props.variant],
66
+ SIZES[props.size],
67
+ props.glass && 'ff-card-glass',
68
+ props.motion && MOTIONS[props.motion]
69
+ ]));
70
+
71
+ return { ffClasses };
72
+ }
73
+ });
74
+ </script>
package/src/Card.vue ADDED
@@ -0,0 +1,56 @@
1
+ <template>
2
+ <div :class="ffClasses" v-bind="$attrs">
3
+ <slot />
4
+ </div>
5
+ </template>
6
+
7
+ <script lang="ts">
8
+ import { defineComponent, computed, PropType } from 'vue';
9
+ import { useFinggu } from './composable';
10
+
11
+ const VARIANTS = {
12
+ outline: 'ff-card-outline',
13
+ glass: 'ff-card-glass'
14
+ };
15
+
16
+ const PADDINGS = {
17
+ none: 'ff-p-0',
18
+ sm: 'ff-p-2',
19
+ md: 'ff-p-4',
20
+ lg: 'ff-p-8'
21
+ };
22
+
23
+ export const __ffClasses_Card = [
24
+ 'ff-card',
25
+ 'ff-card-header',
26
+ 'ff-card-body',
27
+ ...Object.values(VARIANTS),
28
+ ...Object.values(PADDINGS)
29
+ ];
30
+
31
+ export default defineComponent({
32
+ name: 'FingguCard',
33
+ inheritAttrs: false,
34
+ props: {
35
+ variant: {
36
+ type: String as PropType<keyof typeof VARIANTS>,
37
+ default: undefined
38
+ },
39
+ padding: {
40
+ type: String as PropType<keyof typeof PADDINGS>,
41
+ default: 'md'
42
+ }
43
+ },
44
+ setup(props) {
45
+ const { resolveAll } = useFinggu();
46
+
47
+ const ffClasses = computed(() => resolveAll([
48
+ 'ff-card',
49
+ props.variant && VARIANTS[props.variant],
50
+ PADDINGS[props.padding]
51
+ ]));
52
+
53
+ return { ffClasses };
54
+ }
55
+ });
56
+ </script>
@@ -0,0 +1,93 @@
1
+ <template>
2
+ <div :class="ffContainerClasses" ref="containerRef">
3
+ <button
4
+ type="button"
5
+ :class="ffTriggerClasses"
6
+ @click="toggle"
7
+ aria-haspopup="true"
8
+ :aria-expanded="isOpen"
9
+ >
10
+ <slot name="trigger" />
11
+ </button>
12
+ <div
13
+ :class="ffMenuClasses"
14
+ :data-ff-state="isOpen ? 'open' : 'closed'"
15
+ role="menu"
16
+ :aria-hidden="!isOpen"
17
+ >
18
+ <slot />
19
+ </div>
20
+ </div>
21
+ </template>
22
+
23
+ <script lang="ts">
24
+ import { defineComponent, ref, onMounted, onUnmounted, computed, PropType } from 'vue';
25
+ import { useFinggu } from './composable';
26
+
27
+ export const __ffClasses_Dropdown = [
28
+ 'ff-dropdown',
29
+ 'ff-dropdown-trigger',
30
+ 'ff-dropdown-menu',
31
+ 'ff-dropdown-menu-right',
32
+ 'ff-dropdown-item'
33
+ ];
34
+
35
+ export default defineComponent({
36
+ name: 'FingguDropdown',
37
+ props: {
38
+ align: {
39
+ type: String as PropType<'left' | 'right'>,
40
+ default: 'left'
41
+ }
42
+ },
43
+ setup(props) {
44
+ const { resolveAll } = useFinggu();
45
+ const isOpen = ref(false);
46
+ const containerRef = ref<HTMLElement | null>(null);
47
+
48
+ const toggle = () => { isOpen.value = !isOpen.value; };
49
+ const close = () => { isOpen.value = false; };
50
+
51
+ const handleClickOutside = (event: MouseEvent) => {
52
+ if (containerRef.value && !containerRef.value.contains(event.target as Node)) {
53
+ close();
54
+ }
55
+ };
56
+
57
+ onMounted(() => {
58
+ document.addEventListener('mousedown', handleClickOutside);
59
+ });
60
+
61
+ onUnmounted(() => {
62
+ document.removeEventListener('mousedown', handleClickOutside);
63
+ });
64
+
65
+ const ffContainerClasses = computed(() => resolveAll(['ff-dropdown']));
66
+ const ffTriggerClasses = computed(() => resolveAll(['ff-dropdown-trigger']));
67
+ const ffMenuClasses = computed(() => resolveAll([
68
+ 'ff-dropdown-menu',
69
+ props.align === 'right' && 'ff-dropdown-menu-right'
70
+ ]));
71
+
72
+ return {
73
+ isOpen,
74
+ containerRef,
75
+ toggle,
76
+ ffContainerClasses,
77
+ ffTriggerClasses,
78
+ ffMenuClasses
79
+ };
80
+ }
81
+ });
82
+
83
+ // DropdownItem as a simple functional-like component
84
+ export const DropdownItem = defineComponent({
85
+ name: 'FingguDropdownItem',
86
+ setup(_, { slots, attrs }) {
87
+ const { resolveAll } = useFinggu();
88
+ const ffClasses = computed(() => resolveAll(['ff-dropdown-item', attrs.class as string]));
89
+ return () => h('div', { ...attrs, class: ffClasses.value }, slots.default?.());
90
+ }
91
+ });
92
+ import { h } from 'vue';
93
+ </script>
package/src/Input.vue ADDED
@@ -0,0 +1,32 @@
1
+ <template>
2
+ <input :class="ffClasses" v-bind="$attrs" />
3
+ </template>
4
+
5
+ <script lang="ts">
6
+ import { defineComponent, computed } from 'vue';
7
+ import { useFinggu } from './composable';
8
+
9
+ export const __ffClasses_Input = [
10
+ 'ff-input',
11
+ 'ff-input-error'
12
+ ];
13
+
14
+ export default defineComponent({
15
+ name: 'FingguInput',
16
+ inheritAttrs: false,
17
+ props: {
18
+ error: {
19
+ type: Boolean,
20
+ default: false
21
+ }
22
+ },
23
+ setup(props) {
24
+ const { resolveAll } = useFinggu();
25
+ const ffClasses = computed(() => resolveAll([
26
+ 'ff-input',
27
+ props.error && 'ff-input-error'
28
+ ]));
29
+ return { ffClasses };
30
+ }
31
+ });
32
+ </script>
package/src/Modal.vue ADDED
@@ -0,0 +1,79 @@
1
+ <template>
2
+ <teleport to="body" v-if="mounted">
3
+ <div
4
+ v-show="isOpen || animating"
5
+ :class="ffContainerClasses"
6
+ aria-hidden="!isOpen"
7
+ >
8
+ <div class="ff-modal-overlay" @click="$emit('close')" />
9
+ <div :class="ffContentClasses">
10
+ <slot />
11
+ </div>
12
+ </div>
13
+ </teleport>
14
+ </template>
15
+
16
+ <script lang="ts">
17
+ import { defineComponent, ref, watch, onMounted, onUnmounted, computed } from 'vue';
18
+ import { useFinggu } from './composable';
19
+
20
+ export const __ffClasses_Modal = [
21
+ 'ff-modal',
22
+ 'ff-modal-open',
23
+ 'ff-modal-closed',
24
+ 'ff-modal-overlay',
25
+ 'ff-modal-content'
26
+ ];
27
+
28
+ export default defineComponent({
29
+ name: 'FingguModal',
30
+ props: {
31
+ isOpen: {
32
+ type: Boolean,
33
+ required: true
34
+ }
35
+ },
36
+ emits: ['close'],
37
+ setup(props) {
38
+ const { resolveAll } = useFinggu();
39
+ const mounted = ref(false);
40
+ const animating = ref(false);
41
+
42
+ onMounted(() => {
43
+ mounted.value = true;
44
+ });
45
+
46
+ watch(() => props.isOpen, (val) => {
47
+ if (val) {
48
+ document.body.style.overflow = 'hidden';
49
+ animating.value = true;
50
+ } else {
51
+ setTimeout(() => {
52
+ animating.value = false;
53
+ document.body.style.overflow = '';
54
+ }, 300); // Exit animation duration
55
+ }
56
+ });
57
+
58
+ onUnmounted(() => {
59
+ document.body.style.overflow = '';
60
+ });
61
+
62
+ const ffContainerClasses = computed(() => resolveAll([
63
+ 'ff-modal',
64
+ props.isOpen ? 'ff-modal-open' : 'ff-modal-closed'
65
+ ]));
66
+
67
+ const ffContentClasses = computed(() => resolveAll([
68
+ 'ff-modal-content'
69
+ ]));
70
+
71
+ return {
72
+ mounted,
73
+ animating,
74
+ ffContainerClasses,
75
+ ffContentClasses
76
+ };
77
+ }
78
+ });
79
+ </script>
package/src/Tabs.ts ADDED
@@ -0,0 +1,87 @@
1
+ <script lang="ts" >
2
+ import { defineComponent, h, provide, ref, InjectionKey, Ref, PropType } from 'vue';
3
+ import { useFinggu } from './composable';
4
+
5
+ export const __ffClasses_Tabs = [
6
+ 'ff-tabs',
7
+ 'ff-tab-list',
8
+ 'ff-tab',
9
+ 'ff-tab-active',
10
+ 'ff-tab-content'
11
+ ];
12
+
13
+ interface TabsContext {
14
+ activeTab: Ref<string>;
15
+ setActiveTab: (value: string) => void;
16
+ }
17
+
18
+ export const TabsSymbol: InjectionKey<TabsContext> = Symbol('FingguTabs');
19
+
20
+ export const Tabs = defineComponent({
21
+ name: 'FingguTabs',
22
+ props: {
23
+ defaultValue: {
24
+ type: String,
25
+ required: true
26
+ }
27
+ },
28
+ setup(props, { slots }) {
29
+ const activeTab = ref(props.defaultValue);
30
+ const setActiveTab = (value: string) => { activeTab.value = value; };
31
+
32
+ provide(TabsSymbol, { activeTab, setActiveTab });
33
+
34
+ return () => h('div', { class: 'ff-tabs' }, slots.default?.());
35
+ }
36
+ });
37
+
38
+ export const TabList = defineComponent({
39
+ name: 'FingguTabList',
40
+ setup(_, { slots }) {
41
+ return () => h('div', { class: 'ff-tab-list', role: 'tablist' }, slots.default?.());
42
+ }
43
+ });
44
+
45
+ export const TabTrigger = defineComponent({
46
+ name: 'FingguTabTrigger',
47
+ props: {
48
+ value: {
49
+ type: String,
50
+ required: true
51
+ }
52
+ },
53
+ setup(props, { slots }) {
54
+ const context = inject(TabsSymbol);
55
+ const { resolveAll } = useFinggu();
56
+
57
+ return () => {
58
+ const isActive = context?.activeTab.value === props.value;
59
+ return h('button', {
60
+ role: 'tab',
61
+ 'aria-selected': isActive,
62
+ class: resolveAll(['ff-tab', isActive && 'ff-tab-active']),
63
+ onClick: () => context?.setActiveTab(props.value)
64
+ }, slots.default?.());
65
+ };
66
+ }
67
+ });
68
+
69
+ export const TabContent = defineComponent({
70
+ name: 'FingguTabContent',
71
+ props: {
72
+ value: {
73
+ type: String,
74
+ required: true
75
+ }
76
+ },
77
+ setup(props, { slots }) {
78
+ const context = inject(TabsSymbol);
79
+ return () => (context?.activeTab.value === props.value)
80
+ ? h('div', { class: 'ff-tab-content' }, slots.default?.())
81
+ : null;
82
+ }
83
+ });
84
+
85
+ // For SFC imports
86
+ import { inject } from 'vue';
87
+ export default Tabs;
@@ -0,0 +1,44 @@
1
+ import { inject } from 'vue';
2
+ import { FingguSymbol, FingguOptions } from './plugin';
3
+
4
+ /**
5
+ * useFinggu composable to resolve FingguFlux classes.
6
+ */
7
+ export function useFinggu() {
8
+ const finggu = inject<FingguOptions>(FingguSymbol, {
9
+ mapping: null,
10
+ mode: 'dev'
11
+ });
12
+
13
+ const resolve = (className: string): string => {
14
+ const { mapping, mode } = finggu;
15
+ if (mode === 'dev' || !mapping) return className;
16
+
17
+ const mapped = mapping[className];
18
+ if (mode === 'ext' && !mapped && className.startsWith('ff-')) {
19
+ const errorMsg = `[FingguFlux] Critical: Class '${className}' not found in mapping.json in Extreme mode. This will cause broken styles in production.`;
20
+ // @ts-ignore - support vite/nuxt environments
21
+ const isDev = typeof process !== 'undefined' ? process.env.NODE_ENV === 'development' : (import.meta as any).env?.DEV;
22
+
23
+ if (isDev) {
24
+ throw new Error(errorMsg);
25
+ } else {
26
+ console.error(errorMsg);
27
+ }
28
+ }
29
+ return mapped || className;
30
+ };
31
+
32
+ const resolveAll = (classes: (string | undefined | null | false)[]): string => {
33
+ return classes
34
+ .filter(Boolean)
35
+ .map(c => resolve(c as string))
36
+ .join(' ');
37
+ };
38
+
39
+ return {
40
+ resolve,
41
+ resolveAll,
42
+ mode: finggu.mode
43
+ };
44
+ }
package/src/index.ts ADDED
@@ -0,0 +1,10 @@
1
+ export * from './plugin';
2
+ export * from './composable';
3
+
4
+ // Components
5
+ export { default as Button, __ffClasses_Button } from './Button.vue';
6
+ export { default as Card, __ffClasses_Card } from './Card.vue';
7
+ export { default as Input, __ffClasses_Input } from './Input.vue';
8
+ export { default as Modal, __ffClasses_Modal } from './Modal.vue';
9
+ export { Tabs, TabList, TabTrigger, TabContent, __ffClasses_Tabs } from './Tabs';
10
+ export { default as Dropdown, __ffClasses_Dropdown, DropdownItem } from './Dropdown.vue';
package/src/plugin.ts ADDED
@@ -0,0 +1,42 @@
1
+ import { InjectionKey, App, reactive, watch } from 'vue';
2
+ import { setTheme, FingguTheme } from '@finggujadhav/js-helper';
3
+
4
+ export interface FingguMapping {
5
+ _version?: string;
6
+ [key: string]: string | undefined;
7
+ }
8
+
9
+ export interface FingguOptions {
10
+ mapping?: FingguMapping;
11
+ mode?: 'dev' | 'opt' | 'ext';
12
+ version?: string;
13
+ theme?: FingguTheme;
14
+ }
15
+
16
+ export const FingguSymbol: InjectionKey<FingguOptions> = Symbol('FingguFlux');
17
+
18
+ /**
19
+ * FingguFlux Vue Plugin
20
+ * Installs mapping, mode, and theme via provide/inject.
21
+ */
22
+ export const FingguPlugin = {
23
+ install(app: App, options: FingguOptions = {}) {
24
+ const state = reactive({
25
+ mapping: options.mapping || null,
26
+ mode: options.mode || 'dev',
27
+ version: options.version,
28
+ theme: options.theme || 'system'
29
+ });
30
+
31
+ if (state.mapping && state.version && state.mapping._version && state.mapping._version !== state.version) {
32
+ console.warn(`[FingguFlux] Version Mismatch: mapping.json (${state.mapping._version}) does not match expected CSS version (${state.version})`);
33
+ }
34
+
35
+ // Global theme application (optional, can be scoped)
36
+ watch(() => state.theme, (newTheme) => {
37
+ setTheme(newTheme as FingguTheme);
38
+ }, { immediate: true });
39
+
40
+ app.provide(FingguSymbol, state);
41
+ }
42
+ };