frappe-ui 0.1.180 → 0.1.181

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.180",
3
+ "version": "0.1.181",
4
4
  "description": "A set of components and utilities for rapid UI development",
5
5
  "main": "./src/index.ts",
6
6
  "type": "module",
@@ -67,7 +67,7 @@
67
67
  "highlight.js": "^11.11.1",
68
68
  "idb-keyval": "^6.2.0",
69
69
  "lowlight": "^3.3.0",
70
- "lucide-static": "^0.479.0",
70
+ "lucide-static": "^0.535.0",
71
71
  "marked": "^15.0.12",
72
72
  "ora": "5.4.1",
73
73
  "prettier": "^3.3.2",
@@ -9,6 +9,7 @@ const state = reactive({
9
9
  loadingText: null,
10
10
  disabled: false,
11
11
  link: null,
12
+ tooltip: 'Hover for more!',
12
13
  })
13
14
  const variants = ['solid', 'subtle', 'outline', 'ghost']
14
15
  const themes = ['gray', 'blue', 'green', 'red']
@@ -1,55 +1,57 @@
1
1
  <template>
2
- <button
3
- v-bind="$attrs"
4
- :class="buttonClasses"
5
- @click="handleClick"
6
- :disabled="isDisabled"
7
- :ariaLabel="ariaLabel"
8
- >
9
- <LoadingIndicator
10
- v-if="loading"
11
- :class="{
12
- 'h-3 w-3': size == 'sm',
13
- 'h-[13.5px] w-[13.5px]': size == 'md',
14
- 'h-[15px] w-[15px]': size == 'lg',
15
- 'h-4.5 w-4.5': size == 'xl' || size == '2xl',
16
- }"
17
- />
18
- <slot name="prefix" v-else-if="$slots['prefix'] || iconLeft">
19
- <FeatherIcon
20
- v-if="iconLeft && typeof iconLeft === 'string'"
21
- :name="iconLeft"
22
- :class="slotClasses"
23
- aria-hidden="true"
2
+ <Tooltip :text="tooltip" :disabled="!tooltip?.length">
3
+ <button
4
+ v-bind="$attrs"
5
+ :class="buttonClasses"
6
+ @click="handleClick"
7
+ :disabled="isDisabled"
8
+ :ariaLabel="ariaLabel"
9
+ >
10
+ <LoadingIndicator
11
+ v-if="loading"
12
+ :class="{
13
+ 'h-3 w-3': size == 'sm',
14
+ 'h-[13.5px] w-[13.5px]': size == 'md',
15
+ 'h-[15px] w-[15px]': size == 'lg',
16
+ 'h-4.5 w-4.5': size == 'xl' || size == '2xl',
17
+ }"
24
18
  />
25
- <component v-else-if="iconLeft" :is="iconLeft" :class="slotClasses" />
26
- </slot>
27
-
28
- <template v-if="loading && loadingText">{{ loadingText }}</template>
29
- <template v-else-if="isIconButton && !loading">
30
- <FeatherIcon
31
- v-if="icon && typeof icon === 'string'"
32
- :name="icon"
33
- :class="slotClasses"
34
- :aria-label="label"
35
- />
36
- <component v-else-if="icon" :is="icon" :class="slotClasses" />
37
- <slot name="icon" v-else-if="$slots.icon" />
38
- </template>
39
- <span v-else :class="{ 'sr-only': isIconButton }" class="truncate">
40
- <slot>{{ label }}</slot>
41
- </span>
42
-
43
- <slot name="suffix">
44
- <FeatherIcon
45
- v-if="iconRight && typeof iconRight === 'string'"
46
- :name="iconRight"
47
- :class="slotClasses"
48
- aria-hidden="true"
49
- />
50
- <component v-else-if="iconRight" :is="iconRight" :class="slotClasses" />
51
- </slot>
52
- </button>
19
+ <slot name="prefix" v-else-if="$slots['prefix'] || iconLeft">
20
+ <FeatherIcon
21
+ v-if="iconLeft && typeof iconLeft === 'string'"
22
+ :name="iconLeft"
23
+ :class="slotClasses"
24
+ aria-hidden="true"
25
+ />
26
+ <component v-else-if="iconLeft" :is="iconLeft" :class="slotClasses" />
27
+ </slot>
28
+
29
+ <template v-if="loading && loadingText">{{ loadingText }}</template>
30
+ <template v-else-if="isIconButton && !loading">
31
+ <FeatherIcon
32
+ v-if="icon && typeof icon === 'string'"
33
+ :name="icon"
34
+ :class="slotClasses"
35
+ :aria-label="label"
36
+ />
37
+ <component v-else-if="icon" :is="icon" :class="slotClasses" />
38
+ <slot name="icon" v-else-if="$slots.icon" />
39
+ </template>
40
+ <span v-else :class="{ 'sr-only': isIconButton }" class="truncate">
41
+ <slot>{{ label }}</slot>
42
+ </span>
43
+
44
+ <slot name="suffix">
45
+ <FeatherIcon
46
+ v-if="iconRight && typeof iconRight === 'string'"
47
+ :name="iconRight"
48
+ :class="slotClasses"
49
+ aria-hidden="true"
50
+ />
51
+ <component v-else-if="iconRight" :is="iconRight" :class="slotClasses" />
52
+ </slot>
53
+ </button>
54
+ </Tooltip>
53
55
  </template>
54
56
  <script lang="ts" setup>
55
57
  import { computed, useSlots } from 'vue'
@@ -57,6 +59,7 @@ import FeatherIcon from '../FeatherIcon.vue'
57
59
  import LoadingIndicator from '../LoadingIndicator.vue'
58
60
  import { useRouter } from 'vue-router'
59
61
  import type { ButtonProps, ThemeVariant } from './types'
62
+ import Tooltip from '../Tooltip/Tooltip.vue'
60
63
 
61
64
  const props = withDefaults(defineProps<ButtonProps>(), {
62
65
  theme: 'gray',
@@ -13,6 +13,7 @@ export interface ButtonProps {
13
13
  icon?: string | Component
14
14
  iconLeft?: string | Component
15
15
  iconRight?: string | Component
16
+ tooltip?: string
16
17
  loading?: boolean
17
18
  loadingText?: string
18
19
  disabled?: boolean
@@ -11,6 +11,7 @@ const actions = [
11
11
  {
12
12
  label: 'Delete',
13
13
  icon: 'trash-2',
14
+ theme: 'red',
14
15
  onClick: () => console.log('Delete clicked'),
15
16
  },
16
17
  ]
@@ -69,6 +70,7 @@ const groupedActions = [
69
70
  {
70
71
  label: 'Delete',
71
72
  icon: 'trash-2',
73
+ theme: 'red',
72
74
  onClick: () => console.log('Delete clicked'),
73
75
  },
74
76
  ],
@@ -93,6 +95,12 @@ const submenuActions = [
93
95
  icon: 'file-text',
94
96
  onClick: () => console.log('New Template clicked'),
95
97
  },
98
+ {
99
+ label: 'Delete',
100
+ icon: 'trash-2',
101
+ theme: 'red',
102
+ onClick: () => console.log('Delete clicked'),
103
+ },
96
104
  ],
97
105
  },
98
106
  {
@@ -21,122 +21,145 @@
21
21
  :align="contentAlign"
22
22
  :side-offset="4"
23
23
  >
24
- <div
25
- v-for="group in groups"
26
- :key="group.key"
27
- :class="cssClasses.groupContainer"
28
- >
29
- <DropdownMenuLabel
30
- v-if="group.group && !group.hideLabel"
31
- :class="cssClasses.groupLabel"
32
- >
33
- {{ group.group }}
34
- </DropdownMenuLabel>
24
+ <template v-for="group in groups" :key="group.key">
25
+ <div v-if="group.items.length" :class="cssClasses.groupContainer">
26
+ <DropdownMenuLabel
27
+ v-if="group.group && !group.hideLabel"
28
+ :class="[cssClasses.groupLabel, getTextColor(group)]"
29
+ >
30
+ {{ group.group }}
31
+ </DropdownMenuLabel>
35
32
 
36
- <DropdownMenuItem
37
- v-for="item in group.items"
38
- :key="item.label"
39
- as-child
40
- @select="item.onClick"
41
- >
42
- <component
43
- v-if="item.component"
44
- :is="item.component"
45
- :active="false"
46
- />
47
- <DropdownMenuSub v-else-if="item.submenu">
48
- <DropdownMenuSubTrigger as-child>
49
- <button :class="cssClasses.submenuTrigger">
50
- <FeatherIcon
51
- v-if="item.icon && typeof item.icon === 'string'"
52
- :name="item.icon"
53
- :class="cssClasses.itemIcon"
54
- aria-hidden="true"
55
- />
56
- <component
57
- :class="cssClasses.itemIcon"
58
- v-else-if="item.icon"
59
- :is="item.icon"
60
- />
61
- <span :class="cssClasses.itemLabel">
62
- {{ item.label }}
63
- </span>
64
- <FeatherIcon
65
- name="chevron-right"
66
- :class="cssClasses.chevronIcon"
67
- aria-hidden="true"
68
- />
69
- </button>
70
- </DropdownMenuSubTrigger>
71
- <DropdownMenuPortal>
72
- <DropdownMenuSubContent
73
- :class="cssClasses.dropdownContent"
74
- :side-offset="4"
75
- >
76
- <div
77
- v-for="submenuGroup in getSubmenuGroups(item.submenu)"
78
- :key="submenuGroup.key"
79
- :class="cssClasses.groupContainer"
33
+ <DropdownMenuItem
34
+ v-for="item in group.items"
35
+ :key="item.label"
36
+ as-child
37
+ @select="item.onClick"
38
+ >
39
+ <component
40
+ v-if="item.component"
41
+ :is="item.component"
42
+ :active="false"
43
+ />
44
+ <DropdownMenuSub v-else-if="item.submenu">
45
+ <DropdownMenuSubTrigger as-child>
46
+ <button
47
+ :class="[
48
+ cssClasses.submenuTrigger,
49
+ getSubmenuBackgroundColor(item),
50
+ ]"
51
+ >
52
+ <FeatherIcon
53
+ v-if="item.icon && typeof item.icon === 'string'"
54
+ :name="item.icon"
55
+ :class="[cssClasses.itemIcon, getIconColor(item)]"
56
+ aria-hidden="true"
57
+ />
58
+ <component
59
+ :class="[cssClasses.itemIcon, getIconColor(item)]"
60
+ v-else-if="item.icon"
61
+ :is="item.icon"
62
+ />
63
+ <span :class="[cssClasses.itemLabel, getTextColor(item)]">
64
+ {{ item.label }}
65
+ </span>
66
+ <FeatherIcon
67
+ name="chevron-right"
68
+ :class="[cssClasses.chevronIcon, getIconColor(item)]"
69
+ aria-hidden="true"
70
+ />
71
+ </button>
72
+ </DropdownMenuSubTrigger>
73
+ <DropdownMenuPortal>
74
+ <DropdownMenuSubContent
75
+ :class="cssClasses.dropdownContent"
76
+ :side-offset="4"
80
77
  >
81
- <DropdownMenuLabel
82
- v-if="submenuGroup.group && !submenuGroup.hideLabel"
83
- :class="cssClasses.groupLabel"
78
+ <div
79
+ v-for="submenuGroup in getSubmenuGroups(item.submenu)"
80
+ :key="submenuGroup.key"
81
+ :class="cssClasses.groupContainer"
84
82
  >
85
- {{ submenuGroup.group }}
86
- </DropdownMenuLabel>
83
+ <DropdownMenuLabel
84
+ v-if="submenuGroup.group && !submenuGroup.hideLabel"
85
+ :class="cssClasses.groupLabel"
86
+ >
87
+ {{ submenuGroup.group }}
88
+ </DropdownMenuLabel>
87
89
 
88
- <DropdownMenuItem
89
- v-for="subItem in submenuGroup.items"
90
- :key="subItem.label"
91
- as-child
92
- @select="() => handleItemClick(subItem)"
93
- >
94
- <component
95
- v-if="subItem.component"
96
- :is="subItem.component"
97
- :active="false"
98
- />
99
- <button v-else :class="cssClasses.itemButton">
100
- <FeatherIcon
101
- v-if="
102
- subItem.icon && typeof subItem.icon === 'string'
103
- "
104
- :name="subItem.icon"
105
- :class="cssClasses.itemIcon"
106
- aria-hidden="true"
107
- />
90
+ <DropdownMenuItem
91
+ v-for="subItem in submenuGroup.items"
92
+ :key="subItem.label"
93
+ as-child
94
+ @select="() => handleItemClick(subItem)"
95
+ >
108
96
  <component
109
- :class="cssClasses.itemIcon"
110
- v-else-if="subItem.icon"
111
- :is="subItem.icon"
97
+ v-if="subItem.component"
98
+ :is="subItem.component"
99
+ :active="false"
112
100
  />
113
- <span :class="cssClasses.itemLabel">
114
- {{ subItem.label }}
115
- </span>
116
- </button>
117
- </DropdownMenuItem>
118
- </div>
119
- </DropdownMenuSubContent>
120
- </DropdownMenuPortal>
121
- </DropdownMenuSub>
122
- <button v-else :class="cssClasses.itemButton">
123
- <FeatherIcon
124
- v-if="item.icon && typeof item.icon === 'string'"
125
- :name="item.icon"
126
- :class="cssClasses.itemIcon"
127
- aria-hidden="true"
128
- />
129
- <component
130
- :class="cssClasses.itemIcon"
131
- v-else-if="item.icon"
132
- :is="item.icon"
133
- />
134
- <span :class="cssClasses.itemLabel">
135
- {{ item.label }}
136
- </span>
137
- </button>
138
- </DropdownMenuItem>
139
- </div>
101
+ <button
102
+ v-else
103
+ :class="[
104
+ cssClasses.itemButton,
105
+ getBackgroundColor(subItem),
106
+ ]"
107
+ >
108
+ <FeatherIcon
109
+ v-if="
110
+ subItem.icon && typeof subItem.icon === 'string'
111
+ "
112
+ :name="subItem.icon"
113
+ :class="[
114
+ cssClasses.itemIcon,
115
+ getIconColor(subItem),
116
+ ]"
117
+ aria-hidden="true"
118
+ />
119
+ <component
120
+ :class="[
121
+ cssClasses.itemIcon,
122
+ getIconColor(subItem),
123
+ ]"
124
+ v-else-if="subItem.icon"
125
+ :is="subItem.icon"
126
+ />
127
+ <span
128
+ :class="[
129
+ cssClasses.itemLabel,
130
+ getTextColor(subItem),
131
+ ]"
132
+ >
133
+ {{ subItem.label }}
134
+ </span>
135
+ </button>
136
+ </DropdownMenuItem>
137
+ </div>
138
+ </DropdownMenuSubContent>
139
+ </DropdownMenuPortal>
140
+ </DropdownMenuSub>
141
+ <button
142
+ v-else
143
+ :class="[cssClasses.itemButton, getBackgroundColor(item)]"
144
+ >
145
+ <FeatherIcon
146
+ v-if="item.icon && typeof item.icon === 'string'"
147
+ :name="item.icon"
148
+ :class="[cssClasses.itemIcon, getIconColor(item)]"
149
+ aria-hidden="true"
150
+ />
151
+ <component
152
+ :class="[cssClasses.itemIcon, getIconColor(item)]"
153
+ v-else-if="item.icon"
154
+ :is="item.icon"
155
+ />
156
+ <span :class="[cssClasses.itemLabel, getTextColor(item)]"
157
+ >{{ item.label }}
158
+ </span>
159
+ </button>
160
+ </DropdownMenuItem>
161
+ </div>
162
+ </template>
140
163
  </DropdownMenuContent>
141
164
  </DropdownMenuPortal>
142
165
  </DropdownMenuRoot>
@@ -163,6 +186,7 @@ import type {
163
186
  DropdownOption,
164
187
  DropdownGroupOption,
165
188
  DropdownOptions,
189
+ DropdownItem,
166
190
  } from './types'
167
191
 
168
192
  defineOptions({
@@ -189,6 +213,7 @@ const handleItemClick = (item: DropdownOption) => {
189
213
  const normalizeDropdownItem = (option: DropdownOption) => {
190
214
  return {
191
215
  label: option.label,
216
+ theme: option.theme || 'gray',
192
217
  icon: option.icon,
193
218
  component: option.component,
194
219
  onClick: () => handleItemClick(option),
@@ -196,6 +221,19 @@ const normalizeDropdownItem = (option: DropdownOption) => {
196
221
  }
197
222
  }
198
223
 
224
+ const getIconColor = (item: DropdownItem) =>
225
+ item.theme === 'red' ? 'text-ink-red-3' : 'text-ink-gray-6'
226
+ const getTextColor = (item: DropdownItem) =>
227
+ item.theme === 'red' ? 'text-ink-red-3' : 'text-ink-gray-7'
228
+ const getBackgroundColor = (item: DropdownItem) =>
229
+ item.theme === 'red'
230
+ ? 'focus:bg-surface-red-3 data-[highlighted]:bg-surface-red-3 data-[state=open]:bg-surface-red-3'
231
+ : 'focus:bg-surface-gray-3 data-[highlighted]:bg-surface-gray-3 data-[state=open]:bg-surface-gray-3'
232
+ const getSubmenuBackgroundColor = (item: DropdownItem) =>
233
+ getBackgroundColor(item) +
234
+ ' data-[state=open]:bg-surface-' +
235
+ (item.theme === 'red' ? 'red-3' : 'gray-3')
236
+
199
237
  // Unified group processing for both main options and submenu options
200
238
  const processOptionsIntoGroups = (
201
239
  options: DropdownOptions,
@@ -260,18 +298,18 @@ const cssClasses = {
260
298
  groupContainer: 'p-1.5',
261
299
 
262
300
  // Label classes
263
- groupLabel: 'flex h-7 items-center px-2 text-sm font-medium text-ink-gray-6',
264
- itemLabel: 'whitespace-nowrap text-ink-gray-7',
301
+ groupLabel: 'flex h-7 items-center px-2 text-sm font-medium',
302
+ itemLabel: 'whitespace-nowrap',
265
303
 
266
304
  // Icon classes
267
- itemIcon: 'mr-2 h-4 w-4 flex-shrink-0 text-ink-gray-6',
268
- chevronIcon: 'ml-auto h-4 w-4 flex-shrink-0 text-ink-gray-6',
305
+ itemIcon: 'mr-2 h-4 w-4 flex-shrink-0',
306
+ chevronIcon: 'ml-auto h-4 w-4 flex-shrink-0',
269
307
 
270
308
  // Button classes
271
309
  itemButton:
272
- 'group flex h-7 w-full items-center rounded px-2 text-base text-ink-gray-6 focus:bg-surface-gray-3 focus:outline-none data-[highlighted]:bg-surface-gray-3',
310
+ 'group flex h-7 w-full items-center rounded px-2 text-base focus:outline-none',
273
311
  submenuTrigger:
274
- 'group flex h-7 w-full items-center rounded px-2 text-base text-ink-gray-6 focus:bg-surface-gray-3 focus:outline-none data-[highlighted]:bg-surface-gray-3 data-[state=open]:bg-surface-gray-3',
312
+ 'group flex h-7 w-full items-center rounded px-2 text-base text-ink-gray-6 focus:outline-none',
275
313
  }
276
314
 
277
315
  const groups = computed(() => {
@@ -4,6 +4,7 @@ import { ButtonProps } from '../Button'
4
4
  export type DropdownOption = {
5
5
  label: string
6
6
  icon?: string | null
7
+ theme?: 'gray' | 'red'
7
8
  component?: any
8
9
  onClick?: () => void
9
10
  route?: RouterLinkProps['to']
@@ -16,9 +17,11 @@ export type DropdownGroupOption = {
16
17
  group: string
17
18
  items: DropdownOption[]
18
19
  hideLabel?: boolean
20
+ theme?: 'gray' | 'red'
19
21
  }
22
+ export type DropdownItem = DropdownOption | DropdownGroupOption
20
23
 
21
- export type DropdownOptions = Array<DropdownOption | DropdownGroupOption>
24
+ export type DropdownOptions = Array<DropdownItem>
22
25
 
23
26
  export interface DropdownProps {
24
27
  button?: ButtonProps
@@ -75,6 +75,7 @@ const props = withDefaults(defineProps<TreeProps>(), {
75
75
  rowHeight: '25px',
76
76
  indentWidth: '20px',
77
77
  showIndentationGuides: true,
78
+ defaultCollapsed: true,
78
79
  }),
79
80
  })
80
81
 
@@ -96,7 +97,7 @@ const slots = defineSlots<{
96
97
  }
97
98
  }>()
98
99
 
99
- const isCollapsed = ref(true)
100
+ const isCollapsed = ref(props.options.defaultCollapsed ?? true)
100
101
 
101
102
  const linePadding = ref('')
102
103
 
@@ -9,6 +9,7 @@ export type TreeOptions = {
9
9
  rowHeight?: string
10
10
  indentWidth?: string
11
11
  showIndentationGuides?: boolean
12
+ defaultCollapsed?: boolean
12
13
  }
13
14
 
14
15
  export interface TreeProps {