frappe-ui 0.1.276 → 0.1.278

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 (120) hide show
  1. package/frappe/drive/components/MoveDialog.vue +1 -1
  2. package/frappe/drive/components/RenameDialog.vue +14 -6
  3. package/frappe/drive/components/TagInput/TagInput.vue +1 -1
  4. package/frappe/drive/js/resources.js +4 -2
  5. package/frappe/drive/js/utils.js +32 -1
  6. package/package.json +7 -5
  7. package/src/components/Autocomplete/Autocomplete.vue +1 -0
  8. package/src/components/Button/Button.vue +61 -21
  9. package/src/components/Calendar/Calendar.vue +38 -0
  10. package/src/components/Combobox/Combobox.cy.ts +592 -62
  11. package/src/components/Combobox/Combobox.vue +626 -358
  12. package/src/components/Combobox/ComboboxResults.vue +380 -0
  13. package/src/components/Combobox/stories/CustomValue.vue +22 -0
  14. package/src/components/Combobox/stories/EmojiPicker.vue +52 -0
  15. package/src/components/Combobox/stories/Grouped.vue +29 -18
  16. package/src/components/Combobox/stories/MemberPicker.vue +129 -0
  17. package/src/components/Combobox/stories/Simple.vue +22 -9
  18. package/src/components/Combobox/stories/StatusPicker.vue +98 -0
  19. package/src/components/Combobox/types.ts +221 -23
  20. package/src/components/Combobox/utils.ts +268 -0
  21. package/src/components/CommandPalette/CommandPaletteItem.vue +1 -1
  22. package/src/components/DatePicker/DatePicker.vue +26 -0
  23. package/src/components/DatePicker/types.ts +30 -0
  24. package/src/components/Divider/Divider.cy.ts +110 -0
  25. package/src/components/Divider/Divider.vue +64 -30
  26. package/src/components/Divider/stories/Action.vue +32 -0
  27. package/src/components/Divider/stories/Examples.vue +23 -0
  28. package/src/components/Divider/types.ts +4 -2
  29. package/src/components/Dropdown/Dropdown.cy.ts +75 -1
  30. package/src/components/Dropdown/Dropdown.vue +77 -394
  31. package/src/components/Dropdown/DropdownMenuItemContent.vue +226 -0
  32. package/src/components/Dropdown/DropdownMenuList.vue +207 -0
  33. package/src/components/Dropdown/DropdownRenderContent.vue +24 -0
  34. package/src/components/Dropdown/DropdownRenderContentAsChild.vue +19 -0
  35. package/src/components/Dropdown/index.ts +1 -1
  36. package/src/components/Dropdown/stories/01_Simple.vue +38 -0
  37. package/src/components/Dropdown/stories/02_Shortcuts.vue +42 -0
  38. package/src/components/Dropdown/stories/03_Submenus.vue +69 -0
  39. package/src/components/Dropdown/stories/04_Switches.vue +44 -0
  40. package/src/components/Dropdown/stories/05_KebabMenu.vue +78 -0
  41. package/src/components/Dropdown/stories/06_UserMenu.vue +145 -0
  42. package/src/components/Dropdown/types.ts +212 -26
  43. package/src/components/Dropdown/utils.ts +150 -0
  44. package/src/components/FormControl/FormControl.cy.ts +66 -0
  45. package/src/components/FormControl/FormControl.vue +5 -6
  46. package/src/components/ItemList/ItemList.cy.ts +110 -0
  47. package/src/components/ItemList/ItemList.vue +166 -0
  48. package/src/components/ItemList/ItemListRow.cy.ts +52 -0
  49. package/src/components/ItemList/ItemListRow.vue +80 -0
  50. package/src/components/ItemList/index.ts +3 -0
  51. package/src/components/ItemList/stories/AdvancedSlots.vue +73 -0
  52. package/src/components/ItemList/stories/Basic.vue +92 -0
  53. package/src/components/ItemList/stories/CustomSlots.vue +91 -0
  54. package/src/components/ItemList/stories/EmptyAndFooter.vue +79 -0
  55. package/src/components/ItemList/stories/RowStates.vue +38 -0
  56. package/src/components/ItemList/types.ts +121 -0
  57. package/src/components/ListView/ListRow.vue +32 -5
  58. package/src/components/ListView/ListView.vue +21 -15
  59. package/src/components/ListView/stories/Disabled.vue +57 -0
  60. package/src/components/MultiSelect/MultiSelect.cy.ts +101 -19
  61. package/src/components/MultiSelect/MultiSelect.vue +490 -115
  62. package/src/components/MultiSelect/MultiSelectResults.vue +288 -0
  63. package/src/components/MultiSelect/index.ts +16 -1
  64. package/src/components/MultiSelect/stories/Example.vue +13 -12
  65. package/src/components/MultiSelect/stories/Footer.vue +21 -22
  66. package/src/components/MultiSelect/stories/Grouped.vue +42 -0
  67. package/src/components/MultiSelect/stories/Options.vue +16 -17
  68. package/src/components/MultiSelect/stories/TagsTrigger.vue +79 -0
  69. package/src/components/MultiSelect/stories/TriggerSlot.vue +41 -0
  70. package/src/components/MultiSelect/types.ts +191 -6
  71. package/src/components/MultiSelect/utils.ts +191 -0
  72. package/src/components/Select/Select.cy.ts +235 -3
  73. package/src/components/Select/Select.vue +442 -71
  74. package/src/components/Select/index.ts +1 -1
  75. package/src/components/Select/stories/Example.vue +53 -3
  76. package/src/components/Select/stories/OptionSlot.vue +49 -34
  77. package/src/components/Select/stories/States.vue +47 -0
  78. package/src/components/Select/stories/TriggerSlots.vue +84 -31
  79. package/src/components/Select/types.ts +89 -10
  80. package/src/components/Slider/Slider.cy.ts +48 -0
  81. package/src/components/Slider/Slider.vue +26 -21
  82. package/src/components/Slider/types.ts +42 -0
  83. package/src/components/Spinner.vue +7 -0
  84. package/src/components/TabButtons/TabButtons.cy.ts +80 -0
  85. package/src/components/TabButtons/TabButtons.vue +204 -62
  86. package/src/components/Tabs/Tabs.cy.ts +37 -7
  87. package/src/components/Tabs/Tabs.vue +24 -4
  88. package/src/components/Tabs/types.ts +18 -10
  89. package/src/components/TextEditor/components/CodeBlockComponent.vue +183 -58
  90. package/src/components/TextEditor/components/MediaNodeView.vue +4 -4
  91. package/src/components/TextEditor/extensions/mention/mention-extension.ts +4 -0
  92. package/src/components/TextEditor/style.css +17 -0
  93. package/src/components/TextInput/TextInput.vue +3 -3
  94. package/src/components/TextInput/types.ts +14 -9
  95. package/src/components/Textarea/Textarea.cy.ts +72 -0
  96. package/src/components/Textarea/Textarea.vue +3 -3
  97. package/src/components/Textarea/types.ts +14 -9
  98. package/src/components/Toast/Toast.cy.ts +58 -0
  99. package/src/components/Toast/Toast.vue +1 -0
  100. package/src/components/Toast/stories/Actions.vue +65 -0
  101. package/src/components/Toast/stories/Examples.vue +42 -0
  102. package/src/components/Toast/types.ts +25 -5
  103. package/src/components/Tooltip/Tooltip.vue +15 -25
  104. package/src/components/Tooltip/TooltipBubble.vue +52 -0
  105. package/src/composables/usePopoverMotion.ts +37 -0
  106. package/src/index.ts +3 -0
  107. package/src/utils/iconString.ts +25 -0
  108. package/src/utils/vnode.ts +62 -0
  109. package/tailwind/lucideIconsPlugin.js +101 -0
  110. package/tailwind/plugin.js +227 -2
  111. package/tailwind/preset.js +2 -1
  112. package/vite/lucideIcons.js +12 -3
  113. package/src/components/Combobox/stories/CustomRender.vue +0 -26
  114. package/src/components/Combobox/stories/OptionSlots.vue +0 -42
  115. package/src/components/Combobox/stories/WithIcons.vue +0 -16
  116. package/src/components/Dropdown/stories/CustomTrigger.vue +0 -19
  117. package/src/components/Dropdown/stories/Examples.vue +0 -17
  118. package/src/components/Dropdown/stories/Grouped.vue +0 -36
  119. package/src/components/Dropdown/stories/Submenus.vue +0 -31
  120. package/src/components/Dropdown/stories/Switches.vue +0 -28
@@ -443,13 +443,13 @@ function closeEntity(name) {
443
443
  }
444
444
 
445
445
  const moveFile = async () => {
446
- open.value = false
447
446
  emit('success')
448
447
  await move.submit({
449
448
  entity_names: props.entities.map((obj) => obj.name),
450
449
  new_parent: selected.value,
451
450
  team: chosenTeam.value,
452
451
  })
452
+ open.value = false
453
453
  emit('complete')
454
454
  }
455
455
  </script>
@@ -8,7 +8,7 @@
8
8
  {
9
9
  label: 'Confirm',
10
10
  variant: 'solid',
11
- disabled: !newTitle || newTitle === entity.title,
11
+ disabled: !newTitle || newTitle === entity.title || rename.loading,
12
12
  onClick: submit,
13
13
  },
14
14
  ],
@@ -42,7 +42,7 @@ import { Dialog } from '../../../src'
42
42
  import { rename } from '../js/resources'
43
43
 
44
44
  const props = defineProps({ entity: Object, modelValue: String })
45
- const emit = defineEmits(['update:modelValue', 'success'])
45
+ const emit = defineEmits(['success', 'complete'])
46
46
  const dialogType = defineModel()
47
47
  const open = ref(true)
48
48
 
@@ -64,10 +64,18 @@ if (props.entity.is_group || props.entity.doc || props.entity.is_link) {
64
64
  const submit = () => {
65
65
  const formattedTitle =
66
66
  newTitle.value + (file_ext.value ? '.' + file_ext.value : '')
67
- rename.submit({
68
- entity_name: props.entity.name,
69
- new_title: formattedTitle,
70
- })
67
+ rename.submit(
68
+ {
69
+ entity_name: props.entity.name,
70
+ new_title: formattedTitle,
71
+ },
72
+ {
73
+ onSuccess: () => {
74
+ open.value = false
75
+ emit('complete')
76
+ },
77
+ },
78
+ )
71
79
  emit('success', {
72
80
  name: props.entity.name,
73
81
  title: formattedTitle,
@@ -120,7 +120,7 @@ function removeTag(tag: string) {
120
120
  [data-state='active'] {
121
121
  background: var(--surface-gray-1);
122
122
  }
123
- [aria-label='Show popup'] {
123
+ :deep([aria-label='Show popup']) {
124
124
  display: none;
125
125
  }
126
126
  </style>
@@ -1,5 +1,6 @@
1
1
  import { createResource, toast } from '../../../src'
2
- import { prettyData } from '../js/utils'
2
+ import { prettyData, openEntity } from '../js/utils'
3
+
3
4
  export const getTeams = createResource({
4
5
  url: 'drive.api.permissions.get_teams',
5
6
  params: {
@@ -39,9 +40,10 @@ export const updateMoved = (team, new_parent, special) => {
39
40
  export const move = createResource({
40
41
  url: 'drive.api.files.move',
41
42
  onSuccess(data) {
43
+ console.log(data)
42
44
  toast.success('Moved to ' + data.title, {
43
45
  action: {
44
- label: 'Go',
46
+ label: 'Go to folder',
45
47
  onClick: () => {
46
48
  if (!data.special)
47
49
  openEntity({
@@ -26,7 +26,7 @@ export function getFileLink(entity, copy = true) {
26
26
  }
27
27
  if (!copy) return link
28
28
  try {
29
- copyToClipboard(link).then(() => toast.success('Copied to your clipboard!'))
29
+ copyToClipboard(link).then(() => toast.success('Copied to your clipboard.'))
30
30
  } catch (err) {
31
31
  console.error('Failed to copy link:', err)
32
32
  }
@@ -108,3 +108,34 @@ export const formatDate = (date) => {
108
108
 
109
109
  return `${formattedDate}, ${formattedTime}`
110
110
  }
111
+
112
+
113
+ export const openEntity = (entity, new_tab = false) => {
114
+ if (new_tab) {
115
+ return window.open(getFileLink(entity, false), '_blank')
116
+ }
117
+
118
+ if (entity.name === '') {
119
+ if (entity.is_private) window.location.href = '/drive/'
120
+ else window.location.href = '/drive/t/' + entity.team
121
+ } else if (entity.is_group) {
122
+ window.location.href = '/drive/d/' + entity.name
123
+ } else if (entity.is_link) {
124
+ const origin = new URL(entity.path).origin
125
+ if (
126
+ confirm(
127
+ `This will open an external link to ${origin} - are you sure you want to open?`,
128
+ )
129
+ )
130
+ window.open(entity.path, '_blank')
131
+ } else if (entity.mime_type === 'frappe/slides') {
132
+ window.location.href = '/slides/presentation/' + entity.path
133
+ } else if (
134
+ entity.mime_type === 'frappe_doc' ||
135
+ entity.mime_type === 'text/markdown'
136
+ ) {
137
+ window.location.href = '/writer/w/' + entity.name
138
+ } else {
139
+ window.location.href = '/drive/f/' + entity.name
140
+ }
141
+ }
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "frappe-ui",
3
- "version": "0.1.276",
3
+ "version": "0.1.278",
4
4
  "description": "A set of components and utilities for rapid UI development",
5
5
  "type": "module",
6
6
  "sideEffects": false,
7
7
  "scripts": {
8
8
  "test": "vitest --run",
9
9
  "type-check": "tsc --noEmit",
10
- "prettier": "yarn prettier -w ./src",
10
+ "prettier": "yarn run prettier -w ./src",
11
11
  "bump-and-release": "yarn test && git pull --rebase origin main && yarn run release-patch",
12
12
  "release-patch": "yarn version --patch && git push && git push --tags",
13
13
  "dev": "vite",
@@ -16,7 +16,8 @@
16
16
  "docs:dev": "vitepress dev docs --host",
17
17
  "docs:build": "vitepress build docs",
18
18
  "docs:preview": "vitepress preview docs",
19
- "docs:gen": "node docs/scripts/propsgen.ts --trace-uncaught"
19
+ "docs:gen": "tsx docs/scripts/propsgen.ts",
20
+ "docs:gen:all": "yarn docs:gen --all"
20
21
  },
21
22
  "exports": {
22
23
  ".": {
@@ -49,8 +50,8 @@
49
50
  "import": "./src/components/TextEditor/style.css"
50
51
  },
51
52
  "./tsconfig.base.json": {
52
- "default": "./tsconfig.base.json",
53
- "types": "./tsconfig.base.json"
53
+ "types": "./tsconfig.base.json",
54
+ "default": "./tsconfig.base.json"
54
55
  }
55
56
  },
56
57
  "files": [
@@ -140,6 +141,7 @@
140
141
  "postcss": "^8.4.21",
141
142
  "prettier-plugin-tailwindcss": "^0.1.13",
142
143
  "tailwindcss": "^3.2.7",
144
+ "tsx": "^4.20.6",
143
145
  "vite": "^5.1.8",
144
146
  "vitepress": "^2.0.0-alpha.15",
145
147
  "vitest": "^2.1.8",
@@ -321,6 +321,7 @@ const findOption = (option: AutocompleteOption) => {
321
321
  }
322
322
 
323
323
  const makeOption = (option: AutocompleteOption) => {
324
+ if (option == null) return { label: '', value: '' }
324
325
  return isOption(option) ? option : { label: option, value: option }
325
326
  }
326
327
 
@@ -1,14 +1,25 @@
1
1
  <template>
2
- <Tooltip :text="tooltip" :disabled="!tooltip?.length">
3
- <button
4
- v-bind="$attrs"
5
- :class="buttonClasses"
6
- @click="handleClick"
7
- :disabled="isDisabled"
8
- :ariaLabel="label"
9
- :type = "props.type"
10
- ref="rootRef"
11
- >
2
+ <!--
3
+ The <button> is the effective DOM root. reka's Tooltip primitives
4
+ (TooltipProvider/TooltipRoot/TooltipTrigger) are renderless / pass-
5
+ through, so consumers wrapping <Button> in their own reka primitives
6
+ (e.g. ComboboxTrigger as-child) see a native <button> element with
7
+ all forwarded attrs — tabindex, aria-expanded, role — intact. Using
8
+ the separate <Tooltip> component as a wrapper dropped those attrs
9
+ because it's a Vue component with `inheritAttrs: false`.
10
+ -->
11
+ <TooltipProvider>
12
+ <TooltipRoot>
13
+ <TooltipTrigger as-child>
14
+ <button
15
+ v-bind="$attrs"
16
+ :class="buttonClasses"
17
+ @click="handleClick"
18
+ :disabled="isDisabled"
19
+ :aria-label="label"
20
+ :type="props.type"
21
+ ref="rootRef"
22
+ >
12
23
  <LoadingIndicator
13
24
  v-if="loading"
14
25
  :class="{
@@ -19,8 +30,13 @@
19
30
  }"
20
31
  />
21
32
  <slot name="prefix" v-else-if="$slots['prefix'] || iconLeft">
33
+ <span
34
+ v-if="iconLeft && typeof iconLeft === 'string' && iconLeft.startsWith('lucide-')"
35
+ :class="[iconLeft, lucideSlotClasses]"
36
+ aria-hidden="true"
37
+ />
22
38
  <FeatherIcon
23
- v-if="iconLeft && typeof iconLeft === 'string'"
39
+ v-else-if="iconLeft && typeof iconLeft === 'string'"
24
40
  :name="iconLeft"
25
41
  :class="slotClasses"
26
42
  aria-hidden="true"
@@ -30,8 +46,13 @@
30
46
 
31
47
  <template v-if="loading && loadingText">{{ loadingText }}</template>
32
48
  <template v-else-if="isIconButton && !loading">
49
+ <span
50
+ v-if="icon && typeof icon === 'string' && icon.startsWith('lucide-')"
51
+ :class="[icon, lucideSlotClasses]"
52
+ aria-hidden="true"
53
+ />
33
54
  <FeatherIcon
34
- v-if="icon && typeof icon === 'string'"
55
+ v-else-if="icon && typeof icon === 'string'"
35
56
  :name="icon"
36
57
  :class="slotClasses"
37
58
  />
@@ -46,28 +67,37 @@
46
67
  </span>
47
68
 
48
69
  <slot name="suffix">
70
+ <span
71
+ v-if="iconRight && typeof iconRight === 'string' && iconRight.startsWith('lucide-')"
72
+ :class="[iconRight, lucideSlotClasses]"
73
+ aria-hidden="true"
74
+ />
49
75
  <FeatherIcon
50
- v-if="iconRight && typeof iconRight === 'string'"
76
+ v-else-if="iconRight && typeof iconRight === 'string'"
51
77
  :name="iconRight"
52
78
  :class="slotClasses"
53
79
  aria-hidden="true"
54
80
  />
55
- <component
56
- v-else-if="iconRight"
57
- :is="iconRight"
58
- :class="slotClasses"
59
- />
81
+ <component
82
+ v-else-if="iconRight"
83
+ :is="iconRight"
84
+ :class="slotClasses"
85
+ />
60
86
  </slot>
61
- </button>
62
- </Tooltip>
87
+ </button>
88
+ </TooltipTrigger>
89
+ <TooltipBubble v-if="tooltip?.length" :text="tooltip" />
90
+ </TooltipRoot>
91
+ </TooltipProvider>
63
92
  </template>
64
93
  <script lang="ts" setup>
65
94
  import { computed, useSlots, ref } from 'vue'
95
+ import { TooltipProvider, TooltipRoot, TooltipTrigger } from 'reka-ui'
66
96
  import FeatherIcon from '../FeatherIcon.vue'
67
97
  import LoadingIndicator from '../LoadingIndicator.vue'
98
+ import TooltipBubble from '../Tooltip/TooltipBubble.vue'
68
99
  import { useRouter } from 'vue-router'
69
100
  import type { ButtonProps, ThemeVariant } from './types'
70
- import Tooltip from '../Tooltip/Tooltip.vue'
71
101
 
72
102
  defineOptions({ inheritAttrs: false })
73
103
 
@@ -197,6 +227,16 @@ const slotClasses = computed(() => {
197
227
  return classes
198
228
  })
199
229
 
230
+ const lucideSlotClasses = computed(() => {
231
+ return {
232
+ sm: 'size-4',
233
+ md: 'size-4.5',
234
+ lg: 'size-5',
235
+ xl: 'size-6',
236
+ '2xl': 'size-6',
237
+ }[props.size]
238
+ })
239
+
200
240
  const isDisabled = computed(() => {
201
241
  return props.disabled || props.loading
202
242
  })
@@ -182,6 +182,9 @@ function updateActiveView(value, d, isPreviousMonth, isNextMonth) {
182
182
  isPreviousMonth && decrementMonth()
183
183
  isNextMonth && incrementMonth()
184
184
  }
185
+ if (value === 'Week') {
186
+ week.value = findCurrentWeek(currentMonthDates.value[date.value])
187
+ }
185
188
  }
186
189
 
187
190
  const selectedMonthDate = ref(dayjs().format('YYYY-MM-DD'))
@@ -251,6 +254,12 @@ function handleShortcuts(e) {
251
254
  provide('activeView', activeView)
252
255
  provide('config', overrideConfig)
253
256
 
257
+ watch(activeView, (value) => {
258
+ if (value === 'Week') {
259
+ week.value = findCurrentWeek(currentMonthDates.value[date.value])
260
+ }
261
+ })
262
+
254
263
  const parseEvents = computed(() => {
255
264
  return (
256
265
  props.events?.map((event) => {
@@ -401,6 +410,34 @@ let date = ref(
401
410
  )
402
411
  let selectedDay = computed(() => currentMonthDates.value[date.value])
403
412
 
413
+ function computeCurrentDay() {
414
+ if (activeView.value === 'Week') {
415
+ const weekDates = datesInWeeks.value[week.value] || []
416
+ return weekDates[0] ? weekDates[0].getDate() : null
417
+ }
418
+ if (activeView.value === 'Day') {
419
+ const day = selectedDay.value
420
+ return day ? new Date(day).getDate() : null
421
+ }
422
+ return 1
423
+ }
424
+
425
+ let currentDay = ref(computeCurrentDay())
426
+ let _lastInternalDay = currentDay.value
427
+
428
+ watch([activeView, week, date], () => {
429
+ const val = computeCurrentDay()
430
+ _lastInternalDay = val
431
+ currentDay.value = val
432
+ })
433
+
434
+ watch(currentDay, (newVal) => {
435
+ if (newVal == null) return
436
+ if (newVal === _lastInternalDay) return
437
+ const target = new Date(currentYear.value, currentMonth.value, newVal)
438
+ setCalendarDate(target)
439
+ })
440
+
404
441
  function updateCurrentDate(d) {
405
442
  activeView.value = 'Day'
406
443
  date.value = findIndexOfDate(d)
@@ -685,6 +722,7 @@ defineExpose({
685
722
  currentMonthYear,
686
723
  currentYear,
687
724
  currentMonth,
725
+ currentDay,
688
726
  enabledModes,
689
727
  activeView,
690
728
  decrement,