frappe-ui 1.0.0-rc.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (184) hide show
  1. package/experimental/Calendar/Calendar.api.md +71 -0
  2. package/experimental/Calendar/Calendar.vue +19 -0
  3. package/experimental/ListView/ListView.api.md +26 -0
  4. package/experimental/ListView/ListView.vue +7 -0
  5. package/experimental/MultiEmailInput/MultiEmailInput.vue +2 -2
  6. package/experimental/TextEditor/components/MediaNodeView.vue +2 -2
  7. package/package.json +2 -3
  8. package/readme.md +116 -56
  9. package/src/charts/FunnelChart.vue +6 -5
  10. package/src/charts/axisChartCommon.ts +38 -10
  11. package/src/charts/axisChartOptions.ts +16 -11
  12. package/src/charts/axisFormat.ts +21 -1
  13. package/src/charts/core/useAxisChart.ts +3 -6
  14. package/src/charts/docs/AreaChart.api.md +12 -1
  15. package/src/charts/docs/AreaChart.md +20 -22
  16. package/src/charts/docs/BarChart.api.md +12 -1
  17. package/src/charts/docs/BarChart.md +83 -86
  18. package/src/charts/docs/ChartCard.md +14 -16
  19. package/src/charts/docs/ChartContainer.md +38 -39
  20. package/src/charts/docs/ChartLegend.md +19 -19
  21. package/src/charts/docs/ChartTooltip.md +25 -26
  22. package/src/charts/docs/Dashboard.md +3 -3
  23. package/src/charts/docs/DonutChart.api.md +11 -0
  24. package/src/charts/docs/DonutChart.md +19 -20
  25. package/src/charts/docs/FunnelChart.md +8 -8
  26. package/src/charts/docs/HeatmapChart.api.md +11 -0
  27. package/src/charts/docs/HeatmapChart.md +12 -11
  28. package/src/charts/docs/LineChart.api.md +12 -1
  29. package/src/charts/docs/LineChart.md +53 -55
  30. package/src/charts/docs/NumberCard.md +19 -18
  31. package/src/charts/docs/Overview.md +104 -100
  32. package/src/charts/docs/SankeyChart.api.md +11 -0
  33. package/src/charts/docs/SankeyChart.md +14 -14
  34. package/src/charts/docs/ScatterChart.api.md +11 -0
  35. package/src/charts/docs/ScatterChart.md +44 -49
  36. package/src/charts/docs/States.md +21 -24
  37. package/src/charts/index.ts +2 -2
  38. package/src/charts/types.ts +22 -2
  39. package/src/components/Alert/Alert.md +52 -35
  40. package/src/components/Avatar/Avatar.md +55 -9
  41. package/src/components/Badge/Badge.md +34 -8
  42. package/src/components/BottomSheet/BottomSheet.md +48 -37
  43. package/src/components/Breadcrumbs/Breadcrumbs.md +39 -11
  44. package/src/components/Button/Button.md +67 -31
  45. package/src/components/Checkbox/Checkbox.api.md +11 -0
  46. package/src/components/Checkbox/Checkbox.md +48 -42
  47. package/src/components/Checkbox/Checkbox.vue +1 -0
  48. package/src/components/Combobox/Combobox.api.md +16 -0
  49. package/src/components/Combobox/Combobox.md +131 -85
  50. package/src/components/Combobox/Combobox.vue +15 -1
  51. package/src/components/Combobox/ComboboxResults.vue +2 -2
  52. package/src/components/ContextMenu/ContextMenu.md +56 -15
  53. package/src/components/DatePicker/DatePicker.api.md +61 -0
  54. package/src/components/DatePicker/DatePicker.md +85 -27
  55. package/src/components/DatePicker/DatePicker.vue +4 -0
  56. package/src/components/DatePicker/DateRangePicker.vue +4 -0
  57. package/src/components/DatePicker/DateTimePicker.vue +4 -0
  58. package/src/components/DesktopShell/DesktopShell.md +43 -37
  59. package/src/components/Dialog/Dialog.md +61 -85
  60. package/src/components/Divider/Divider.md +37 -9
  61. package/src/components/Dropdown/Dropdown.md +78 -23
  62. package/src/components/Duration/Duration.api.md +11 -0
  63. package/src/components/Duration/Duration.md +50 -59
  64. package/src/components/Duration/Duration.vue +1 -0
  65. package/src/components/ErrorMessage/ErrorMessage.md +32 -12
  66. package/src/components/FileUploader/FileUploader.md +75 -16
  67. package/src/components/FormControl/FormControl.api.md +11 -0
  68. package/src/components/FormControl/FormControl.md +44 -26
  69. package/src/components/FormControl/FormControl.vue +1 -0
  70. package/src/components/FormLabel/FormLabel.md +28 -8
  71. package/src/components/FrappeUIProvider/FrappeUIProvider.md +25 -15
  72. package/src/components/HoverCard/HoverCard.api.md +16 -0
  73. package/src/components/HoverCard/HoverCard.md +37 -32
  74. package/src/components/Icon/Icon.md +46 -16
  75. package/src/components/ItemListRow/ItemListRow.md +40 -7
  76. package/src/components/KeyboardShortcut/KeyboardShortcut.api.md +3 -3
  77. package/src/components/KeyboardShortcut/KeyboardShortcut.md +59 -37
  78. package/src/components/KeyboardShortcut/KeyboardShortcut.vue +19 -4
  79. package/src/components/KeyboardShortcut/types.ts +2 -2
  80. package/src/components/KeyboardShortcutsDialog/KeyboardShortcutsDialog.md +48 -45
  81. package/src/components/LoadingIndicator/index.ts +5 -2
  82. package/src/components/LoadingText/LoadingText.md +23 -4
  83. package/src/components/LoadingText/LoadingText.vue +2 -2
  84. package/src/components/MobileShell/MobileShell.api.md +73 -2
  85. package/src/components/MobileShell/MobileShell.md +107 -18
  86. package/src/components/MultiSelect/MultiSelect.api.md +16 -0
  87. package/src/components/MultiSelect/MultiSelect.md +106 -66
  88. package/src/components/MultiSelect/MultiSelect.vue +7 -3
  89. package/src/components/MultiSelect/MultiSelectResults.vue +2 -2
  90. package/src/components/PageHeader/PageHeader.md +79 -19
  91. package/src/components/PageHeader/PageHeaderTarget.vue +4 -1
  92. package/src/components/Password/Password.api.md +16 -0
  93. package/src/components/Password/Password.md +64 -14
  94. package/src/components/Password/Password.vue +27 -14
  95. package/src/components/Popover/Popover.api.md +21 -0
  96. package/src/components/Popover/Popover.md +61 -90
  97. package/src/components/Popover/Popover.vue +3 -0
  98. package/src/components/Progress/Progress.md +39 -19
  99. package/src/components/Radio/Radio.api.md +11 -0
  100. package/src/components/Radio/Radio.md +72 -35
  101. package/src/components/Radio/RadioGroup.vue +4 -0
  102. package/src/components/Rating/Rating.api.md +11 -0
  103. package/src/components/Rating/Rating.md +94 -56
  104. package/src/components/Rating/Rating.vue +8 -5
  105. package/src/components/ScrollArea/ScrollArea.api.md +11 -0
  106. package/src/components/ScrollArea/ScrollArea.md +29 -28
  107. package/src/components/Select/Select.api.md +16 -0
  108. package/src/components/Select/Select.md +80 -57
  109. package/src/components/Select/Select.vue +6 -1
  110. package/src/components/SettingsDialog/SettingsDialog.api.md +11 -0
  111. package/src/components/SettingsDialog/SettingsDialog.md +97 -26
  112. package/src/components/Sidebar/Sidebar.md +97 -87
  113. package/src/components/SidebarRail/SidebarRail.md +74 -31
  114. package/src/components/Skeleton/Skeleton.md +27 -10
  115. package/src/components/Slider/Slider.api.md +11 -0
  116. package/src/components/Slider/Slider.md +88 -60
  117. package/src/components/Slider/Slider.vue +1 -0
  118. package/src/components/Spinner/Spinner.md +36 -8
  119. package/src/components/Switch/Switch.api.md +11 -0
  120. package/src/components/Switch/Switch.md +42 -34
  121. package/src/components/Switch/Switch.vue +1 -0
  122. package/src/components/TabButtons/TabButtons.api.md +15 -4
  123. package/src/components/TabButtons/TabButtons.md +59 -104
  124. package/src/components/TabButtons/TabButtons.vue +10 -4
  125. package/src/components/TabButtons/types.ts +6 -3
  126. package/src/components/Tabs/TabList.vue +4 -4
  127. package/src/components/Tabs/TabTrigger.vue +2 -2
  128. package/src/components/Tabs/Tabs.api.md +6 -6
  129. package/src/components/Tabs/Tabs.md +58 -94
  130. package/src/components/Tabs/Tabs.vue +1 -1
  131. package/src/components/Tabs/context.ts +2 -2
  132. package/src/components/Tabs/index.ts +1 -1
  133. package/src/components/Tabs/types.ts +7 -4
  134. package/src/components/TextInput/TextInput.api.md +16 -0
  135. package/src/components/TextInput/TextInput.md +68 -31
  136. package/src/components/TextInput/TextInput.vue +1 -0
  137. package/src/components/Textarea/Textarea.api.md +16 -0
  138. package/src/components/Textarea/Textarea.md +53 -15
  139. package/src/components/Textarea/Textarea.vue +1 -0
  140. package/src/components/TimePicker/TimePicker.api.md +21 -0
  141. package/src/components/TimePicker/TimePicker.md +57 -32
  142. package/src/components/TimePicker/TimePicker.vue +1 -0
  143. package/src/components/Toast/Toast.md +101 -30
  144. package/src/components/Tooltip/Tooltip.md +59 -81
  145. package/src/components/Tree/Tree.api.md +31 -0
  146. package/src/components/Tree/Tree.md +77 -112
  147. package/src/components/Tree/types.ts +5 -1
  148. package/src/components/shared/tabs/pillTypes.ts +1 -1
  149. package/src/components/shared/tabs/styles.ts +12 -12
  150. package/src/data-fetching/docStore.ts +16 -9
  151. package/src/data-fetching/useCall/useCall.ts +26 -14
  152. package/src/data-fetching/useIsolatedCall.ts +13 -11
  153. package/src/data-fetching/useList/useList.ts +103 -49
  154. package/src/data-fetching/utils.ts +42 -2
  155. package/src/molecules/code-editor/style.css +4 -4
  156. package/src/molecules/list/list.md +244 -182
  157. package/tailwind/preset.js +1 -1
  158. package/tailwind/tokens/build.js +1 -1
  159. package/vite/index.d.ts +3 -2
  160. package/vitepress/components/Brand.vue +26 -0
  161. package/vitepress/components/Docs/CommandPalette.vue +111 -51
  162. package/vitepress/components/Docs/Demo.vue +5 -0
  163. package/vitepress/components/Docs/ExposedTable.vue +100 -0
  164. package/vitepress/components/Docs/Header.vue +75 -0
  165. package/vitepress/components/Docs/MobileNavSheet.vue +7 -0
  166. package/vitepress/components/Docs/OnThisPage.vue +51 -21
  167. package/vitepress/components/Docs/PlaygroundFrame.vue +72 -40
  168. package/vitepress/components/Docs/PrevNextBtns.vue +0 -1
  169. package/vitepress/components/Docs/Search.vue +24 -5
  170. package/vitepress/components/Docs/Sidebar.vue +47 -47
  171. package/vitepress/components/Docs/index.ts +2 -0
  172. package/vitepress/components/Docs/pageMarkdown.ts +99 -0
  173. package/vitepress/components/Layout.vue +31 -22
  174. package/vitepress/components/Navbar.vue +9 -83
  175. package/vitepress/components/SiteActions.vue +68 -0
  176. package/vitepress/components/index.ts +2 -0
  177. package/vitepress/css/style.css +29 -1
  178. package/vitepress/plugins/colocatedComponentDocs.ts +3 -2
  179. package/src/components/LoadingIndicator/LoadingIndicator.api.md +0 -20
  180. package/src/components/LoadingIndicator/LoadingIndicator.md +0 -11
  181. package/src/components/LoadingIndicator/LoadingIndicator.vue +0 -19
  182. package/src/components/LoadingIndicator/types.ts +0 -4
  183. package/src/components/MobileNav/MobileNav.api.md +0 -77
  184. package/src/components/MobileNav/MobileNav.md +0 -35
@@ -3,6 +3,7 @@
3
3
  import PropsTable from '@/components/Docs/PropsTable.vue'
4
4
  import SlotsTable from '@/components/Docs/SlotsTable.vue'
5
5
  import EmitsTable from '@/components/Docs/EmitsTable.vue'
6
+ import ExposedTable from '@/components/Docs/ExposedTable.vue'
6
7
 
7
8
  const propsData = [
8
9
  {
@@ -95,6 +96,74 @@
95
96
  type: '[payload: { view: CalendarMode; startDate: string; endDate: string; }]'
96
97
  }
97
98
  ]
99
+
100
+ const exposedData = [
101
+ {
102
+ name: 'reloadEvents',
103
+ description: 'Rebuilds the calendar\'s own copy of `events`. The calendar already does\nthis whenever `events` changes.',
104
+ type: '() => void'
105
+ },
106
+ {
107
+ name: 'currentMonthYear',
108
+ description: 'The title of the visible range, e.g. "August 2026".',
109
+ type: 'string'
110
+ },
111
+ {
112
+ name: 'currentYear',
113
+ description: 'The year of the visible month.',
114
+ type: 'number'
115
+ },
116
+ {
117
+ name: 'currentMonth',
118
+ description: 'The visible month, `0` for January.',
119
+ type: 'number'
120
+ },
121
+ {
122
+ name: 'currentDay',
123
+ description: 'The day of the month the view is anchored on.',
124
+ type: 'number | null'
125
+ },
126
+ {
127
+ name: 'enabledModes',
128
+ description: 'The views that `config` does not disable. Read once, when the calendar\nmounts.',
129
+ type: 'CalendarActionOption[]'
130
+ },
131
+ {
132
+ name: 'activeView',
133
+ description: 'The visible view.',
134
+ type: 'CalendarMode'
135
+ },
136
+ {
137
+ name: 'decrement',
138
+ description: 'Moves back one day, week or month, depending on the view.',
139
+ type: '() => void'
140
+ },
141
+ {
142
+ name: 'increment',
143
+ description: 'Moves forward one day, week or month, depending on the view.',
144
+ type: '() => void'
145
+ },
146
+ {
147
+ name: 'updateActiveView',
148
+ description: 'Switches to this view.',
149
+ type: '(value: CalendarMode, d?: Date, isPreviousMonth?: boolean, isNextMonth?: boolean) => void'
150
+ },
151
+ {
152
+ name: 'setCalendarDate',
153
+ description: 'Jumps to this date, or to today when no date is given.',
154
+ type: '(d?: string | Date) => void'
155
+ },
156
+ {
157
+ name: 'onMonthYearChange',
158
+ description: 'Jumps to this date and moves the month picker to it.',
159
+ type: '(val?: string | Date) => void'
160
+ },
161
+ {
162
+ name: 'selectedMonthDate',
163
+ description: 'The month picker\'s date, as `YYYY-MM-DD`.',
164
+ type: 'string'
165
+ }
166
+ ]
98
167
  </script>
99
168
 
100
169
  ## API Reference
@@ -104,3 +173,5 @@
104
173
  <SlotsTable :data="slotsData"/>
105
174
 
106
175
  <EmitsTable :data="emitsData"/>
176
+
177
+ <ExposedTable :data="exposedData"/>
@@ -830,18 +830,37 @@ watch(
830
830
  )
831
831
 
832
832
  defineExpose({
833
+ /**
834
+ * Rebuilds the calendar's own copy of `events`. The calendar already does
835
+ * this whenever `events` changes.
836
+ */
833
837
  reloadEvents,
838
+ /** The title of the visible range, e.g. "August 2026". */
834
839
  currentMonthYear,
840
+ /** The year of the visible month. */
835
841
  currentYear,
842
+ /** The visible month, `0` for January. */
836
843
  currentMonth,
844
+ /** The day of the month the view is anchored on. */
837
845
  currentDay,
846
+ /**
847
+ * The views that `config` does not disable. Read once, when the calendar
848
+ * mounts.
849
+ */
838
850
  enabledModes,
851
+ /** The visible view. */
839
852
  activeView,
853
+ /** Moves back one day, week or month, depending on the view. */
840
854
  decrement,
855
+ /** Moves forward one day, week or month, depending on the view. */
841
856
  increment,
857
+ /** Switches to this view. */
842
858
  updateActiveView,
859
+ /** Jumps to this date, or to today when no date is given. */
843
860
  setCalendarDate,
861
+ /** Jumps to this date and moves the month picker to it. */
844
862
  onMonthYearChange,
863
+ /** The month picker's date, as `YYYY-MM-DD`. */
845
864
  selectedMonthDate,
846
865
  })
847
866
  </script>
@@ -3,6 +3,7 @@
3
3
  import PropsTable from '@/components/Docs/PropsTable.vue'
4
4
  import SlotsTable from '@/components/Docs/SlotsTable.vue'
5
5
  import EmitsTable from '@/components/Docs/EmitsTable.vue'
6
+ import ExposedTable from '@/components/Docs/ExposedTable.vue'
6
7
 
7
8
  const listViewProps = [
8
9
  {
@@ -55,6 +56,29 @@
55
56
  }
56
57
  ]
57
58
 
59
+ const listViewExposed = [
60
+ {
61
+ name: 'selections',
62
+ description: 'The keys (`rowKey` values) of the selected rows.',
63
+ type: 'Reactive<Set<unknown>>'
64
+ },
65
+ {
66
+ name: 'allRowsSelected',
67
+ description: 'Whether every enabled row is selected.',
68
+ type: 'boolean'
69
+ },
70
+ {
71
+ name: 'toggleRow',
72
+ description: 'Selects the row with this key, or deselects it if it is selected.',
73
+ type: '(row: any) => void'
74
+ },
75
+ {
76
+ name: 'toggleAllRows',
77
+ description: 'Selects every enabled row. Clears the selection instead when `select` is\nfalse or every row is already selected.',
78
+ type: '(select: any) => void'
79
+ }
80
+ ]
81
+
58
82
  const listEmptyStateSlots = [
59
83
  {
60
84
  name: 'default',
@@ -319,6 +343,8 @@
319
343
 
320
344
  <EmitsTable :data="listViewEmits"/>
321
345
 
346
+ <ExposedTable :data="listViewExposed"/>
347
+
322
348
  ### ListEmptyState
323
349
 
324
350
  <SlotsTable :data="listEmptyStateSlots"/>
@@ -170,9 +170,16 @@ provide(
170
170
  )
171
171
 
172
172
  defineExpose({
173
+ /** The keys (`rowKey` values) of the selected rows. */
173
174
  selections,
175
+ /** Whether every enabled row is selected. */
174
176
  allRowsSelected,
177
+ /** Selects the row with this key, or deselects it if it is selected. */
175
178
  toggleRow,
179
+ /**
180
+ * Selects every enabled row. Clears the selection instead when `select` is
181
+ * false or every row is already selected.
182
+ */
176
183
  toggleAllRows,
177
184
  })
178
185
  </script>
@@ -15,7 +15,7 @@ import {
15
15
  } from 'reka-ui'
16
16
  import Avatar from '../../src/components/Avatar/Avatar.vue'
17
17
  import ItemListRow from '../../src/components/ItemListRow/ItemListRow.vue'
18
- import { LoadingIndicator } from '../../src/components/LoadingIndicator'
18
+ import { Spinner } from '../../src/components/Spinner'
19
19
  import {
20
20
  InputDescription,
21
21
  InputError,
@@ -377,7 +377,7 @@ defineSlots<MultiEmailInputSlots>()
377
377
  data-slot="loading"
378
378
  class="flex items-center gap-2 px-2 py-1.5 text-base text-ink-gray-5"
379
379
  >
380
- <LoadingIndicator class="size-4" />
380
+ <Spinner class="size-4" />
381
381
  <span>{{ loadingText }}</span>
382
382
  </div>
383
383
 
@@ -3,7 +3,7 @@
3
3
  import { ref, onMounted, onUnmounted, computed, h } from 'vue'
4
4
  import { NodeViewWrapper, nodeViewProps } from '@tiptap/vue-3'
5
5
  import { Button } from '#components/Button'
6
- import { LoadingIndicator } from '#components/LoadingIndicator'
6
+ import { Spinner } from '#components/Spinner'
7
7
  import Tooltip from '#components/Tooltip/Tooltip.vue'
8
8
  import { localFileMap } from '../extensions/image/image-extension'
9
9
  import { ErrorMessage } from '#components/ErrorMessage'
@@ -375,7 +375,7 @@ const wrapperClasses = (float: string) => [
375
375
  <div
376
376
  class="bg-gray-900/80 p-2 inset-0 leading-none rounded-1 flex flex-col items-center justify-center gap-2">
377
377
  <div class="flex items-center gap-2">
378
- <LoadingIndicator class="text-gray-100 size-4" />
378
+ <Spinner class="text-gray-100 size-4" />
379
379
  <span class="text-gray-100">Uploading {{ isVideo ? 'video' : 'image' }}...</span>
380
380
  </div>
381
381
  </div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "frappe-ui",
3
- "version": "1.0.0-rc.1",
3
+ "version": "1.0.0",
4
4
  "description": "A set of components and utilities for rapid UI development",
5
5
  "engines": {
6
6
  "node": ">=20.19.0"
@@ -50,7 +50,7 @@
50
50
  "test": "vitest --run",
51
51
  "test:coverage": "vitest --run --coverage",
52
52
  "test:cypress": "cypress run --component",
53
- "test:cypress:coverage": "cross-env COVERAGE=true cypress run --component",
53
+ "test:cypress:coverage": "cross-env COVERAGE=true cypress run --component && nyc report",
54
54
  "coverage": "yarn test:coverage && yarn test:cypress:coverage && yarn coverage:merge",
55
55
  "coverage:merge": "tsx .github/scripts/merge-coverage.ts",
56
56
  "type-check": "vue-tsc --noEmit -p tsconfig.app.json && vue-tsc --noEmit -p tsconfig.node.json",
@@ -310,7 +310,6 @@
310
310
  "@codemirror/lang-xml": "^6.1.0",
311
311
  "@codemirror/lang-yaml": "^6.1.3",
312
312
  "@codemirror/lint": "^6.0.0",
313
- "@cypress/code-coverage": "^3.13.10",
314
313
  "@shikijs/transformers": "^3.15.0",
315
314
  "@vitejs/plugin-vue": "^6.0.7",
316
315
  "@vitest/coverage-v8": "^3.2.4",
package/readme.md CHANGED
@@ -3,116 +3,176 @@
3
3
  <img src="https://github.com/user-attachments/assets/0a81cdc1-d957-47a9-b151-f5571be0d038" width="80" />
4
4
 
5
5
  # Frappe UI
6
- **Rapidly build modern frontends for Frappe apps**
7
6
 
8
- <img alt="NPM Downloads" src="https://img.shields.io/npm/dm/frappe-ui.svg?style=flat"/>
7
+ **The all-in-one toolkit for modern frontends. High-quality components, app recipes and AI-ready docs.**
8
+
9
+ <a href="https://www.npmjs.com/package/frappe-ui"><img alt="npm version" src="https://img.shields.io/npm/v/frappe-ui.svg?style=flat" /></a>
10
+ <a href="https://www.npmjs.com/package/frappe-ui"><img alt="npm downloads" src="https://img.shields.io/npm/dm/frappe-ui.svg?style=flat" /></a>
11
+ <a href="./license.md"><img alt="MIT license" src="https://img.shields.io/npm/l/frappe-ui.svg?style=flat" /></a>
9
12
 
10
13
  <a href="https://ui.frappe.io">
11
- <img width="1292" alt="Screenshot 2024-12-12 at 5 27 58 PM" src="https://github.com/user-attachments/assets/56800b45-2859-4dc5-92b8-e40959ce4902" />
14
+ <picture>
15
+ <source media="(prefers-color-scheme: dark)" srcset="./assets/readme-hero-dark.png" />
16
+ <img width="1392" alt="The Button page on the Frappe UI documentation site" src="./assets/readme-hero-light.png" />
17
+ </picture>
12
18
  </a>
13
- </div>
14
-
15
- ## Frappe UI
16
-
17
- Frappe UI provides a set of components and utilities for rapid UI development. Components are built using Vue 3 and Tailwind.
18
- Along with generic components like Button, Link, Dialog, etc., it also contains utilities for handling server-side data fetching, directives and utilities.
19
-
20
-
21
- ### Motivation
22
- In 2019, I began building [Frappe Books](https://github.com/frappe/books) which had a new design. This led to the creation of small reusable components like Button, Dialog, and Card. Moving on to [Frappe Cloud](https://github.com/frappe/press) in 2020, I reused and evolved these components in the Frappe Cloud UI. In 2022, while starting a new project, I decided to extract these components into a standalone package to avoid repeating the copy-paste process. This package is now being developed alongside the [Gameplan](https://github.com/frappe/gameplan), continually adding generic components and utilities for frontend development.
23
19
 
24
- ### Under the Hood
20
+ [Documentation](https://ui.frappe.io) ·
21
+ [Getting started](https://ui.frappe.io/docs/getting-started) ·
22
+ [Migration guide](https://ui.frappe.io/docs/migration) ·
23
+ [Changelog](https://ui.frappe.io/docs/changelog)
25
24
 
26
- - [TailwindCSS](https://github.com/tailwindlabs/tailwindcss): Utility first CSS Framework to build design system based UI.
27
- - [Reka UI](https://github.com/unovue/reka-ui): Unstyled and accessible UI primitives.
28
- - [TipTap](https://github.com/ueberdosis/tiptap): ProseMirror based rich-text editor with a Vue API.
29
- - [dayjs](https://github.com/iamkun/dayjs): Minimal javascript library for working with dates.
30
-
31
- ## Links
32
-
33
- - [Documentation](https://ui.frappe.io)
34
- - [Vite Plugins](vite/README.md)
35
- - [Frappe UI Starter Boilerplate](https://github.com/netchampfaris/frappe-ui-starter)
36
- - [Community](https://github.com/frappe/frappe-ui/discussions)
37
-
38
- ## Usage
25
+ </div>
39
26
 
40
- Requires **Node `>=20.19.0`**, Vite, Vue 3 and Tailwind CSS v3.4.
27
+ ## What you get
28
+
29
+ - **50+ components.** Buttons, form controls, dialogs, popovers, tabs, sidebars,
30
+ app shells and more. Built with [Vue 3](https://vuejs.org),
31
+ [Tailwind CSS](https://tailwindcss.com) and [Reka UI](https://reka-ui.com)
32
+ primitives.
33
+ - **Design tokens with dark mode built in.** Semantic `surface`, `ink` and
34
+ `outline` color tokens, plus radius, typography and elevation scales. Tokens
35
+ flip under `[data-theme="dark"]`, so a `dark:` variant is never needed.
36
+ - **Data composables for Frappe.** `useCall`, `useList`, `useDoc`, `useDoctype`
37
+ and `useNewDoc` handle fetching, caching, pagination and writes against a
38
+ Frappe backend.
39
+ - **Rich text editor, lists and charts** as separate entries: `frappe-ui/editor`
40
+ (TipTap), `frappe-ui/list` and `frappe-ui/charts` (ECharts).
41
+ - **Vite plugins** for the Frappe dev-server proxy, Lucide icon auto-imports,
42
+ DocType type generation and production builds.
43
+ - **TypeScript first.** Every core component ships typed props, slots and emits.
44
+ - **Recipes.** Eight full app screens built with frappe-ui, such as mail,
45
+ tickets, deals and accounting, each with a desktop and a mobile layout. See
46
+ them on the [home page](https://ui.frappe.io).
47
+ - **Agent skills.** An [agent skill](./skills/frappe-ui/) and
48
+ [llms.txt](https://ui.frappe.io/llms.txt) teach Claude Code, Cursor, Codex
49
+ and similar tools how to use the library. See
50
+ [Using with AI agents](#using-with-ai-agents).
51
+
52
+ ## Quick start
53
+
54
+ Requires **Node `>=20.19.0`**, **Vite**, **Vue `>=3.5`** and **Tailwind CSS
55
+ `>=3.4 <4`**.
41
56
 
42
57
  ```sh
43
58
  npm install frappe-ui
44
- # or
45
- yarn add frappe-ui
46
59
  ```
47
60
 
48
- Add the frappe-ui Vite plugin in `vite.config.js`:
61
+ Add the Vite plugin in `vite.config.js`:
49
62
 
50
63
  ```js
64
+ import { defineConfig } from 'vite'
51
65
  import vue from '@vitejs/plugin-vue'
52
66
  import frappeui from 'frappe-ui/vite'
53
67
 
54
- export default {
68
+ export default defineConfig({
55
69
  plugins: [frappeui(), vue()],
56
- }
70
+ })
57
71
  ```
58
72
 
59
- Add the preset and frappe-ui's source globs in `tailwind.config.js`:
73
+ Add the preset and content globs in `tailwind.config.js`:
60
74
 
61
75
  ```js
62
- import frappeUIPreset, { content } from 'frappe-ui/tailwind'
76
+ import preset, { content } from 'frappe-ui/tailwind'
63
77
 
64
78
  export default {
65
- presets: [frappeUIPreset],
66
- content: ['./index.html', './src/**/*.{vue,js,ts}', ...content],
79
+ presets: [preset],
80
+ content: [...content, './index.html', './src/**/*.{vue,js,ts}'],
67
81
  }
68
82
  ```
69
83
 
70
- Import the stylesheet once, from your CSS entry:
84
+ Import the stylesheet once from your CSS entry:
71
85
 
72
86
  ```css
73
87
  @import 'frappe-ui/style.css';
74
88
  ```
75
89
 
76
- Now use the components:
90
+ Wrap your app root in `FrappeUIProvider` and start using components:
77
91
 
78
92
  ```vue
79
93
  <script setup>
80
- import { Button } from 'frappe-ui'
94
+ import { FrappeUIProvider, Button, useList } from 'frappe-ui'
95
+
96
+ const todos = useList({
97
+ doctype: 'ToDo',
98
+ fields: ['name', 'description'],
99
+ filters: { status: 'Open' },
100
+ })
81
101
  </script>
82
102
 
83
103
  <template>
84
- <Button variant="solid" icon-left="lucide-plus">Click me</Button>
104
+ <FrappeUIProvider>
105
+ <ul>
106
+ <li v-for="todo in todos.data" :key="todo.name">
107
+ {{ todo.description }}
108
+ </li>
109
+ </ul>
110
+ <Button variant="solid" icon-left="lucide-plus" @click="todos.next()">
111
+ Load more
112
+ </Button>
113
+ </FrappeUIProvider>
85
114
  </template>
86
115
  ```
87
116
 
88
- The full setup, including TypeScript and the Frappe dev-server proxy, is on the
89
- [Getting Started](https://ui.frappe.io/docs/getting-started) page.
117
+ The full setup, including the starter template and TypeScript, is on the
118
+ [Installation](https://ui.frappe.io/docs/getting-started) page. The
119
+ [Frappe app](https://ui.frappe.io/docs/getting-started/frappe) guide covers the
120
+ dev-server proxy.
121
+
122
+ ## Package entries
90
123
 
91
- ## Claude Code skill
124
+ | Import | Contents |
125
+ | ----------------------- | ---------------------------------------------------------------------- |
126
+ | `frappe-ui` | Core components, data composables, `dialog` and `toast`, directives |
127
+ | `frappe-ui/editor` | TipTap-based rich text editor, kits and extensions |
128
+ | `frappe-ui/code-editor` | CodeMirror-based code editor and `CodeKit` |
129
+ | `frappe-ui/list` | Composable `List` family: rows, cells, headers, groups, sorting |
130
+ | `frappe-ui/charts` | ECharts-based area, bar, line, donut, funnel, heatmap, sankey, scatter |
131
+ | `frappe-ui/icons` | Frappe's own icons, such as `StepsIcon` and `LightningIcon` |
132
+ | `frappe-ui/vite` | Vite plugins |
133
+ | `frappe-ui/tailwind` | Tailwind preset and content globs |
134
+ | `frappe-ui/style.css` | Base stylesheet, fonts and token variables |
92
135
 
93
- For AI coding agents (Claude Code, Cursor, Codex, etc.), Frappe UI ships an agent skill that teaches the agent the library's conventions — semantic Tailwind tokens, the `variant` + `theme` color axes, the `useCall` data-fetching composable, common UI recipes, and anti-patterns to avoid.
136
+ ## Using with AI agents
94
137
 
95
- Install with [Vercel's `skills` CLI](https://github.com/vercel-labs/skills):
138
+ Install the frappe-ui skill in your coding agent:
96
139
 
97
140
  ```sh
98
141
  npx skills add https://github.com/frappe/frappe-ui/tree/main/skills/frappe-ui
99
142
  ```
100
143
 
101
- The skill lives in [`skills/frappe-ui/`](./skills/frappe-ui/) and is updated alongside the library.
144
+ Or point it to [ui.frappe.io/llms.txt](https://ui.frappe.io/llms.txt), a list
145
+ of every docs page.
146
+
147
+ ## Contributing
102
148
 
103
- ## Used By
149
+ ```sh
150
+ yarn # install
151
+ yarn dev # component playground
152
+ yarn docs:dev # documentation site
153
+ yarn test # vitest
154
+ yarn type-check # vue-tsc
155
+ ```
104
156
 
105
- Frappe UI is being used in a lot of products by
106
- [Frappe](https://github.com/frappe).
157
+ Before you change a public API, read [`PHILOSOPHY.md`](./PHILOSOPHY.md) for the
158
+ design rules, [`CONTEXT.md`](./CONTEXT.md) for the shared vocabulary and
159
+ [`spec/`](./spec/) for component contracts and decision records.
107
160
 
108
- - [Frappe Cloud](https://frappecloud.com)
109
- - [Gameplan](https://github.com/frappe/gameplan)
161
+ ## Used by
162
+
163
+ - [Frappe Cloud](https://frappe.io/cloud)
164
+ - [Frappe CRM](https://github.com/frappe/crm)
110
165
  - [Helpdesk](https://github.com/frappe/helpdesk)
111
- - [Frappe Insights](https://github.com/frappe/insights)
112
- - [Frappe Drive](https://github.com/frappe/drive)
113
- - [Frappe Builder](https://github.com/frappe/builder)
166
+ - [Frappe HR](https://github.com/frappe/hrms)
167
+ - [Frappe Learning](https://github.com/frappe/lms)
168
+ - [Insights](https://github.com/frappe/insights)
169
+ - [Builder](https://github.com/frappe/builder)
170
+ - [Gameplan](https://github.com/frappe/gameplan)
171
+
172
+ ## License
173
+
174
+ [MIT](./license.md)
114
175
 
115
- <br>
116
176
  <br>
117
177
  <div align="center">
118
178
  <a href="https://frappe.io" target="_blank">
@@ -121,10 +121,9 @@
121
121
  :dir="dir"
122
122
  >
123
123
  <template #default="slotProps">
124
+ <!-- The value item carries the stage's name, so a header would print
125
+ it twice. -->
124
126
  <slot name="tooltip" v-bind="slotProps">
125
- <div class="mb-2 text-p-sm text-ink-gray-5">
126
- {{ slotProps.label }}
127
- </div>
128
127
  <div class="flex flex-col gap-1.5 text-p-sm">
129
128
  <div
130
129
  v-for="item in slotProps.items.filter(
@@ -170,7 +169,7 @@
170
169
 
171
170
  <script setup lang="ts">
172
171
  import { computed, reactive, ref } from 'vue'
173
- import { formatLabel, formatPercent, formatValue } from './format'
172
+ import { formatPercent, formatValue } from './format'
174
173
  import { buildFunnelStages, funnelShapes } from './funnelGeometry'
175
174
  import { useTooltipDismiss } from './core/useTooltipDismiss'
176
175
  import { paletteColors, useChartTokens } from './tokens'
@@ -290,7 +289,9 @@ function readStage(stage: FunnelStage) {
290
289
  tooltip.items = [
291
290
  {
292
291
  name: String(stage.index),
293
- label: formatLabel(props.value),
292
+ // `value` is a row key, often one the caller named when reshaping its
293
+ // rows, so it says nothing to the reader.
294
+ label: stage.label,
294
295
  color: colors.value[stage.index],
295
296
  value: stage.value,
296
297
  formattedValue: formatMeasure(stage.value),
@@ -12,12 +12,14 @@ import {
12
12
  } from './format'
13
13
  import { CHART_FONT_FAMILY } from './measureText'
14
14
  import { paletteColors, type ChartTokens } from './tokens'
15
+ import type { AxisChartFormatters } from './seriesData'
15
16
  import { mergeDeep } from './utils'
16
17
  import type {
17
18
  AxisChartBaseConfig,
18
19
  AxisChartConfig,
19
20
  AxisChartSeriesConfig,
20
21
  ChartMark,
22
+ ChartPalette,
21
23
  ChartPaletteName,
22
24
  ChartYAxisConfig,
23
25
  } from './types'
@@ -28,6 +30,8 @@ export type AxisChartOptionContext = {
28
30
  hiddenSeries?: string[]
29
31
  /** Plot width in pixels, once measured. Unset leaves pixel sizes to echarts. */
30
32
  width?: number
33
+ /** What a data label prints in when its series has no `format` of its own. */
34
+ format?: AxisChartFormatters
31
35
  }
32
36
 
33
37
  export const AXIS_LABEL_FONT_SIZE = 11
@@ -106,6 +110,35 @@ export const AXIS_LABEL_MARGIN = 8
106
110
 
107
111
  const DEFAULT_PALETTE: ChartPaletteName = 'sequential'
108
112
 
113
+ /**
114
+ * The categorical ramp is five hue families, each a dark member then its light
115
+ * partner. Two strengths of one hue tangle where lines cross, so lines take the
116
+ * partners shifted one family along: no two neighbouring lines share a hue.
117
+ */
118
+ const LINE_ORDER = [0, 3, 4, 7, 8, 1, 2, 5, 6, 9]
119
+
120
+ /**
121
+ * One hue getting paler reads as a single series once two lines cross, so a
122
+ * chart of two or more lines, and nothing else, is drawn in separate hues. A
123
+ * bar among them keeps the sequential ramp: see `colorSlots`.
124
+ */
125
+ function defaultPalette(
126
+ config: AxisChartConfig,
127
+ tokens: ChartTokens,
128
+ ): ChartPalette {
129
+ const lines = config.series.filter(
130
+ (series) => resolveMark(series, config, true) === 'line',
131
+ )
132
+ const ramp = tokens.categorical
133
+ if (
134
+ lines.length < 2 ||
135
+ lines.length < config.series.length ||
136
+ ramp.length !== LINE_ORDER.length
137
+ )
138
+ return DEFAULT_PALETTE
139
+ return LINE_ORDER.map((i) => ramp[i])
140
+ }
141
+
109
142
  const MARKS: ChartMark[] = ['bar', 'line', 'area']
110
143
 
111
144
  /**
@@ -120,13 +153,9 @@ export function resolveSeriesColors(
120
153
  config: AxisChartConfig,
121
154
  tokens: ChartTokens,
122
155
  ): Record<string, string> {
123
- const assigned = paletteColors(
124
- config.palette,
125
- tokens,
126
- config.series.length,
127
- DEFAULT_PALETTE,
128
- )
129
- const slots = colorSlots(config)
156
+ const palette = config.palette ?? defaultPalette(config, tokens)
157
+ const assigned = paletteColors(palette, tokens, config.series.length)
158
+ const slots = colorSlots(config, palette)
130
159
  const colors: Record<string, string> = {}
131
160
  config.series.forEach((series, index) => {
132
161
  colors[series.name] = series.color || assigned[slots[index]]
@@ -144,10 +173,9 @@ export function resolveSeriesColors(
144
173
  * list is drawn as it was written, a diverging ramp's direction is its meaning,
145
174
  * and a categorical set is unrelated hues with no ramp to reorder.
146
175
  */
147
- function colorSlots(config: AxisChartConfig): number[] {
176
+ function colorSlots(config: AxisChartConfig, palette: ChartPalette): number[] {
148
177
  const identity = config.series.map((_, index) => index)
149
- if (Array.isArray(config.palette)) return identity
150
- if ((config.palette ?? DEFAULT_PALETTE) !== 'sequential') return identity
178
+ if (palette !== 'sequential') return identity
151
179
 
152
180
  const weights = config.series.map(
153
181
  (series) => INK_WEIGHT[resolveMark(series, config, true)],