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.
- package/experimental/Calendar/Calendar.api.md +71 -0
- package/experimental/Calendar/Calendar.vue +19 -0
- package/experimental/ListView/ListView.api.md +26 -0
- package/experimental/ListView/ListView.vue +7 -0
- package/experimental/MultiEmailInput/MultiEmailInput.vue +2 -2
- package/experimental/TextEditor/components/MediaNodeView.vue +2 -2
- package/package.json +2 -3
- package/readme.md +116 -56
- package/src/charts/FunnelChart.vue +6 -5
- package/src/charts/axisChartCommon.ts +38 -10
- package/src/charts/axisChartOptions.ts +16 -11
- package/src/charts/axisFormat.ts +21 -1
- package/src/charts/core/useAxisChart.ts +3 -6
- package/src/charts/docs/AreaChart.api.md +12 -1
- package/src/charts/docs/AreaChart.md +20 -22
- package/src/charts/docs/BarChart.api.md +12 -1
- package/src/charts/docs/BarChart.md +83 -86
- package/src/charts/docs/ChartCard.md +14 -16
- package/src/charts/docs/ChartContainer.md +38 -39
- package/src/charts/docs/ChartLegend.md +19 -19
- package/src/charts/docs/ChartTooltip.md +25 -26
- package/src/charts/docs/Dashboard.md +3 -3
- package/src/charts/docs/DonutChart.api.md +11 -0
- package/src/charts/docs/DonutChart.md +19 -20
- package/src/charts/docs/FunnelChart.md +8 -8
- package/src/charts/docs/HeatmapChart.api.md +11 -0
- package/src/charts/docs/HeatmapChart.md +12 -11
- package/src/charts/docs/LineChart.api.md +12 -1
- package/src/charts/docs/LineChart.md +53 -55
- package/src/charts/docs/NumberCard.md +19 -18
- package/src/charts/docs/Overview.md +104 -100
- package/src/charts/docs/SankeyChart.api.md +11 -0
- package/src/charts/docs/SankeyChart.md +14 -14
- package/src/charts/docs/ScatterChart.api.md +11 -0
- package/src/charts/docs/ScatterChart.md +44 -49
- package/src/charts/docs/States.md +21 -24
- package/src/charts/index.ts +2 -2
- package/src/charts/types.ts +22 -2
- package/src/components/Alert/Alert.md +52 -35
- package/src/components/Avatar/Avatar.md +55 -9
- package/src/components/Badge/Badge.md +34 -8
- package/src/components/BottomSheet/BottomSheet.md +48 -37
- package/src/components/Breadcrumbs/Breadcrumbs.md +39 -11
- package/src/components/Button/Button.md +67 -31
- package/src/components/Checkbox/Checkbox.api.md +11 -0
- package/src/components/Checkbox/Checkbox.md +48 -42
- package/src/components/Checkbox/Checkbox.vue +1 -0
- package/src/components/Combobox/Combobox.api.md +16 -0
- package/src/components/Combobox/Combobox.md +131 -85
- package/src/components/Combobox/Combobox.vue +15 -1
- package/src/components/Combobox/ComboboxResults.vue +2 -2
- package/src/components/ContextMenu/ContextMenu.md +56 -15
- package/src/components/DatePicker/DatePicker.api.md +61 -0
- package/src/components/DatePicker/DatePicker.md +85 -27
- package/src/components/DatePicker/DatePicker.vue +4 -0
- package/src/components/DatePicker/DateRangePicker.vue +4 -0
- package/src/components/DatePicker/DateTimePicker.vue +4 -0
- package/src/components/DesktopShell/DesktopShell.md +43 -37
- package/src/components/Dialog/Dialog.md +61 -85
- package/src/components/Divider/Divider.md +37 -9
- package/src/components/Dropdown/Dropdown.md +78 -23
- package/src/components/Duration/Duration.api.md +11 -0
- package/src/components/Duration/Duration.md +50 -59
- package/src/components/Duration/Duration.vue +1 -0
- package/src/components/ErrorMessage/ErrorMessage.md +32 -12
- package/src/components/FileUploader/FileUploader.md +75 -16
- package/src/components/FormControl/FormControl.api.md +11 -0
- package/src/components/FormControl/FormControl.md +44 -26
- package/src/components/FormControl/FormControl.vue +1 -0
- package/src/components/FormLabel/FormLabel.md +28 -8
- package/src/components/FrappeUIProvider/FrappeUIProvider.md +25 -15
- package/src/components/HoverCard/HoverCard.api.md +16 -0
- package/src/components/HoverCard/HoverCard.md +37 -32
- package/src/components/Icon/Icon.md +46 -16
- package/src/components/ItemListRow/ItemListRow.md +40 -7
- package/src/components/KeyboardShortcut/KeyboardShortcut.api.md +3 -3
- package/src/components/KeyboardShortcut/KeyboardShortcut.md +59 -37
- package/src/components/KeyboardShortcut/KeyboardShortcut.vue +19 -4
- package/src/components/KeyboardShortcut/types.ts +2 -2
- package/src/components/KeyboardShortcutsDialog/KeyboardShortcutsDialog.md +48 -45
- package/src/components/LoadingIndicator/index.ts +5 -2
- package/src/components/LoadingText/LoadingText.md +23 -4
- package/src/components/LoadingText/LoadingText.vue +2 -2
- package/src/components/MobileShell/MobileShell.api.md +73 -2
- package/src/components/MobileShell/MobileShell.md +107 -18
- package/src/components/MultiSelect/MultiSelect.api.md +16 -0
- package/src/components/MultiSelect/MultiSelect.md +106 -66
- package/src/components/MultiSelect/MultiSelect.vue +7 -3
- package/src/components/MultiSelect/MultiSelectResults.vue +2 -2
- package/src/components/PageHeader/PageHeader.md +79 -19
- package/src/components/PageHeader/PageHeaderTarget.vue +4 -1
- package/src/components/Password/Password.api.md +16 -0
- package/src/components/Password/Password.md +64 -14
- package/src/components/Password/Password.vue +27 -14
- package/src/components/Popover/Popover.api.md +21 -0
- package/src/components/Popover/Popover.md +61 -90
- package/src/components/Popover/Popover.vue +3 -0
- package/src/components/Progress/Progress.md +39 -19
- package/src/components/Radio/Radio.api.md +11 -0
- package/src/components/Radio/Radio.md +72 -35
- package/src/components/Radio/RadioGroup.vue +4 -0
- package/src/components/Rating/Rating.api.md +11 -0
- package/src/components/Rating/Rating.md +94 -56
- package/src/components/Rating/Rating.vue +8 -5
- package/src/components/ScrollArea/ScrollArea.api.md +11 -0
- package/src/components/ScrollArea/ScrollArea.md +29 -28
- package/src/components/Select/Select.api.md +16 -0
- package/src/components/Select/Select.md +80 -57
- package/src/components/Select/Select.vue +6 -1
- package/src/components/SettingsDialog/SettingsDialog.api.md +11 -0
- package/src/components/SettingsDialog/SettingsDialog.md +97 -26
- package/src/components/Sidebar/Sidebar.md +97 -87
- package/src/components/SidebarRail/SidebarRail.md +74 -31
- package/src/components/Skeleton/Skeleton.md +27 -10
- package/src/components/Slider/Slider.api.md +11 -0
- package/src/components/Slider/Slider.md +88 -60
- package/src/components/Slider/Slider.vue +1 -0
- package/src/components/Spinner/Spinner.md +36 -8
- package/src/components/Switch/Switch.api.md +11 -0
- package/src/components/Switch/Switch.md +42 -34
- package/src/components/Switch/Switch.vue +1 -0
- package/src/components/TabButtons/TabButtons.api.md +15 -4
- package/src/components/TabButtons/TabButtons.md +59 -104
- package/src/components/TabButtons/TabButtons.vue +10 -4
- package/src/components/TabButtons/types.ts +6 -3
- package/src/components/Tabs/TabList.vue +4 -4
- package/src/components/Tabs/TabTrigger.vue +2 -2
- package/src/components/Tabs/Tabs.api.md +6 -6
- package/src/components/Tabs/Tabs.md +58 -94
- package/src/components/Tabs/Tabs.vue +1 -1
- package/src/components/Tabs/context.ts +2 -2
- package/src/components/Tabs/index.ts +1 -1
- package/src/components/Tabs/types.ts +7 -4
- package/src/components/TextInput/TextInput.api.md +16 -0
- package/src/components/TextInput/TextInput.md +68 -31
- package/src/components/TextInput/TextInput.vue +1 -0
- package/src/components/Textarea/Textarea.api.md +16 -0
- package/src/components/Textarea/Textarea.md +53 -15
- package/src/components/Textarea/Textarea.vue +1 -0
- package/src/components/TimePicker/TimePicker.api.md +21 -0
- package/src/components/TimePicker/TimePicker.md +57 -32
- package/src/components/TimePicker/TimePicker.vue +1 -0
- package/src/components/Toast/Toast.md +101 -30
- package/src/components/Tooltip/Tooltip.md +59 -81
- package/src/components/Tree/Tree.api.md +31 -0
- package/src/components/Tree/Tree.md +77 -112
- package/src/components/Tree/types.ts +5 -1
- package/src/components/shared/tabs/pillTypes.ts +1 -1
- package/src/components/shared/tabs/styles.ts +12 -12
- package/src/data-fetching/docStore.ts +16 -9
- package/src/data-fetching/useCall/useCall.ts +26 -14
- package/src/data-fetching/useIsolatedCall.ts +13 -11
- package/src/data-fetching/useList/useList.ts +103 -49
- package/src/data-fetching/utils.ts +42 -2
- package/src/molecules/code-editor/style.css +4 -4
- package/src/molecules/list/list.md +244 -182
- package/tailwind/preset.js +1 -1
- package/tailwind/tokens/build.js +1 -1
- package/vite/index.d.ts +3 -2
- package/vitepress/components/Brand.vue +26 -0
- package/vitepress/components/Docs/CommandPalette.vue +111 -51
- package/vitepress/components/Docs/Demo.vue +5 -0
- package/vitepress/components/Docs/ExposedTable.vue +100 -0
- package/vitepress/components/Docs/Header.vue +75 -0
- package/vitepress/components/Docs/MobileNavSheet.vue +7 -0
- package/vitepress/components/Docs/OnThisPage.vue +51 -21
- package/vitepress/components/Docs/PlaygroundFrame.vue +72 -40
- package/vitepress/components/Docs/PrevNextBtns.vue +0 -1
- package/vitepress/components/Docs/Search.vue +24 -5
- package/vitepress/components/Docs/Sidebar.vue +47 -47
- package/vitepress/components/Docs/index.ts +2 -0
- package/vitepress/components/Docs/pageMarkdown.ts +99 -0
- package/vitepress/components/Layout.vue +31 -22
- package/vitepress/components/Navbar.vue +9 -83
- package/vitepress/components/SiteActions.vue +68 -0
- package/vitepress/components/index.ts +2 -0
- package/vitepress/css/style.css +29 -1
- package/vitepress/plugins/colocatedComponentDocs.ts +3 -2
- package/src/components/LoadingIndicator/LoadingIndicator.api.md +0 -20
- package/src/components/LoadingIndicator/LoadingIndicator.md +0 -11
- package/src/components/LoadingIndicator/LoadingIndicator.vue +0 -19
- package/src/components/LoadingIndicator/types.ts +0 -4
- package/src/components/MobileNav/MobileNav.api.md +0 -77
- 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 {
|
|
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
|
-
<
|
|
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 {
|
|
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
|
-
<
|
|
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
|
|
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
|
-
|
|
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
|
-
<
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
73
|
+
Add the preset and content globs in `tailwind.config.js`:
|
|
60
74
|
|
|
61
75
|
```js
|
|
62
|
-
import
|
|
76
|
+
import preset, { content } from 'frappe-ui/tailwind'
|
|
63
77
|
|
|
64
78
|
export default {
|
|
65
|
-
presets: [
|
|
66
|
-
content: ['./index.html', './src/**/*.{vue,js,ts}'
|
|
79
|
+
presets: [preset],
|
|
80
|
+
content: [...content, './index.html', './src/**/*.{vue,js,ts}'],
|
|
67
81
|
}
|
|
68
82
|
```
|
|
69
83
|
|
|
70
|
-
Import the stylesheet once
|
|
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
|
-
|
|
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
|
-
<
|
|
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
|
|
89
|
-
[
|
|
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
|
-
|
|
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
|
-
|
|
136
|
+
## Using with AI agents
|
|
94
137
|
|
|
95
|
-
Install
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
106
|
-
[
|
|
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
|
-
|
|
109
|
-
|
|
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
|
|
112
|
-
- [Frappe
|
|
113
|
-
- [
|
|
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 {
|
|
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
|
-
|
|
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
|
|
124
|
-
|
|
125
|
-
|
|
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 (
|
|
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)],
|