diff --git a/apps/storybook/.storybook/main.ts b/apps/storybook/.storybook/main.ts index 9d6f2bc3ca..4877590e57 100644 --- a/apps/storybook/.storybook/main.ts +++ b/apps/storybook/.storybook/main.ts @@ -33,6 +33,14 @@ const config: StorybookConfig = { + ` }, previewBody: (body) => { diff --git a/apps/web/src/common/modules/navigations/top-bar/modules/top-bar-toolset/modules/top-bar-integration/modules/TopBarIntegrationContextMenu.vue b/apps/web/src/common/modules/navigations/top-bar/modules/top-bar-toolset/modules/top-bar-integration/modules/TopBarIntegrationContextMenu.vue index 0218fc3ed6..ba546f6762 100644 --- a/apps/web/src/common/modules/navigations/top-bar/modules/top-bar-toolset/modules/top-bar-integration/modules/TopBarIntegrationContextMenu.vue +++ b/apps/web/src/common/modules/navigations/top-bar/modules/top-bar-toolset/modules/top-bar-integration/modules/TopBarIntegrationContextMenu.vue @@ -17,11 +17,11 @@ const labelRef = ref(null); const domainStore = useDomainStore(); const state = reactive({ integrationMenus: computed(() => domainStore.getters.domainExtraMenu?.contents ?? []), - tabs: computed(() => state.integrationMenus.map((menu) => ({ + tabs: computed(() => state.integrationMenus.map((menu) => ({ label: menu.title, name: menu.title, keepAlive: true, - })) as TabItem[]), + }))), activeTab: '', }); diff --git a/packages/mirinae/src/hooks/dynamin-layout-raw-table-data-sorting/__tests__/index.test.ts b/packages/mirinae/src/data-display/dynamic/dynamic-layout/templates/raw-table/dynamin-layout-raw-table-data-sorting/__tests__/index.test.ts similarity index 100% rename from packages/mirinae/src/hooks/dynamin-layout-raw-table-data-sorting/__tests__/index.test.ts rename to packages/mirinae/src/data-display/dynamic/dynamic-layout/templates/raw-table/dynamin-layout-raw-table-data-sorting/__tests__/index.test.ts diff --git a/packages/mirinae/src/data-display/tables/toolbox-table/PToolboxTable.stories.ts b/packages/mirinae/src/data-display/tables/toolbox-table/PToolboxTable.stories.ts index d3d6376e07..fa137516c2 100644 --- a/packages/mirinae/src/data-display/tables/toolbox-table/PToolboxTable.stories.ts +++ b/packages/mirinae/src/data-display/tables/toolbox-table/PToolboxTable.stories.ts @@ -5,7 +5,7 @@ import type { ComponentProps } from 'vue-component-type-helpers'; import { getUserFields, getUsers } from '@/data-display/tables/data-table/mock'; import { getToolboxTableArgs, getToolboxTableArgTypes, getToolboxTableParameters } from '@/data-display/tables/toolbox-table/story-helper'; -import { useProxyValue } from '@/hooks/proxy-state'; +import { useProxyValue } from '@/hooks/use-proxy-state/use-proxy-state'; import PButton from '@/inputs/buttons/button/PButton.vue'; import PSelectStatus from '@/inputs/select-status/PSelectStatus.vue'; diff --git a/packages/mirinae/src/foundation/icons/config.ts b/packages/mirinae/src/foundation/icons/config.ts index a91841f9ca..caac4120f5 100644 --- a/packages/mirinae/src/foundation/icons/config.ts +++ b/packages/mirinae/src/foundation/icons/config.ts @@ -1,6 +1,6 @@ export const ANIMATION_TYPE = { spin: 'spin', reserveSpin: 'reserve-spin', -}; +} as const; export type AnimationType = typeof ANIMATION_TYPE[keyof typeof ANIMATION_TYPE]; diff --git a/packages/mirinae/src/hooks/hookGuidelines.mdx b/packages/mirinae/src/hooks/hookGuidelines.mdx new file mode 100644 index 0000000000..f181205f32 --- /dev/null +++ b/packages/mirinae/src/hooks/hookGuidelines.mdx @@ -0,0 +1,25 @@ +{/* hookGuidelines.mdx */} + +import { Meta } from '@storybook/blocks'; + + + +# Hook Guidelines + +This document outlines the guidelines for creating and using hooks within the design system, as well as for external usage, ensuring consistency and clarity across all hooks. +
+ +## 1. Hook Argument - Options + +- **Single Argument**: Every hook accepts a single argument, which is always an object. This object is referred to as the "Hook Options." +- **Naming Convention**: The interface for these options follows the naming rule `HookNameOptions`, where `HookName` is the name of the specific hook. + - **Example**: `UseTabOptions` +
+ +## 2. Hook Returns + +- **Return Format**: Hooks always return a single object, called the "Hook Returns." +- **Naming Convention**: The interface for the return object follows the naming rule `HookNameReturns`, where `HookName` is the name of the specific hook. + - **Example**: `UseTabReturns` +
+ diff --git a/packages/mirinae/src/hooks/hookGuidelines.stories.ts b/packages/mirinae/src/hooks/hookGuidelines.stories.ts new file mode 100644 index 0000000000..223e1a28c3 --- /dev/null +++ b/packages/mirinae/src/hooks/hookGuidelines.stories.ts @@ -0,0 +1,7 @@ +import type { Meta } from '@storybook/vue'; + +const meta: Meta = { + title: 'Hooks/Guidelines', +}; + +export default meta; diff --git a/packages/mirinae/src/hooks/index.ts b/packages/mirinae/src/hooks/index.ts index 0510ad82b8..456db9990d 100644 --- a/packages/mirinae/src/hooks/index.ts +++ b/packages/mirinae/src/hooks/index.ts @@ -1,8 +1,8 @@ -export * from './context-menu-controller'; -export * from './context-menu-style'; -export * from './query-search'; -export * from './ignore-window-arrow-keydown-events'; -export * from './list-focus'; -export * from './proxy-state'; -export * from './select'; -export * from './tab'; +export * from './use-context-menu-controller/use-context-menu-controller'; +export * from './use-context-menu-style/use-context-menu-style'; +export * from './use-query-search/use-query-search'; +export * from './use-ignore-window-arrow-keydown-events/use-ignore-window-arrow-keydown-events'; +export * from './use-list-focus/use-list-focus'; +export * from './use-proxy-state/use-proxy-state'; +export * from './use-select/use-select'; +export * from './use-tab/use-tab'; diff --git a/packages/mirinae/src/hooks/tab/index.ts b/packages/mirinae/src/hooks/tab/index.ts deleted file mode 100644 index d1b27efc91..0000000000 --- a/packages/mirinae/src/hooks/tab/index.ts +++ /dev/null @@ -1,55 +0,0 @@ -import type { UnwrapRef, ComputedRef } from 'vue'; -import { - computed, reactive, toRefs, -} from 'vue'; - -import type { TabItem } from '@/navigation/tabs/tab/type'; - - -interface TabStateArgs { - tabs: ComputedRef> | Array; - activeTab: ComputedRef | string; -} - -interface TabState { - tabItems: TabItem[]; - keepAliveTabNames: string[]; - nonKeepAliveTabNames: string[]; - currentTabItem?: TabItem; -} - -export const useTab = ({ tabs, activeTab }: TabStateArgs) => { - const state = reactive({ - tabs, - activeTab, - }); - const tabState: UnwrapRef = reactive({ - tabItems: computed(() => state.tabs.map((tab) => generateTabItem(tab))), - keepAliveTabNames: computed(() => tabState.tabItems.filter((tabItem) => tabItem.keepAlive).map((tabItem) => tabItem.name)), - nonKeepAliveTabNames: computed(() => tabState.tabItems.filter((tabItem) => !tabItem.keepAlive).map((tabItem) => tabItem.name)), - currentTabItem: computed(() => tabState.tabItems.find((tabItem) => tabItem.name === state.activeTab)), - }); - - const generateTabItem = (tab: string | TabItem): TabItem => { - if (typeof tab === 'string') { - return { - name: tab, - label: tab, - keepAlive: false, - tabType: 'tab', - }; - } - return { - name: tab.name, - label: tab.label ?? tab.name, - keepAlive: !!tab.keepAlive, - tabType: tab.tabType ?? 'tab', - icon: tab.icon, - subItems: tab.subItems?.map((subItem) => generateTabItem(subItem)), - }; - }; - - return { - ...toRefs(tabState), - }; -}; diff --git a/packages/mirinae/src/hooks/context-menu-controller/__tests__/index.test.ts b/packages/mirinae/src/hooks/use-context-menu-controller/__tests__/index.test.ts similarity index 98% rename from packages/mirinae/src/hooks/context-menu-controller/__tests__/index.test.ts rename to packages/mirinae/src/hooks/use-context-menu-controller/__tests__/index.test.ts index f562cc7cb9..f012ae68fa 100644 --- a/packages/mirinae/src/hooks/context-menu-controller/__tests__/index.test.ts +++ b/packages/mirinae/src/hooks/use-context-menu-controller/__tests__/index.test.ts @@ -3,8 +3,8 @@ import { defineComponent, ref } from 'vue'; import { describe, expect } from 'vitest'; -import type { UseContextMenuControllerOptions } from '@/hooks/context-menu-controller'; -import { useContextMenuController } from '@/hooks/context-menu-controller'; +import type { UseContextMenuControllerOptions } from '@/hooks/use-context-menu-controller/use-context-menu-controller'; +import { useContextMenuController } from '@/hooks/use-context-menu-controller/use-context-menu-controller'; import PContextMenu from '@/inputs/context-menu/PContextMenu.vue'; import type { MenuItem } from '@/inputs/context-menu/type'; diff --git a/packages/mirinae/src/hooks/context-menu-controller/context-menu-attach.ts b/packages/mirinae/src/hooks/use-context-menu-controller/use-context-menu-attach.ts similarity index 100% rename from packages/mirinae/src/hooks/context-menu-controller/context-menu-attach.ts rename to packages/mirinae/src/hooks/use-context-menu-controller/use-context-menu-attach.ts diff --git a/packages/mirinae/src/hooks/context-menu-controller/index.ts b/packages/mirinae/src/hooks/use-context-menu-controller/use-context-menu-controller.ts similarity index 97% rename from packages/mirinae/src/hooks/context-menu-controller/index.ts rename to packages/mirinae/src/hooks/use-context-menu-controller/use-context-menu-controller.ts index b4f699f5f7..58396940aa 100644 --- a/packages/mirinae/src/hooks/context-menu-controller/index.ts +++ b/packages/mirinae/src/hooks/use-context-menu-controller/use-context-menu-controller.ts @@ -6,9 +6,9 @@ import { import { isEmpty } from 'lodash'; -import type { MenuAttachHandler } from '@/hooks/context-menu-controller/context-menu-attach'; -import { useContextMenuAttach } from '@/hooks/context-menu-controller/context-menu-attach'; -import { useContextMenuStyle } from '@/hooks/context-menu-style'; +import type { MenuAttachHandler } from '@/hooks/use-context-menu-controller/use-context-menu-attach'; +import { useContextMenuAttach } from '@/hooks/use-context-menu-controller/use-context-menu-attach'; +import { useContextMenuStyle } from '@/hooks/use-context-menu-style/use-context-menu-style'; import type { MenuItem } from '@/inputs/context-menu/type'; import { getTextHighlightRegex } from '@/utils/helpers'; diff --git a/packages/mirinae/src/hooks/context-menu-style/index.ts b/packages/mirinae/src/hooks/use-context-menu-style/use-context-menu-style.ts similarity index 100% rename from packages/mirinae/src/hooks/context-menu-style/index.ts rename to packages/mirinae/src/hooks/use-context-menu-style/use-context-menu-style.ts diff --git a/packages/mirinae/src/hooks/ignore-window-arrow-keydown-events/index.ts b/packages/mirinae/src/hooks/use-ignore-window-arrow-keydown-events/use-ignore-window-arrow-keydown-events.ts similarity index 100% rename from packages/mirinae/src/hooks/ignore-window-arrow-keydown-events/index.ts rename to packages/mirinae/src/hooks/use-ignore-window-arrow-keydown-events/use-ignore-window-arrow-keydown-events.ts diff --git a/packages/mirinae/src/hooks/list-focus/index.ts b/packages/mirinae/src/hooks/use-list-focus/use-list-focus.ts similarity index 100% rename from packages/mirinae/src/hooks/list-focus/index.ts rename to packages/mirinae/src/hooks/use-list-focus/use-list-focus.ts diff --git a/packages/mirinae/src/hooks/proxy-state/index.ts b/packages/mirinae/src/hooks/use-proxy-state/use-proxy-state.ts similarity index 100% rename from packages/mirinae/src/hooks/proxy-state/index.ts rename to packages/mirinae/src/hooks/use-proxy-state/use-proxy-state.ts diff --git a/packages/mirinae/src/hooks/query-search/index.ts b/packages/mirinae/src/hooks/use-query-search/use-query-search.ts similarity index 100% rename from packages/mirinae/src/hooks/query-search/index.ts rename to packages/mirinae/src/hooks/use-query-search/use-query-search.ts diff --git a/packages/mirinae/src/hooks/select/index.ts b/packages/mirinae/src/hooks/use-select/use-select.ts similarity index 83% rename from packages/mirinae/src/hooks/select/index.ts rename to packages/mirinae/src/hooks/use-select/use-select.ts index 5af181e02b..4826877ab7 100644 --- a/packages/mirinae/src/hooks/select/index.ts +++ b/packages/mirinae/src/hooks/use-select/use-select.ts @@ -5,21 +5,13 @@ import { import { pull, remove } from 'lodash'; -export interface SelectProps { - value?: any; - selected?: any | any[]; - disabled?: boolean; - predicate?: Predicate; - multiSelectable?: boolean; -} - -interface Predicate { +export interface SelectionPredicate { (value: any, current: any): boolean; } -interface SelectStateArgs { +interface UseSelectOptions { value?: ComputedRef | any; selected?: ComputedRef | any | any[]; - predicate?: ComputedRef | Predicate; + predicate?: ComputedRef | SelectionPredicate; disabled?: ComputedRef | boolean; multiSelectable?: ComputedRef | boolean; } @@ -28,7 +20,7 @@ interface SelectState { isSelected: boolean; } -const getSelectState = (state: UnwrapRef) => reactive({ +const getSelectState = (state: UnwrapRef) => reactive({ isSelected: computed(() => { if (Array.isArray(state.selected)) { if (state.predicate) { @@ -41,7 +33,7 @@ const getSelectState = (state: UnwrapRef) => reactive({ return state.selected === state.value; }), }); -const getSingleSelected = (state: UnwrapRef) => { +const getSingleSelected = (state: UnwrapRef) => { if (state.disabled) return undefined; let result: any; @@ -53,7 +45,7 @@ const getSingleSelected = (state: UnwrapRef) => { return result; }; -const getMultiSelected = (state: UnwrapRef, selectState: SelectState) => { +const getMultiSelected = (state: UnwrapRef, selectState: SelectState) => { if (state.disabled) return undefined; let result: any; @@ -78,7 +70,7 @@ const getMultiSelected = (state: UnwrapRef, selectState: Select export const useMultiSelect = ({ value, selected, predicate, disabled, -}: SelectStateArgs) => { +}: UseSelectOptions) => { const state = reactive({ value, selected, predicate, disabled, }); @@ -94,7 +86,7 @@ export const useMultiSelect = ({ export const useSingleSelect = ({ value, selected, predicate, disabled, -}: SelectStateArgs) => { +}: UseSelectOptions) => { const state = reactive({ value, selected, predicate, disabled, }); @@ -110,7 +102,7 @@ export const useSingleSelect = ({ export const useSelect = ({ value, selected, predicate, disabled, multiSelectable, -}: SelectStateArgs) => { +}: UseSelectOptions) => { const state = reactive({ value, selected, predicate, disabled, multiSelectable, }); diff --git a/packages/mirinae/src/hooks/use-tab/type.ts b/packages/mirinae/src/hooks/use-tab/type.ts new file mode 100644 index 0000000000..9bc29b679c --- /dev/null +++ b/packages/mirinae/src/hooks/use-tab/type.ts @@ -0,0 +1,36 @@ +import type { ComputedRef, Ref } from 'vue'; + +import type { TranslateResult } from 'vue-i18n'; + +/* Generic type T represents the additional properties that can be added to the tab item. */ +export type TabItem = { + /* name: The identifier for the tab. */ + name: string; + /* label: The display label for the tab. This supports localized string or other message formats. */ + label?: TranslateResult; + /* keepAlive: A flag indicating if the tab should be preserved when switching to other tabs. */ + keepAlive?: boolean; + /* subItems: An array containing nested `TabItem` elements for hierarchical tab structures. */ + subItems?: Array>; +} & T; + +export interface UseTabOptions { + /* tabs: An array containing tab information. Each tab can be a `string` or an object that follows the `TabItem` structure. */ + tabs: Ref>> | Array>; + /* activeTab: Represents the currently active tab. */ + activeTab: Ref | string; + /* defaultItem: An object that provides default properties for all generated tabs. + This ensures consistency across tab items when only partial information is provided. */ + defaultItem?: T; +} + +export interface UseTabReturns { + /* tabItems: An array containing the resolved tab items based on the `tabs` input and the optional `defaultItem`. It provides a processed list of tabs with consistent structure. */ + tabItems: ComputedRef[]>; + /* keepAliveTabNames: An array listing the names of tabs marked to be kept alive (`keepAlive: true`.) */ + keepAliveTabNames: ComputedRef; + /* nonKeepAliveTabNames: An array listing the names of tabs not set to be kept alive. */ + nonKeepAliveTabNames: ComputedRef; + /* currentTabItem: Represents the currently active tab item based on the `activeTab` input. If no matching tab is found, it returns `undefined`. */ + currentTabItem: ComputedRef|undefined>; +} diff --git a/packages/mirinae/src/hooks/use-tab/use-tab.stories.ts b/packages/mirinae/src/hooks/use-tab/use-tab.stories.ts new file mode 100644 index 0000000000..cbdcf88a5f --- /dev/null +++ b/packages/mirinae/src/hooks/use-tab/use-tab.stories.ts @@ -0,0 +1,262 @@ +import { ref } from 'vue'; + +import type { Meta } from '@storybook/vue'; +import type { ComponentProps } from 'vue-component-type-helpers'; + +import PI from '@/foundation/icons/PI.vue'; +import PTextarea from '@/inputs/textarea/PTextarea.vue'; + +import type { UseTabOptions } from './type'; +import { useTab } from './use-tab'; + +type UseTabPropsAndCustomArgs = ComponentProps>>; + +const meta: Meta = { + title: 'Hooks/useTab', +}; + +export default meta; + + +export const WithStringTabs = { + render: () => ({ + template: ` +
+
    +
  • + {{ tab.label }} +
  • +
+
+

currentTabItem:

+
{{ currentTabItem }}
+
+
+ `, + setup() { + const activeTab = ref('tab1'); + const { tabItems, currentTabItem } = useTab({ + tabs: ['tab1', 'tab2'], + activeTab, + }); + return { + activeTab, + tabItems, + currentTabItem, + }; + }, + }), +}; + +export const WithObjectTabs = { + render: () => ({ + template: ` +
+
    +
  • + {{ tab.label }} +
  • +
+
+

currentTabItem:

+
{{ currentTabItem }}
+
+
+ `, + setup() { + const activeTab = ref('tab1'); + const { tabItems, currentTabItem } = useTab({ + tabs: [{ name: 'tab1', label: 'Tab Labels' }, { name: 'tab2', label: 'Can be Duplicated' }, { name: 'tab3', label: 'Can be Duplicated' }], + activeTab, + }); + return { + activeTab, + tabItems, + currentTabItem, + }; + }, + }), +}; + +export const WithSubItems = { + render: () => ({ + template: ` +
+
    +
  • +

    {{ tab.label }}

    +
      +
    • + {{ subItem.label }} +
    • +
    +
  • +
+
+

activeTab:

+
{{ activeTab }}
+
+
+

activeSubTab:

+
{{ activeSubTab }}
+
+
+ `, + setup() { + const activeTab = ref('tab1'); + const { tabItems, currentTabItem } = useTab({ + tabs: ['tab1', { + name: 'tab2', + subItems: [ + { name: 'sub1', label: 'Sub1' }, + { name: 'sub2', label: 'Sub2' }, + ], + }], + activeTab, + }); + const activeSubTab = ref(''); + const handleClickTabItem = (tabItem) => { + activeTab.value = tabItem.name; + activeSubTab.value = ''; + }; + const handleClickSubItem = (subItem, tabItem) => { + activeTab.value = tabItem.name; + activeSubTab.value = subItem.name; + }; + return { + activeTab, + activeSubTab, + tabItems, + currentTabItem, + handleClickTabItem, + handleClickSubItem, + }; + }, + }), +}; + +export const WithKeepAlive = { + render: () => ({ + components: { + InnerComponent: { + components: { PTextarea }, + template: ` +
+

Input something in the input below and switch tabs

+ +
+ `, + }, + }, + template: ` +
+
    +
  • + {{ tab.label }} +
  • +
+ + + + +
+

currentTabItem:

+
{{ currentTabItem }}
+
+
+ `, + setup() { + const activeTab = ref('keep-alive'); + const { + tabItems, currentTabItem, + keepAliveTabNames, nonKeepAliveTabNames, + } = useTab({ + tabs: [{ + name: 'keep-alive', + label: 'Keep Alive', + keepAlive: true, + }, + { + name: 'no-keep-alive', + label: 'No Keep Alive', + keepAlive: false, + }], + activeTab, + }); + return { + activeTab, + tabItems, + currentTabItem, + keepAliveTabNames, + nonKeepAliveTabNames, + }; + }, + }), +}; + +export const WithDefaultItem = { + render: () => ({ + components: { PI }, + template: ` +
+
    +
  • + {{ tab.label }} +
  • +
+
+

currentTabItem:

+
{{ currentTabItem }}
+
+
+ `, + setup() { + const activeTab = ref('tab1'); + const { + tabItems, + currentTabItem, + } = useTab({ + tabs: ['tab1', 'tab2', { + name: 'tab3', + icon: 'ic_face-frown', + }], + activeTab, + defaultItem: { icon: 'ic_face-smile' }, + }); + return { + activeTab, + tabItems, + currentTabItem, + }; + }, + }), +}; diff --git a/packages/mirinae/src/hooks/use-tab/use-tab.ts b/packages/mirinae/src/hooks/use-tab/use-tab.ts new file mode 100644 index 0000000000..4cb77e042c --- /dev/null +++ b/packages/mirinae/src/hooks/use-tab/use-tab.ts @@ -0,0 +1,37 @@ +import { + computed, reactive, +} from 'vue'; + +import type { TabItem, UseTabOptions, UseTabReturns } from './type'; + +const generateTabItem = (tab: string|TabItem, defaultItem: T = {} as T): TabItem => { + if (typeof tab === 'string') { + return { + ...defaultItem, + name: tab, + label: tab, + }; + } + return { + ...defaultItem, + ...tab, + name: tab.name, + label: tab.label ?? tab.name, + keepAlive: !!tab.keepAlive, + subItems: tab.subItems?.map((subItem) => generateTabItem(subItem, defaultItem)), + }; +}; +export const useTab = ({ tabs, activeTab, defaultItem }: UseTabOptions): UseTabReturns => { + const state = reactive({ + tabs, + activeTab, + }); + const tabItems = computed[]>(() => state.tabs.map((tab) => generateTabItem(tab, defaultItem))); + + return { + tabItems, + keepAliveTabNames: computed(() => tabItems.value.filter((tabItem) => tabItem.keepAlive).map((tabItem) => tabItem.name)), + nonKeepAliveTabNames: computed(() => tabItems.value.filter((tabItem) => !tabItem.keepAlive).map((tabItem) => tabItem.name)), + currentTabItem: computed | undefined>(() => tabItems.value.find((tabItem) => tabItem.name === state.activeTab)), + }; +}; diff --git a/packages/mirinae/src/hooks/use-tab/useTab.mdx b/packages/mirinae/src/hooks/use-tab/useTab.mdx new file mode 100644 index 0000000000..98e4dbf47e --- /dev/null +++ b/packages/mirinae/src/hooks/use-tab/useTab.mdx @@ -0,0 +1,79 @@ +{/* useTab.mdx */} + +import {Canvas, Meta } from '@storybook/blocks'; + +import * as useTabStories from './use-tab.stories'; + + + + +# useTab + +The `useTab` hook manages tab-related state and behavior, supporting dynamic and complex tab structures. It offers several computed values to assist in tab management and allows for the use of a default item structure. + +
+
+ +## Type Declarations + +```typescript +import type { ComputedRef, Ref } from 'vue'; + +import type { TranslateResult } from 'vue-i18n'; + +/* Generic type T represents the additional properties that can be added to the tab item. */ +export type TabItem = { + /* name: The identifier for the tab. */ + name: string; + /* label: The display label for the tab. This supports localized string or other message formats. */ + label?: TranslateResult; + /* keepAlive: A flag indicating if the tab should be preserved when switching to other tabs. */ + keepAlive?: boolean; + /* subItems: An array containing nested `TabItem` elements for hierarchical tab structures. */ + subItems?: Array>; +} & T; + +export interface UseTabOptions { + /* tabs: An array containing tab information. Each tab can be a `string` or an object that follows the `TabItem` structure. */ + tabs: Ref>> | Array>; + /* activeTab: Represents the currently active tab. */ + activeTab: Ref | string; + /* defaultItem: An object that provides default properties for all generated tabs. + This ensures consistency across tab items when only partial information is provided. */ + defaultItem?: T; +} + +export interface UseTabReturns { + /* tabItems: An array containing the resolved tab items based on the `tabs` input and the optional `defaultItem`. It provides a processed list of tabs with consistent structure. */ + tabItems: ComputedRef[]>; + /* keepAliveTabNames: An array listing the names of tabs marked to be kept alive (`keepAlive: true`.) */ + keepAliveTabNames: ComputedRef; + /* nonKeepAliveTabNames: An array listing the names of tabs not set to be kept alive. */ + nonKeepAliveTabNames: ComputedRef; + /* currentTabItem: Represents the currently active tab item based on the `activeTab` input. If no matching tab is found, it returns `undefined`. */ + currentTabItem: ComputedRef|undefined>; +} +``` + +## Demo +
+ +### With String Tabs + +
+ +### With Object Tabs + +
+ +### With Sub Items + +
+ +### With Keep Alive + +
+ +### With Default Item + +
diff --git a/packages/mirinae/src/inputs/checkbox/PCheckbox.vue b/packages/mirinae/src/inputs/checkbox/PCheckbox.vue index b3a3af822c..8e519606c3 100644 --- a/packages/mirinae/src/inputs/checkbox/PCheckbox.vue +++ b/packages/mirinae/src/inputs/checkbox/PCheckbox.vue @@ -33,10 +33,15 @@ import { } from 'vue'; import PI from '@/foundation/icons/PI.vue'; -import type { SelectProps } from '@/hooks/select'; -import { useMultiSelect } from '@/hooks/select'; +import type { SelectionPredicate } from '@/hooks/use-select/use-select'; +import { useMultiSelect } from '@/hooks/use-select/use-select'; -interface CheckboxProps extends SelectProps { +interface CheckboxProps { + value?: any; + selected?: any | any[]; + disabled?: boolean; + predicate?: SelectionPredicate; + multiSelectable?: boolean; invalid?: boolean; indeterminate?: boolean; } diff --git a/packages/mirinae/src/inputs/context-menu/PContextMenu.vue b/packages/mirinae/src/inputs/context-menu/PContextMenu.vue index 485ed295cf..5ac99ca7c2 100644 --- a/packages/mirinae/src/inputs/context-menu/PContextMenu.vue +++ b/packages/mirinae/src/inputs/context-menu/PContextMenu.vue @@ -6,8 +6,8 @@ import { import { reduce } from 'lodash'; import PSpinner from '@/feedbacks/loading/spinner/PSpinner.vue'; -import { useListFocus } from '@/hooks/list-focus'; -import { useProxyValue } from '@/hooks/proxy-state'; +import { useListFocus } from '@/hooks/use-list-focus/use-list-focus'; +import { useProxyValue } from '@/hooks/use-proxy-state/use-proxy-state'; import PButton from '@/inputs/buttons/button/PButton.vue'; import PTextButton from '@/inputs/buttons/text-button/PTextButton.vue'; import PContextMenuItem from '@/inputs/context-menu/context-menu-item/PContextMenuItem.vue'; diff --git a/packages/mirinae/src/inputs/dropdown/filterable-query-dropdown/PFilterableQueryDropdown.vue b/packages/mirinae/src/inputs/dropdown/filterable-query-dropdown/PFilterableQueryDropdown.vue index 2053780fad..4c23e14bca 100644 --- a/packages/mirinae/src/inputs/dropdown/filterable-query-dropdown/PFilterableQueryDropdown.vue +++ b/packages/mirinae/src/inputs/dropdown/filterable-query-dropdown/PFilterableQueryDropdown.vue @@ -96,7 +96,7 @@ import { focus as vFocus } from 'vue-focus'; import PTag from '@/data-display/tags/PTag.vue'; import PI from '@/foundation/icons/PI.vue'; import { useContextMenuStyle, useProxyValue } from '@/hooks'; -import { useQuerySearch } from '@/hooks/query-search'; +import { useQuerySearch } from '@/hooks/use-query-search/use-query-search'; import PContextMenu from '@/inputs/context-menu/PContextMenu.vue'; import type { FilterableQueryDropdownProps } from '@/inputs/dropdown/filterable-query-dropdown/type'; import type { SelectDropdownMenuItem } from '@/inputs/dropdown/select-dropdown/type'; diff --git a/packages/mirinae/src/inputs/dropdown/select-dropdown/PSelectDropdown.stories.ts b/packages/mirinae/src/inputs/dropdown/select-dropdown/PSelectDropdown.stories.ts index f2b8ec59a0..db8dea579c 100644 --- a/packages/mirinae/src/inputs/dropdown/select-dropdown/PSelectDropdown.stories.ts +++ b/packages/mirinae/src/inputs/dropdown/select-dropdown/PSelectDropdown.stories.ts @@ -3,7 +3,7 @@ import { reactive, toRefs } from 'vue'; import type { Meta, StoryObj } from '@storybook/vue'; import type { ComponentProps } from 'vue-component-type-helpers'; -import { useProxyValue } from '@/hooks/proxy-state'; +import { useProxyValue } from '@/hooks/use-proxy-state/use-proxy-state'; import PButton from '@/inputs/buttons/button/PButton.vue'; import PToggleButton from '@/inputs/buttons/toggle-button/PToggleButton.vue'; import { menuItems } from '@/inputs/context-menu/mock'; diff --git a/packages/mirinae/src/inputs/dropdown/select-dropdown/PSelectDropdown.vue b/packages/mirinae/src/inputs/dropdown/select-dropdown/PSelectDropdown.vue index 2a60df2a3e..02818667a0 100644 --- a/packages/mirinae/src/inputs/dropdown/select-dropdown/PSelectDropdown.vue +++ b/packages/mirinae/src/inputs/dropdown/select-dropdown/PSelectDropdown.vue @@ -7,7 +7,7 @@ import { onClickOutside, useFocus } from '@vueuse/core'; import { debounce, reduce } from 'lodash'; import { useContextMenuController, useProxyValue } from '@/hooks'; -import { useIgnoreWindowArrowKeydownEvents } from '@/hooks/ignore-window-arrow-keydown-events'; +import { useIgnoreWindowArrowKeydownEvents } from '@/hooks/use-ignore-window-arrow-keydown-events/use-ignore-window-arrow-keydown-events'; import PContextMenu from '@/inputs/context-menu/PContextMenu.vue'; import type { ContextMenuType } from '@/inputs/context-menu/type'; import DropdownButton from '@/inputs/dropdown/select-dropdown/components/dropdown-button.vue'; diff --git a/packages/mirinae/src/inputs/dropdown/select-dropdown/type.ts b/packages/mirinae/src/inputs/dropdown/select-dropdown/type.ts index 058f25b2e5..c3bb47c7bb 100644 --- a/packages/mirinae/src/inputs/dropdown/select-dropdown/type.ts +++ b/packages/mirinae/src/inputs/dropdown/select-dropdown/type.ts @@ -1,4 +1,4 @@ -import type { MenuAttachHandler, MenuAttachHandlerRes } from '@/hooks/context-menu-controller/context-menu-attach'; +import type { MenuAttachHandler, MenuAttachHandlerRes } from '@/hooks/use-context-menu-controller/use-context-menu-attach'; import type { MenuItem } from '@/inputs/context-menu/type'; export interface SelectDropdownMenuItem extends MenuItem { diff --git a/packages/mirinae/src/inputs/input/query-input/PQueryInput.stories.ts b/packages/mirinae/src/inputs/input/query-input/PQueryInput.stories.ts index 9af538887e..5b00fd6866 100644 --- a/packages/mirinae/src/inputs/input/query-input/PQueryInput.stories.ts +++ b/packages/mirinae/src/inputs/input/query-input/PQueryInput.stories.ts @@ -3,7 +3,7 @@ import { reactive, toRefs } from 'vue'; import type { Meta, StoryObj } from '@storybook/vue'; import type { ComponentProps } from 'vue-component-type-helpers'; -import { useProxyValue } from '@/hooks/proxy-state'; +import { useProxyValue } from '@/hooks/use-proxy-state/use-proxy-state'; import { getKeyItemSets, getValueHandlerMap, getValueItems } from '@/inputs/input/query-input/mock'; import { getQueryInputArgs, getQueryInputArgTypes, getQueryInputParameters } from '@/inputs/input/query-input/story-helper'; import { INPUT_SIZE } from '@/inputs/input/text-input/type'; diff --git a/packages/mirinae/src/inputs/input/query-input/PQueryInput.vue b/packages/mirinae/src/inputs/input/query-input/PQueryInput.vue index 0ded1e70de..cdc59b6909 100644 --- a/packages/mirinae/src/inputs/input/query-input/PQueryInput.vue +++ b/packages/mirinae/src/inputs/input/query-input/PQueryInput.vue @@ -102,7 +102,7 @@ import { isEqual } from 'lodash'; import PI from '@/foundation/icons/PI.vue'; import { useContextMenuStyle, useProxyValue } from '@/hooks'; -import { useQuerySearch } from '@/hooks/query-search'; +import { useQuerySearch } from '@/hooks/use-query-search/use-query-search'; // eslint-disable-next-line @typescript-eslint/consistent-type-imports import PContextMenu from '@/inputs/context-menu/PContextMenu.vue'; import type { SelectDropdownAppearanceType } from '@/inputs/dropdown/select-dropdown/type'; diff --git a/packages/mirinae/src/inputs/input/text-input/PTextInput.vue b/packages/mirinae/src/inputs/input/text-input/PTextInput.vue index 73e08a609b..623a1d7e0f 100644 --- a/packages/mirinae/src/inputs/input/text-input/PTextInput.vue +++ b/packages/mirinae/src/inputs/input/text-input/PTextInput.vue @@ -17,9 +17,9 @@ :key="index" :deletable="!disabled" :selected="index === deleteTargetIdx" - :invalid="isSelectedItemInvalid(tag, index)" + :invalid="isSelectedItemInvalid(tag, Number(index))" class="tag" - @delete="handleDeleteTag(tag, index)" + @delete="handleDeleteTag(tag, Number(index))" > {{ tag.label || tag.name }} @@ -129,9 +129,9 @@ import type { TranslateResult } from 'vue-i18n'; import PBadge from '@/data-display/badge/PBadge.vue'; import PTag from '@/data-display/tags/PTag.vue'; import PI from '@/foundation/icons/PI.vue'; -import { useIgnoreWindowArrowKeydownEvents } from '@/hooks'; -import { useContextMenuController } from '@/hooks/context-menu-controller'; -import { useProxyValue } from '@/hooks/proxy-state'; +import { useContextMenuController } from '@/hooks/use-context-menu-controller/use-context-menu-controller'; +import { useIgnoreWindowArrowKeydownEvents } from '@/hooks/use-ignore-window-arrow-keydown-events/use-ignore-window-arrow-keydown-events'; +import { useProxyValue } from '@/hooks/use-proxy-state/use-proxy-state'; import PButton from '@/inputs/buttons/button/PButton.vue'; import PContextMenu from '@/inputs/context-menu/PContextMenu.vue'; import type { MenuItem } from '@/inputs/context-menu/type'; diff --git a/packages/mirinae/src/inputs/radio/PRadio.vue b/packages/mirinae/src/inputs/radio/PRadio.vue index e7074b4946..f26acf7a72 100644 --- a/packages/mirinae/src/inputs/radio/PRadio.vue +++ b/packages/mirinae/src/inputs/radio/PRadio.vue @@ -39,10 +39,15 @@ import { } from 'vue'; import PI from '@/foundation/icons/PI.vue'; -import type { SelectProps } from '@/hooks/select'; -import { useSingleSelect } from '@/hooks/select'; +import type { SelectionPredicate } from '@/hooks/use-select/use-select'; +import { useSingleSelect } from '@/hooks/use-select/use-select'; -interface Props extends SelectProps { +interface Props { + value?: any; + selected?: any | any[]; + disabled?: boolean; + predicate?: SelectionPredicate; + multiSelectable?: boolean; invalid?: boolean; readonly?: boolean; } diff --git a/packages/mirinae/src/inputs/search/query-search/PQuerySearch.vue b/packages/mirinae/src/inputs/search/query-search/PQuerySearch.vue index 3c8d6e51ea..526a10f8b8 100644 --- a/packages/mirinae/src/inputs/search/query-search/PQuerySearch.vue +++ b/packages/mirinae/src/inputs/search/query-search/PQuerySearch.vue @@ -102,7 +102,7 @@ import vClickOutside from 'v-click-outside'; import { focus as vFocus } from 'vue-focus'; import PI from '@/foundation/icons/PI.vue'; -import { useQuerySearch } from '@/hooks/query-search'; +import { useQuerySearch } from '@/hooks/use-query-search/use-query-search'; import PContextMenu from '@/inputs/context-menu/PContextMenu.vue'; import type { KeyMenuItem, ValueMenuItem } from '@/inputs/search/query-search/type'; import PSearch from '@/inputs/search/search/PSearch.vue'; diff --git a/packages/mirinae/src/inputs/select-button/PSelectButton.vue b/packages/mirinae/src/inputs/select-button/PSelectButton.vue index f8ed436a8c..2c7f16ed8d 100644 --- a/packages/mirinae/src/inputs/select-button/PSelectButton.vue +++ b/packages/mirinae/src/inputs/select-button/PSelectButton.vue @@ -26,8 +26,8 @@ import { computed, defineComponent, reactive } from 'vue'; import PI from '@/foundation/icons/PI.vue'; -import type { SelectProps } from '@/hooks/select'; -import { useSelect } from '@/hooks/select'; +import type { SelectionPredicate } from '@/hooks/use-select/use-select'; +import { useSelect } from '@/hooks/use-select/use-select'; import type { SelectButtonLayoutType, SelectButtonSize, @@ -42,7 +42,12 @@ import { import { gray, white } from '@/styles/colors.cjs'; -interface Props extends SelectProps { +interface Props { + value?: any; + selected?: any | any[]; + disabled?: boolean; + predicate?: SelectionPredicate; + multiSelectable?: boolean; layout?: SelectButtonLayoutType; styleType?: SelectButtonStyleType; size?: SelectButtonSize; diff --git a/packages/mirinae/src/inputs/select-card/PSelectCard.vue b/packages/mirinae/src/inputs/select-card/PSelectCard.vue index 9df1c04daf..df3a8034c7 100644 --- a/packages/mirinae/src/inputs/select-card/PSelectCard.vue +++ b/packages/mirinae/src/inputs/select-card/PSelectCard.vue @@ -37,11 +37,16 @@ import { import PLazyImg from '@/feedbacks/loading/lazy-img/PLazyImg.vue'; import PI from '@/foundation/icons/PI.vue'; -import type { SelectProps } from '@/hooks/select'; -import { useSelect } from '@/hooks/select'; +import type { SelectionPredicate } from '@/hooks/use-select/use-select'; +import { useSelect } from '@/hooks/use-select/use-select'; -interface Props extends SelectProps { +interface Props { + value?: any; + selected?: any | any[]; + disabled?: boolean; + predicate?: SelectionPredicate; + multiSelectable?: boolean; block?: boolean; imageUrl?: string; icon?: string|boolean; diff --git a/packages/mirinae/src/inputs/select-status/PSelectStatus.vue b/packages/mirinae/src/inputs/select-status/PSelectStatus.vue index 8ccbb76f7a..d0c2253ff0 100644 --- a/packages/mirinae/src/inputs/select-status/PSelectStatus.vue +++ b/packages/mirinae/src/inputs/select-status/PSelectStatus.vue @@ -16,14 +16,20 @@ import { computed, defineComponent } from 'vue'; import PStatus from '@/data-display/status/PStatus.vue'; +import type { AnimationType } from '@/foundation/icons/config'; import { ANIMATION_TYPE } from '@/foundation/icons/config'; -import type { SelectProps } from '@/hooks/select'; -import { useSelect } from '@/hooks/select'; +import type { SelectionPredicate } from '@/hooks/use-select/use-select'; +import { useSelect } from '@/hooks/use-select/use-select'; -interface Props extends SelectProps { +interface Props { + value?: any; + selected?: any | any[]; + disabled?: boolean; + predicate?: SelectionPredicate; + multiSelectable?: boolean; icon?: string; iconColor?: string; - iconAnimation?: ANIMATION_TYPE; + iconAnimation?: AnimationType; disableCheckIcon?: boolean; } diff --git a/packages/mirinae/src/navigation/tabs/ballon-tab/PBalloonTab.vue b/packages/mirinae/src/navigation/tabs/ballon-tab/PBalloonTab.vue index be2ec5e921..da794cd3b1 100644 --- a/packages/mirinae/src/navigation/tabs/ballon-tab/PBalloonTab.vue +++ b/packages/mirinae/src/navigation/tabs/ballon-tab/PBalloonTab.vue @@ -46,7 +46,7 @@ import { } from 'vue'; -import { useTab } from '@/hooks/tab'; +import { useTab } from '@/hooks/use-tab/use-tab'; import { BALLOON_TAB_POSITION, BALLOON_TAB_SIZE, BALLOON_TAB_STYLE_TYPE, } from '@/navigation/tabs/ballon-tab/config'; diff --git a/packages/mirinae/src/navigation/tabs/box-tab/PBoxTab.vue b/packages/mirinae/src/navigation/tabs/box-tab/PBoxTab.vue index 6b17e67782..e6ed201bb2 100644 --- a/packages/mirinae/src/navigation/tabs/box-tab/PBoxTab.vue +++ b/packages/mirinae/src/navigation/tabs/box-tab/PBoxTab.vue @@ -35,7 +35,7 @@ import { computed, defineComponent } from 'vue'; import PI from '@/foundation/icons/PI.vue'; -import { useTab } from '@/hooks/tab'; +import { useTab } from '@/hooks/use-tab/use-tab'; import { BOX_TAB_STYLE_TYPE } from '@/navigation/tabs/box-tab/config'; import type { TabItem } from '@/navigation/tabs/tab/type'; diff --git a/packages/mirinae/src/navigation/tabs/button-tab/PButtonTab.vue b/packages/mirinae/src/navigation/tabs/button-tab/PButtonTab.vue index 3e9dd8f2ab..4dea2a2798 100644 --- a/packages/mirinae/src/navigation/tabs/button-tab/PButtonTab.vue +++ b/packages/mirinae/src/navigation/tabs/button-tab/PButtonTab.vue @@ -40,7 +40,7 @@ import { computed, defineComponent, reactive, toRefs, } from 'vue'; -import { useTab } from '@/hooks/tab'; +import { useTab } from '@/hooks/use-tab/use-tab'; import type { TabItem } from '@/navigation/tabs/tab/type'; diff --git a/packages/mirinae/src/navigation/tabs/tab/PTab.vue b/packages/mirinae/src/navigation/tabs/tab/PTab.vue index 72e90b1d2b..fb54f16a24 100644 --- a/packages/mirinae/src/navigation/tabs/tab/PTab.vue +++ b/packages/mirinae/src/navigation/tabs/tab/PTab.vue @@ -7,7 +7,7 @@ import { onClickOutside, useResizeObserver } from '@vueuse/core'; import { throttle } from 'lodash'; import PI from '@/foundation/icons/PI.vue'; -import { useTab } from '@/hooks/tab'; +import { useTab } from '@/hooks/use-tab/use-tab'; import PTextButton from '@/inputs/buttons/text-button/PTextButton.vue'; import PContextMenu from '@/inputs/context-menu/PContextMenu.vue'; import type { MenuItem } from '@/inputs/context-menu/type'; diff --git a/packages/mirinae/src/navigation/tabs/tab/type.ts b/packages/mirinae/src/navigation/tabs/tab/type.ts index 6daeba4252..e79347670c 100644 --- a/packages/mirinae/src/navigation/tabs/tab/type.ts +++ b/packages/mirinae/src/navigation/tabs/tab/type.ts @@ -1,4 +1,4 @@ -import type { TranslateResult } from 'vue-i18n'; +import type { TabItem as BaseTabItem } from '@/hooks/use-tab/use-tab'; export const TAB_MENU_TYPE = { @@ -10,11 +10,7 @@ export const TAB_MENU_TYPE = { export type TabMenuType = typeof TAB_MENU_TYPE[keyof typeof TAB_MENU_TYPE]; -export interface TabItem { - name: string; +export type TabItem = BaseTabItem<{ tabType?: TabMenuType; - label?: string | TranslateResult; - keepAlive?: boolean; - subItems?: TabItem[]; icon?: string; -} +}>; diff --git a/packages/mirinae/src/navigation/toolbox/PToolbox.vue b/packages/mirinae/src/navigation/toolbox/PToolbox.vue index 0e33a91215..d44c622379 100644 --- a/packages/mirinae/src/navigation/toolbox/PToolbox.vue +++ b/packages/mirinae/src/navigation/toolbox/PToolbox.vue @@ -107,7 +107,7 @@ import { import { groupBy } from 'lodash'; -import { useProxyValue } from '@/hooks/proxy-state'; +import { useProxyValue } from '@/hooks/use-proxy-state/use-proxy-state'; import PIconButton from '@/inputs/buttons/icon-button/PIconButton.vue'; import type { MenuItem } from '@/inputs/context-menu/type'; import PSelectDropdown from '@/inputs/dropdown/select-dropdown/PSelectDropdown.vue';