frappe-ui 0.1.224 → 0.1.226

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "frappe-ui",
3
- "version": "0.1.224",
3
+ "version": "0.1.226",
4
4
  "description": "A set of components and utilities for rapid UI development",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -14,6 +14,7 @@
14
14
  type="checkbox"
15
15
  :disabled="disabled"
16
16
  :id="htmlId"
17
+ :checked="Boolean(modelValue)"
17
18
  @change="
18
19
  (e) =>
19
20
  $emit('update:modelValue', (e.target as HTMLInputElement).checked)
@@ -314,6 +314,7 @@ defineExpose({
314
314
  @click="handleClick"
315
315
  >
316
316
  <div class="flex items-center gap-2 flex-1 overflow-hidden">
317
+ <slot name="prefix" />
317
318
  <RenderIcon v-if="selectedOptionIcon" :icon="selectedOptionIcon" />
318
319
  <ComboboxInput
319
320
  :value="searchTerm"
@@ -54,10 +54,10 @@ const variants = ['subtle', 'outline']
54
54
  />
55
55
  </div>
56
56
  </Variant>
57
- <Variant title="autocomplete">
57
+ <Variant title="Combobox">
58
58
  <div class="p-2">
59
59
  <FormControl
60
- type="autocomplete"
60
+ type="combobox"
61
61
  :options="[
62
62
  { label: 'One', value: '1' },
63
63
  { label: 'Two', value: '2' },
@@ -20,6 +20,15 @@
20
20
  <slot name="prefix" />
21
21
  </template>
22
22
  </Select>
23
+ <Combobox
24
+ v-else-if="type === 'combobox'"
25
+ :id="id"
26
+ v-bind="{ ...controlAttrs, variant }"
27
+ >
28
+ <template #prefix v-if="$slots.prefix">
29
+ <slot name="prefix" />
30
+ </template>
31
+ </Combobox>
23
32
  <Autocomplete
24
33
  v-else-if="type === 'autocomplete'"
25
34
  v-bind="{ ...controlAttrs }"
@@ -66,6 +75,7 @@ import { Select } from '../Select'
66
75
  import { Textarea } from '../Textarea'
67
76
  import { Checkbox } from '../Checkbox'
68
77
  import { Autocomplete } from '../Autocomplete'
78
+ import { Combobox } from '../Combobox'
69
79
  import FormLabel from '../FormLabel.vue'
70
80
  import type { FormControlProps } from './types'
71
81
 
@@ -3,7 +3,7 @@ import type { TextInputTypes } from '../types/TextInput'
3
3
  export interface FormControlProps {
4
4
  label?: string
5
5
  description?: string
6
- type?: TextInputTypes | 'textarea' | 'select' | 'checkbox' | 'autocomplete'
6
+ type?: TextInputTypes | 'textarea' | 'select' | 'checkbox' | 'autocomplete' | 'combobox'
7
7
  size?: 'sm' | 'md'
8
8
  variant?: 'subtle' | 'outline'
9
9
  required?: boolean
@@ -77,7 +77,7 @@ const selectClasses = computed(() => {
77
77
  const selectOptions = computed(() => {
78
78
  const str = typeof props.options?.[0] == 'string'
79
79
  const tmp = props.options?.map((x) => ({ label: x, value: x }))
80
- return (str ? tmp : props.options)?.filter((x) => x && x.value) || []
80
+ return (str ? tmp : props.options)?.filter((x) => x && String(x.value)) || []
81
81
  })
82
82
  </script>
83
83
 
@@ -1,7 +1,10 @@
1
1
  <script setup lang="ts">
2
- import { h, reactive } from 'vue'
2
+ import { reactive } from 'vue'
3
3
  import Tabs from './Tabs.vue'
4
- import FeatherIcon from '../FeatherIcon.vue'
4
+ import LucideGithub from "~icons/lucide/github";
5
+ import LucideTwitter from "~icons/lucide/twitter";
6
+ import LucideLinkedin from "~icons/lucide/linkedin";
7
+
5
8
  const state = reactive({
6
9
  index: 0,
7
10
  tabs_without_icon: [
@@ -21,24 +24,25 @@ const state = reactive({
21
24
  'LinkedIn is an American business and employment-oriented online service that operates via websites and mobile apps.',
22
25
  },
23
26
  ],
27
+
24
28
  tabs_with_icon: [
25
29
  {
26
30
  label: 'Github',
27
31
  content:
28
32
  'Github is a code hosting platform for version control and collaboration. It lets you and others work together on projects from anywhere.',
29
- icon: h(FeatherIcon, { class: 'w-4 h-4', name: 'github' }),
33
+ icon: LucideGithub,
30
34
  },
31
35
  {
32
36
  label: 'Twitter',
33
37
  content:
34
38
  'Twitter is an American microblogging and social networking service on which users post and interact with messages known as "tweets".',
35
- icon: h(FeatherIcon, { class: 'w-4 h-4', name: 'twitter' }),
39
+ icon: LucideTwitter,
36
40
  },
37
41
  {
38
42
  label: 'Linkedin',
39
43
  content:
40
44
  'LinkedIn is an American business and employment-oriented online service that operates via websites and mobile apps.',
41
- icon: h(FeatherIcon, { class: 'w-4 h-4', name: 'linkedin' }),
45
+ icon: LucideLinkedin,
42
46
  },
43
47
  ],
44
48
  })
@@ -48,7 +52,6 @@ const state = reactive({
48
52
  <Story :layout="{ type: 'grid', width: '80%' }">
49
53
  <Variant title="Without Icon">
50
54
  <Tabs
51
- as="div"
52
55
  class="border rounded"
53
56
  v-model="state.index"
54
57
  :tabs="state.tabs_without_icon"
@@ -60,9 +63,9 @@ const state = reactive({
60
63
  </template>
61
64
  </Tabs>
62
65
  </Variant>
66
+
63
67
  <Variant title="With Icon">
64
68
  <Tabs
65
- as="div"
66
69
  class="border rounded"
67
70
  v-model="state.index"
68
71
  :tabs="state.tabs_with_icon"
@@ -74,13 +77,13 @@ const state = reactive({
74
77
  </template>
75
78
  </Tabs>
76
79
  </Variant>
80
+
77
81
  <Variant title="Vertical Tabs">
78
82
  <Tabs
79
- as="div"
80
83
  class="border rounded"
81
84
  v-model="state.index"
82
85
  :tabs="state.tabs_with_icon"
83
- vertical
86
+ :vertical='true'
84
87
  >
85
88
  <template #tab-panel="{ tab }">
86
89
  <div class="p-5">
@@ -1,57 +1,64 @@
1
+ <script setup lang="ts">
2
+ import {
3
+ TabsContent,
4
+ TabsIndicator,
5
+ TabsList,
6
+ TabsRoot,
7
+ TabsTrigger,
8
+ } from 'reka-ui'
9
+
10
+ import type { TabProps } from './types'
11
+ import { h } from 'vue'
12
+
13
+ const props = defineProps<TabProps>()
14
+
15
+ const indicatorXCss = `left-0 bottom-0 h-[1px] w-[--reka-tabs-indicator-size] transition-[width,transform]
16
+ translate-x-[--reka-tabs-indicator-position] translate-y-[1px]`
17
+
18
+ const indicatorYCss = `right-0 w-[1px] h-[--reka-tabs-indicator-size] transition-[height,transform]
19
+ translate-y-[--reka-tabs-indicator-position] translate-x-[1px]`
20
+
21
+ // Using a plain <button> element via `h('button')` to avoid picking up
22
+ // the globally registered Button component and its styles.
23
+ const Btn = h('button')
24
+ </script>
25
+
1
26
  <template>
2
- <TabGroup
3
- v-bind="
4
- as !== 'template'
5
- ? {
6
- as,
7
- class: ['flex flex-1 overflow-hidden', vertical ? '' : 'flex-col '],
8
- }
9
- : {}
10
- "
11
- :defaultIndex="tabIndex"
12
- :selectedIndex="tabIndex"
13
- @change="(idx) => (tabIndex = idx)"
27
+ <TabsRoot
28
+ :as="props.as"
29
+ class="data-[orientation=vertical]:flex"
30
+ :orientation="props.vertical ? 'vertical' : 'horizontal'"
31
+ :default-value="props.tabs[0].label"
14
32
  >
15
- <slot>
16
- <TabList v-slot="{ tab, selected }">
17
- <slot name="tab-item" v-bind="{ tab, selected }" />
18
- </TabList>
19
- <TabPanel v-slot="{ tab }">
20
- <slot name="tab-panel" v-bind="{ tab }" />
21
- </TabPanel>
22
- </slot>
23
- </TabGroup>
24
- </template>
33
+ <TabsList
34
+ class="relative flex data-[orientation=vertical]:flex-col p-1 border-b data-[orientation=vertical]:border-r"
35
+ >
36
+ <TabsIndicator
37
+ class="absolute rounded-full duration-300"
38
+ :class="props.vertical ? indicatorYCss : indicatorXCss"
39
+ >
40
+ <div class="w-full h-full bg-surface-gray-7" />
41
+ </TabsIndicator>
25
42
 
26
- <script setup>
27
- import TabList from './TabList.vue'
28
- import TabPanel from './TabPanel.vue'
29
- import { TabGroup } from '@headlessui/vue'
30
- import { computed, provide } from 'vue'
31
-
32
- const props = defineProps({
33
- as: {
34
- type: String,
35
- default: 'template',
36
- },
37
- tabs: {
38
- type: Array,
39
- required: true,
40
- },
41
- vertical: {
42
- type: Boolean,
43
- default: false,
44
- },
45
- })
46
-
47
- const tabIndex = defineModel()
48
-
49
- provide(
50
- 'tab',
51
- computed(() => ({
52
- tabIndex,
53
- tabs: props.tabs,
54
- vertical: props.vertical,
55
- })),
56
- )
57
- </script>
43
+ <TabsTrigger as="template" v-for="(tab, i) in props.tabs" :value="i">
44
+ <slot name="tab-item" v-bind="{ tab }">
45
+ <component
46
+ :is="tab.route ? 'router-link' : Btn"
47
+ :to="tab.route"
48
+ class="flex items-center gap-1.5 text-base text-ink-gray-5 duration-300 ease-in-out hover:text-ink-gray-9 p-2.5 data-[state=active]:text-ink-gray-9"
49
+ :class="{ 'py-2.5': props.vertical, }"
50
+ >
51
+ <component v-if="tab.icon" :is="tab.icon" class="size-4">
52
+ </component>
53
+
54
+ {{ tab.label }}
55
+ </component>
56
+ </slot>
57
+ </TabsTrigger>
58
+ </TabsList>
59
+
60
+ <TabsContent v-for="(tab, i) in props.tabs" :value="i">
61
+ <slot name="tab-panel" v-bind="{ tab }"> </slot>
62
+ </TabsContent>
63
+ </TabsRoot>
64
+ </template>
@@ -0,0 +1,11 @@
1
+ type Tab = {
2
+ label: string
3
+ icon?: string
4
+ route?:string
5
+ }
6
+
7
+ export interface TabProps {
8
+ as?: string
9
+ tabs: Tab[]
10
+ vertical?: Boolean
11
+ }
@@ -257,7 +257,7 @@ function openLinkEditor(href: string, anchor: HTMLElement): Promise<string> {
257
257
  content: container,
258
258
  trigger: 'manual',
259
259
  interactive: true,
260
- appendTo: document.body,
260
+ appendTo: () => anchor.closest('[role="dialog"]') || document.body,
261
261
  placement: 'top',
262
262
  arrow: false,
263
263
  theme: 'link-editor',
package/src/index.ts CHANGED
@@ -33,8 +33,6 @@ export * from './components/Spinner'
33
33
  export * from './components/Switch'
34
34
  export * from './components/TabButtons'
35
35
  export { default as Tabs } from './components/Tabs/Tabs.vue'
36
- export { default as TabList } from './components/Tabs/TabList.vue'
37
- export { default as TabPanel } from './components/Tabs/TabPanel.vue'
38
36
  export * from './components/TextInput'
39
37
  export * from './components/Textarea'
40
38
  export * from './components/TextEditor'
@@ -14,9 +14,7 @@
14
14
  "skipLibCheck": true,
15
15
  "noEmit": true,
16
16
  "types": ["vitest/globals", "unplugin-icons/types/vue"],
17
- "baseUrl": ".",
18
- "paths": {
19
- "@/*": ["src/*"]
20
- }
17
+ "declaration": true,
18
+ "emitDeclarationOnly": true,
21
19
  }
22
20
  }
package/vite/index.js CHANGED
@@ -29,6 +29,22 @@ function frappeuiPlugin(
29
29
  if (options.buildConfig) {
30
30
  plugins.push(buildConfig(options.buildConfig))
31
31
  }
32
+
33
+ const DepsIncludePlugin = {
34
+ name: 'optimize-deps-include',
35
+ config(config) {
36
+ if (!config.optimizeDeps) config.optimizeDeps = {}
37
+
38
+ const includedDeps = config.optimizeDeps?.include || []
39
+ const moduleName = 'highlight.js/lib/core'
40
+
41
+ if (includedDeps.includes(moduleName)) return
42
+ config.optimizeDeps.include = [moduleName, ...includedDeps]
43
+ },
44
+ }
45
+
46
+ plugins.push(DepsIncludePlugin)
47
+
32
48
  return plugins
33
49
  }
34
50
 
@@ -1,82 +0,0 @@
1
- <template>
2
- <TabList
3
- class="relative flex"
4
- :class="
5
- t.vertical
6
- ? 'flex-col border-r overflow-y-auto'
7
- : 'gap-7.5 border-b overflow-x-auto items-center px-5'
8
- "
9
- >
10
- <Tab
11
- ref="tabRef"
12
- as="template"
13
- v-for="(tab, i) in t.tabs"
14
- :key="i"
15
- v-slot="{ selected }"
16
- class="focus:outline-none focus:transition-none"
17
- >
18
- <slot v-bind="{ tab, selected }">
19
- <button
20
- class="flex items-center gap-1.5 text-base text-ink-gray-5 duration-300 ease-in-out hover:text-ink-gray-9"
21
- :class="[
22
- selected ? 'text-ink-gray-9' : '',
23
- t.vertical
24
- ? 'py-2.5 px-4 border-r border-transparent hover:border-outline-gray-3'
25
- : 'py-3 border-b border-transparent hover:border-outline-gray-3',
26
- ]"
27
- >
28
- <component v-if="tab.icon" :is="tab.icon" class="size-4" />
29
- {{ tab.label }}
30
- </button>
31
- </slot>
32
- </Tab>
33
- <div
34
- ref="indicator"
35
- class="tab-indicator absolute bg-surface-gray-7"
36
- :class="[t.vertical ? 'right-0 w-px' : 'bottom-0 h-px', transitionClass]"
37
- />
38
- </TabList>
39
- </template>
40
- <script setup>
41
- import { TabList, Tab } from '@headlessui/vue'
42
- import { ref, watch, computed, onMounted, nextTick, inject } from 'vue'
43
-
44
- const t = inject('tab')
45
-
46
- const tabRef = ref([])
47
- const indicator = ref(null)
48
- const tabsLength = computed(() => t.value.tabs.value?.length)
49
-
50
- const transitionClass = ref('')
51
-
52
- function moveIndicator(index) {
53
- if (index >= tabsLength.value) {
54
- index = tabsLength.value - 1
55
- }
56
- const selectedTab = tabRef.value[index].el
57
- if (t.value.vertical.value) {
58
- indicator.value.style.height = `${selectedTab.offsetHeight}px`
59
- indicator.value.style.top = `${selectedTab.offsetTop}px`
60
- } else {
61
- indicator.value.style.width = `${selectedTab.offsetWidth}px`
62
- indicator.value.style.left = `${selectedTab.offsetLeft}px`
63
- }
64
- }
65
-
66
- watch(
67
- () => t.value.tabIndex.value,
68
- (index) => {
69
- if (index >= tabsLength.value) {
70
- t.value.tabIndex.value = tabsLength.value - 1
71
- }
72
- transitionClass.value = 'transition-all duration-300 ease-in-out'
73
- nextTick(() => moveIndicator(index))
74
- },
75
- )
76
-
77
- onMounted(() => {
78
- nextTick(() => moveIndicator(t.value.tabIndex.value))
79
- // Fix for indicator not moving on initial load
80
- setTimeout(() => moveIndicator(t.value.tabIndex.value), 100)
81
- })
82
- </script>
@@ -1,17 +0,0 @@
1
- <template>
2
- <TabPanels class="flex flex-1 overflow-hidden">
3
- <TabPanel
4
- class="flex flex-1 flex-col overflow-y-auto focus:outline-none"
5
- v-for="(tab, i) in t.tabs"
6
- :key="i"
7
- >
8
- <slot v-bind="{ tab }" />
9
- </TabPanel>
10
- </TabPanels>
11
- </template>
12
- <script setup>
13
- import { TabPanels, TabPanel } from '@headlessui/vue'
14
- import { inject } from 'vue'
15
-
16
- const t = inject('tab')
17
- </script>
@@ -1,97 +0,0 @@
1
- ## Props
2
-
3
- ### tabs
4
-
5
- It is an array of objects which contains the following attributes:
6
-
7
- 1. `label` is the name of the tab, it is required.
8
- 2. `icon` is the icon to be shown in the tab, it accept component and it is
9
- optional.
10
- 3. You can add more attributes which can be used for custom rendering in the tab
11
- header or content.
12
-
13
- ### v-model
14
-
15
- It is used to set the active tab or change the active tab. It is required.
16
-
17
- ### vertical
18
-
19
- It is used to show the tabs vertically. It is optional.
20
-
21
- ### as
22
-
23
- You can set it to `div` to wrap tabs in a `div`. It can be any valid HTML tag.
24
- This is useful to control the layout of the tabs. It is optional.
25
-
26
- 1. `as="div"` or any valid HTML tag
27
-
28
- ```html
29
- <div>
30
- <!-- container div -->
31
- <div>
32
- <div active>Tab 1</div>
33
- <div>Tab 2</div>
34
- <div>Tab 3</div>
35
- </div>
36
- <div>
37
- <div active>Content 1</div>
38
- <div>Content 2</div>
39
- <div>Content 3</div>
40
- </div>
41
- </div>
42
- ```
43
-
44
- 2. `as` is not set
45
-
46
- ```html
47
- <div>
48
- <div active>Tab 1</div>
49
- <div>Tab 2</div>
50
- <div>Tab 3</div>
51
- </div>
52
- <div>
53
- <div active>Content 1</div>
54
- <div>Content 2</div>
55
- <div>Content 3</div>
56
- </div>
57
- ```
58
-
59
- ## Slots
60
-
61
- 1. **tab-item:** You can use this slot to render custom tab items. It is
62
- optional.
63
- 2. **tab-panel:** You can use this slot to render custom tab panels. It is
64
- required. Example:
65
-
66
- ```vue
67
- <Tabs v-model="tabIndex" :tabs="tabs">
68
- <template #tab-item="{ tab, selected }">
69
- <div :class="{ 'text-gray-900 font-semibold': selected }">
70
- <span>{{ tab.label }}</span>
71
- <span>{{ tab.icon }}</span>
72
- </div>
73
- </template>
74
- <template #tab-panel="{ tab }">
75
- <div>{{ tab.content }}</div>
76
- </template>
77
- </Tabs>
78
- ```
79
-
80
- ## Layout Customization
81
-
82
- You can customize the layout of the tabs by using `<TabList />` and `<TabPanels />`
83
- components.
84
-
85
- ```vue
86
- <Tabs v-model="tabIndex" :tabs="tabs">
87
- <TabList v-slot="{ tab, selected }">
88
- <div :class="{ 'text-gray-900 font-semibold': selected }">
89
- <span>{{ tab.label }}</span>
90
- <span>{{ tab.icon }}</span>
91
- </div>
92
- </TabList>
93
- <TabPanel v-slot="{ tab }">
94
- <div>{{ tab.content }}</div>
95
- </TabPanel>
96
- </Tabs>
97
- ```