frappe-ui 1.0.0-beta.62 → 1.0.0-beta.63
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/CodeEditor/CodeEditor.api.md +1 -1
- package/experimental/CodeEditor/CodeEditor.md +1 -1
- package/experimental/CodeEditor/CodeEditor.playground.vue +1 -1
- package/experimental/CodeEditor/CodeEditor.vue +2 -2
- package/experimental/CodeEditor/stories/Sizes.vue +1 -1
- package/experimental/CodeEditor/types.ts +1 -1
- package/experimental/MultiEmailInput/MultiEmailInput.cy.ts +27 -0
- package/experimental/MultiEmailInput/MultiEmailInput.vue +9 -1
- package/package.json +1 -1
- package/src/components/Checkbox/Checkbox.cy.ts +3 -3
- package/src/components/Combobox/Combobox.api.md +2 -2
- package/src/components/Combobox/Combobox.playground.vue +1 -1
- package/src/components/Combobox/Combobox.vue +5 -0
- package/src/components/Combobox/stories/Clearable.vue +5 -5
- package/src/components/Combobox/types.ts +9 -8
- package/src/components/ContextMenu/index.ts +1 -0
- package/src/components/ContextMenu/types.ts +16 -0
- package/src/components/DesktopShell/DesktopShell.api.md +1 -1
- package/src/components/DesktopShell/DesktopShell.md +6 -5
- package/src/components/DesktopShell/DesktopShell.vue +1 -1
- package/src/components/DesktopShell/stories/Default.vue +8 -8
- package/src/components/Divider/Divider.api.md +4 -4
- package/src/components/Divider/index.ts +1 -1
- package/src/components/Divider/types.ts +24 -1
- package/src/components/Duration/Duration.playground.vue +1 -1
- package/src/components/Duration/stories/Sizes.vue +1 -1
- package/src/components/FormControl/FormControl.api.md +2 -2
- package/src/components/FormControl/FormControl.cy.ts +40 -0
- package/src/components/FormControl/FormControl.playground.vue +2 -0
- package/src/components/FormControl/FormControl.vue +6 -1
- package/src/components/FormControl/types.ts +7 -2
- package/src/components/FormLabel/FormLabel.api.md +0 -7
- package/src/components/FormLabel/FormLabel.cy.ts +24 -5
- package/src/components/FormLabel/FormLabel.playground.vue +1 -11
- package/src/components/FormLabel/FormLabel.vue +7 -12
- package/src/components/FormLabel/types.ts +0 -3
- package/src/components/HoverCard/HoverCard.api.md +1 -1
- package/src/components/HoverCard/types.ts +3 -0
- package/src/components/InputLabeling/InputDescription.vue +1 -1
- package/src/components/InputLabeling/InputLabel.vue +9 -4
- package/src/components/ItemListRow/ItemListRow.api.md +1 -1
- package/src/components/ItemListRow/ItemListRow.vue +13 -7
- package/src/components/ItemListRow/types.ts +7 -1
- package/src/components/KeyboardShortcutsDialog/KeyboardShortcutsDialog.api.md +1 -1
- package/src/components/KeyboardShortcutsDialog/types.ts +3 -0
- package/src/components/MultiSelect/MultiSelect.api.md +3 -3
- package/src/components/MultiSelect/MultiSelect.playground.vue +1 -1
- package/src/components/MultiSelect/MultiSelect.vue +5 -2
- package/src/components/MultiSelect/types.ts +4 -8
- package/src/components/Password/Password.playground.vue +1 -1
- package/src/components/Password/stories/Sizes.vue +1 -1
- package/src/components/Rating/Rating.cy.ts +1 -1
- package/src/components/Rating/Rating.playground.vue +1 -1
- package/src/components/Rating/Rating.vue +14 -8
- package/src/components/Rating/stories/Sizes.vue +1 -1
- package/src/components/Select/Select.api.md +2 -2
- package/src/components/Select/Select.cy.ts +19 -1
- package/src/components/Select/Select.playground.vue +1 -1
- package/src/components/Select/types.ts +3 -2
- package/src/components/Select/utils.ts +12 -6
- package/src/components/{Rail/Rail.api.md → SidebarRail/SidebarRail.api.md} +11 -11
- package/src/components/{Rail/Rail.cy.ts → SidebarRail/SidebarRail.cy.ts} +31 -31
- package/src/components/SidebarRail/SidebarRail.md +36 -0
- package/src/components/{Rail/Rail.vue → SidebarRail/SidebarRail.vue} +2 -2
- package/src/components/{Rail/RailItem.vue → SidebarRail/SidebarRailItem.vue} +12 -12
- package/src/components/{Rail/RailItemBadge.vue → SidebarRail/SidebarRailItemBadge.vue} +1 -1
- package/src/components/SidebarRail/index.ts +4 -0
- package/src/components/{Rail → SidebarRail}/stories/Default.vue +10 -10
- package/src/components/{Rail → SidebarRail}/types.ts +1 -1
- package/src/components/Switch/Switch.cy.ts +2 -2
- package/src/components/TabButtons/TabButtons.api.md +4 -4
- package/src/components/TabButtons/TabButtons.vue +3 -0
- package/src/components/TabButtons/index.ts +1 -0
- package/src/components/TabButtons/types.ts +7 -0
- package/src/components/TextInput/TextInput.cy.ts +108 -4
- package/src/components/TextInput/TextInput.playground.vue +1 -1
- package/src/components/TextInput/TextInput.vue +60 -26
- package/src/components/TextInput/stories/Sizes.vue +1 -1
- package/src/components/Textarea/Textarea.api.md +1 -1
- package/src/components/Textarea/Textarea.cy.ts +65 -34
- package/src/components/Textarea/Textarea.playground.vue +1 -1
- package/src/components/Textarea/Textarea.vue +39 -14
- package/src/components/Textarea/stories/Sizes.vue +1 -1
- package/src/components/Textarea/types.ts +5 -1
- package/src/components/TimePicker/stories/SizesAndVariants.vue +1 -1
- package/src/components/Tree/Tree.vue +2 -6
- package/src/components/Tree/types.ts +8 -0
- package/src/components/shared/picker/PickerShell.vue +34 -37
- package/src/components/shared/selection/utils.test.ts +54 -2
- package/src/components/shared/selection/utils.ts +64 -21
- package/src/components/shared/tabs/styles.ts +1 -1
- package/src/composables/inputTypes.ts +14 -2
- package/src/composables/usePortalTarget.spec.ts +6 -5
- package/src/index.ts +12 -1
- package/src/molecules/list/List.cy.ts +426 -0
- package/src/molecules/list/List.ssr.test.ts +59 -0
- package/src/molecules/list/List.vue +92 -7
- package/src/molecules/list/index.ts +2 -0
- package/src/molecules/list/list-context.ts +2 -1
- package/src/molecules/list/list.api.md +3 -3
- package/src/molecules/list/list.md +152 -33
- package/src/molecules/list/stories/Columns.vue +15 -9
- package/src/molecules/list/stories/Responsive.vue +56 -0
- package/src/molecules/list/stories/RowActions.vue +123 -0
- package/src/molecules/list/style.css +67 -26
- package/src/molecules/list/types.ts +51 -9
- package/tailwind/listColumns.js +191 -0
- package/tailwind/listColumns.test.js +358 -0
- package/tailwind/plugin.js +5 -6
- package/src/components/Rail/Rail.md +0 -30
- package/src/components/Rail/index.ts +0 -2
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
},
|
|
42
42
|
{
|
|
43
43
|
name: 'size',
|
|
44
|
-
description: 'Size token, mirroring frappe-ui inputs. Scales the editor\'s font size and\nminimum height (`sm | md | lg
|
|
44
|
+
description: 'Size token, mirroring frappe-ui inputs. Scales the editor\'s font size and\nminimum height (`xs | sm | md | lg`). Defaults to `md`.',
|
|
45
45
|
required: false,
|
|
46
46
|
type: 'InputSize',
|
|
47
47
|
default: '"md"'
|
|
@@ -40,7 +40,7 @@ and loses the affordance that it's an editable field.
|
|
|
40
40
|
|
|
41
41
|
## Sizes
|
|
42
42
|
|
|
43
|
-
`size` mirrors the frappe-ui input sizes (`sm | md | lg
|
|
43
|
+
`size` mirrors the frappe-ui input sizes (`xs | sm | md | lg`, default `md`),
|
|
44
44
|
scaling the editor's font size and minimum height.
|
|
45
45
|
|
|
46
46
|
<ComponentPreview name="CodeEditor-Sizes" />
|
|
@@ -43,10 +43,10 @@ const knobs: Knob[] = [
|
|
|
43
43
|
type: 'tabs',
|
|
44
44
|
default: 'md',
|
|
45
45
|
options: [
|
|
46
|
+
{ label: 'xs', value: 'xs' },
|
|
46
47
|
{ label: 'sm', value: 'sm' },
|
|
47
48
|
{ label: 'md', value: 'md' },
|
|
48
49
|
{ label: 'lg', value: 'lg' },
|
|
49
|
-
{ label: 'xl', value: 'xl' },
|
|
50
50
|
],
|
|
51
51
|
},
|
|
52
52
|
{ name: 'placeholder', type: 'text', default: '', width: '12rem' },
|
|
@@ -288,17 +288,17 @@ function buildVariant(v?: string, disabled?: boolean): Extension {
|
|
|
288
288
|
return cmView.EditorView.theme(fragments[v ?? 'subtle'] ?? fragments.subtle)
|
|
289
289
|
}
|
|
290
290
|
|
|
291
|
-
// Per-size font scale + content min-height, mirroring frappe-ui's `sm|md|lg
|
|
291
|
+
// Per-size font scale + content min-height, mirroring frappe-ui's `xs|sm|md|lg`
|
|
292
292
|
// input sizes. This compartment is the SOLE source of `&` font size and
|
|
293
293
|
// `.cm-content` min-height — the base `theme` deliberately omits both so there's
|
|
294
294
|
// no equal-specificity fight whose winner depends on StyleModule order.
|
|
295
295
|
function buildSize(s?: string): Extension {
|
|
296
296
|
if (!cmView) return []
|
|
297
297
|
const sizes: Record<string, { fontSize: string; minHeight: string }> = {
|
|
298
|
+
xs: { fontSize: '11px', minHeight: '2.5rem' },
|
|
298
299
|
sm: { fontSize: '12px', minHeight: '3.5rem' },
|
|
299
300
|
md: { fontSize: '13px', minHeight: '4.5rem' },
|
|
300
301
|
lg: { fontSize: '14px', minHeight: '6rem' },
|
|
301
|
-
xl: { fontSize: '16px', minHeight: '7.5rem' },
|
|
302
302
|
}
|
|
303
303
|
const cfg = sizes[s ?? 'md'] ?? sizes.md
|
|
304
304
|
return cmView.EditorView.theme({
|
|
@@ -7,9 +7,9 @@ const code = ref(`const sum = (a, b) => a + b`)
|
|
|
7
7
|
|
|
8
8
|
<template>
|
|
9
9
|
<div class="w-full max-w-xl space-y-4">
|
|
10
|
+
<CodeEditor v-model="code" language="javascript" size="xs" />
|
|
10
11
|
<CodeEditor v-model="code" language="javascript" size="sm" />
|
|
11
12
|
<CodeEditor v-model="code" language="javascript" size="md" />
|
|
12
13
|
<CodeEditor v-model="code" language="javascript" size="lg" />
|
|
13
|
-
<CodeEditor v-model="code" language="javascript" size="xl" />
|
|
14
14
|
</div>
|
|
15
15
|
</template>
|
|
@@ -42,7 +42,7 @@ export interface CodeEditorProps extends InputLabelingProps {
|
|
|
42
42
|
variant?: Exclude<InputVariant, 'ghost'>
|
|
43
43
|
/**
|
|
44
44
|
* Size token, mirroring frappe-ui inputs. Scales the editor's font size and
|
|
45
|
-
* minimum height (`sm | md | lg
|
|
45
|
+
* minimum height (`xs | sm | md | lg`). Defaults to `md`.
|
|
46
46
|
*/
|
|
47
47
|
size?: InputSize
|
|
48
48
|
}
|
|
@@ -17,6 +17,7 @@ function host(
|
|
|
17
17
|
required?: boolean
|
|
18
18
|
error?: string
|
|
19
19
|
disabled?: boolean
|
|
20
|
+
size?: 'xs' | 'sm' | 'md' | 'lg'
|
|
20
21
|
} = {},
|
|
21
22
|
) {
|
|
22
23
|
return defineComponent({
|
|
@@ -46,6 +47,7 @@ function host(
|
|
|
46
47
|
required: options.required,
|
|
47
48
|
error: options.error,
|
|
48
49
|
disabled: options.disabled,
|
|
50
|
+
size: options.size,
|
|
49
51
|
placeholder: 'Add email…',
|
|
50
52
|
}),
|
|
51
53
|
]
|
|
@@ -152,4 +154,29 @@ describe('MultiEmailInput', () => {
|
|
|
152
154
|
cy.mount(host({ disabled: true }))
|
|
153
155
|
cy.get('[data-slot="input"]').should('have.attr', 'disabled')
|
|
154
156
|
})
|
|
157
|
+
|
|
158
|
+
// The option-row avatar has to shrink with the row. `md` (24px) is the whole
|
|
159
|
+
// height of an `xs` row before its padding, so a size-invariant avatar makes
|
|
160
|
+
// the smallest control draw the biggest picture.
|
|
161
|
+
const rowAvatarHeights: Array<[size: 'xs' | 'sm' | 'md' | 'lg', px: number]> =
|
|
162
|
+
[
|
|
163
|
+
['xs', 16],
|
|
164
|
+
['sm', 20],
|
|
165
|
+
['md', 24],
|
|
166
|
+
['lg', 24],
|
|
167
|
+
]
|
|
168
|
+
|
|
169
|
+
for (const [size, px] of rowAvatarHeights) {
|
|
170
|
+
it(`draws a ${px}px option-row avatar at size="${size}"`, () => {
|
|
171
|
+
cy.mount(host({ size }))
|
|
172
|
+
cy.get('[data-slot="input"]').click()
|
|
173
|
+
cy.get('[data-slot="item"]')
|
|
174
|
+
.first()
|
|
175
|
+
.find('[data-slot="item-prefix"] > div')
|
|
176
|
+
.first()
|
|
177
|
+
.then(($avatar) => {
|
|
178
|
+
expect($avatar[0].getBoundingClientRect().height).to.equal(px)
|
|
179
|
+
})
|
|
180
|
+
})
|
|
181
|
+
}
|
|
155
182
|
})
|
|
@@ -166,7 +166,15 @@ const showEmpty = computed(
|
|
|
166
166
|
() => !props.loading && !suggestions.value.length && !showCreateOption.value,
|
|
167
167
|
)
|
|
168
168
|
|
|
169
|
-
|
|
169
|
+
// Avatar has its own scale (`xs` 16px, `sm` 20px, `md` 24px), and the option
|
|
170
|
+
// row is `min-h-6`/`min-h-7`/`min-h-8`/`min-h-10` plus `ItemListRow`'s own
|
|
171
|
+
// padding. Step the avatar down with the row for the two small sizes rather
|
|
172
|
+
// than forwarding a size the row cannot draw — a `md` avatar is 24px, the
|
|
173
|
+
// whole height of an `xs` row before its `py-1`. `md` and `lg` rows both fit
|
|
174
|
+
// `md`; a 28px `lg` avatar would leave no room in a 32px `md` row.
|
|
175
|
+
const rowAvatarSize = computed(() =>
|
|
176
|
+
props.size === 'xs' ? 'xs' : props.size === 'sm' ? 'sm' : 'md',
|
|
177
|
+
)
|
|
170
178
|
|
|
171
179
|
const boxClasses = computed(() => [
|
|
172
180
|
'flex flex-wrap items-center gap-1.5 rounded-4 bg-surface-gray-2 px-1.5 py-1 transition-colors focus-within:ring-2 focus-within:ring-outline-gray-3',
|
package/package.json
CHANGED
|
@@ -17,7 +17,7 @@ describe('Checkbox', () => {
|
|
|
17
17
|
cy.get('input[type="checkbox"]').should('be.disabled')
|
|
18
18
|
// A disabled control dims its label — `disabled` outranks `color="gray-7"`.
|
|
19
19
|
cy.get('label')
|
|
20
|
-
.should('have.class', 'text-
|
|
20
|
+
.should('have.class', 'text-sm')
|
|
21
21
|
.and('have.class', 'text-ink-gray-4')
|
|
22
22
|
.and('not.have.class', 'text-ink-gray-7')
|
|
23
23
|
})
|
|
@@ -28,8 +28,8 @@ describe('Checkbox', () => {
|
|
|
28
28
|
})
|
|
29
29
|
|
|
30
30
|
cy.get('[data-slot="description"]')
|
|
31
|
-
.should('have.class', 'text-ink-gray-
|
|
32
|
-
.and('not.have.class', 'text-ink-gray-
|
|
31
|
+
.should('have.class', 'text-ink-gray-4')
|
|
32
|
+
.and('not.have.class', 'text-ink-gray-6')
|
|
33
33
|
})
|
|
34
34
|
|
|
35
35
|
it('toggles with the space key', () => {
|
|
@@ -30,14 +30,14 @@
|
|
|
30
30
|
name: 'variant',
|
|
31
31
|
description: 'Visual style of the combobox.',
|
|
32
32
|
required: false,
|
|
33
|
-
type: '
|
|
33
|
+
type: 'InputVariant',
|
|
34
34
|
default: '"subtle"'
|
|
35
35
|
},
|
|
36
36
|
{
|
|
37
37
|
name: 'size',
|
|
38
38
|
description: 'Size of the trigger and option rows.',
|
|
39
39
|
required: false,
|
|
40
|
-
type: '
|
|
40
|
+
type: 'InputSize',
|
|
41
41
|
default: '"sm"'
|
|
42
42
|
},
|
|
43
43
|
{
|
|
@@ -35,10 +35,10 @@ const knobs: Knob[] = [
|
|
|
35
35
|
type: 'tabs',
|
|
36
36
|
default: 'sm',
|
|
37
37
|
options: [
|
|
38
|
+
{ label: 'xs', value: 'xs' },
|
|
38
39
|
{ label: 'sm', value: 'sm' },
|
|
39
40
|
{ label: 'md', value: 'md' },
|
|
40
41
|
{ label: 'lg', value: 'lg' },
|
|
41
|
-
{ label: 'xl', value: 'xl' },
|
|
42
42
|
],
|
|
43
43
|
},
|
|
44
44
|
{
|
|
@@ -88,7 +88,12 @@ const slots = useReactiveSlots<ComboboxSlots>()
|
|
|
88
88
|
const slotFns = slots as Record<string, ((props?: any) => any) | undefined>
|
|
89
89
|
|
|
90
90
|
const model = defineModel<ComboboxOptionValue | null>({ default: null })
|
|
91
|
+
// Documented on `open` in `./types.ts`. A JSDoc block here would be appended
|
|
92
|
+
// to that description by `propsgen`, not replace it.
|
|
91
93
|
const open = defineModel<boolean>('open', { default: false })
|
|
94
|
+
// Optional outside-in control of the search box. Unbound, `defineModel` keeps
|
|
95
|
+
// the value local, so `v-model:query` is never required. Documented on `query`
|
|
96
|
+
// in `./types.ts`.
|
|
92
97
|
const query = defineModel<string>('query', { default: '' })
|
|
93
98
|
// Bound, the query is the consumer's — the component never resets it on its
|
|
94
99
|
// own (see `skipInitialDisplaySync` and the open watcher below).
|
|
@@ -11,7 +11,7 @@ const fields: Field[] = [
|
|
|
11
11
|
label: 'colour',
|
|
12
12
|
options: ['gray', 'blue', 'green', 'red'],
|
|
13
13
|
},
|
|
14
|
-
{ key: 'size', label: 'size', options: ['
|
|
14
|
+
{ key: 'size', label: 'size', options: ['xs', 'sm', 'md', 'lg'] },
|
|
15
15
|
{ key: 'style', label: 'style', options: ['subtle', 'outline', 'ghost'] },
|
|
16
16
|
{
|
|
17
17
|
key: 'fontSize',
|
|
@@ -41,10 +41,10 @@ const colourSwatch: Record<string, string> = {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
const sizeDot: Record<string, string> = {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
44
|
+
xs: 'size-1.5',
|
|
45
|
+
sm: 'size-2',
|
|
46
|
+
md: 'size-2.5',
|
|
47
|
+
lg: 'size-3',
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
const fontSizePx: Record<string, number> = {
|
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
import type { Component, VNodeChild } from 'vue'
|
|
2
|
+
import type { InputSize, InputVariant } from '../../composables/inputTypes'
|
|
2
3
|
import type { InputLabelingProps } from '../../composables/useInputLabeling'
|
|
3
4
|
import type { PortalTarget } from '../../composables/usePortalTarget'
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
6
|
+
/**
|
|
7
|
+
* Combobox renders the same trigger geometry and surfaces as every other text
|
|
8
|
+
* input, so these are the input scales under the component's own names. They
|
|
9
|
+
* stay aliases, not copies — a Combobox-only scale would have to come with a
|
|
10
|
+
* Combobox-only renderer.
|
|
11
|
+
*/
|
|
12
|
+
export type ComboboxVariant = InputVariant
|
|
13
|
+
export type ComboboxSize = InputSize
|
|
7
14
|
|
|
8
15
|
import type { PopoverSide, PopoverAlign } from '../shared/selection/types'
|
|
9
16
|
export type { PopoverSide, PopoverAlign }
|
|
@@ -312,12 +319,6 @@ export interface ComboboxEmits {
|
|
|
312
319
|
/** Fired when the committed value changes. */
|
|
313
320
|
'update:modelValue': [value: ComboboxOptionValue | null]
|
|
314
321
|
|
|
315
|
-
/** Fired when the open state changes. */
|
|
316
|
-
'update:open': [value: boolean]
|
|
317
|
-
|
|
318
|
-
/** Fired when the query changes. */
|
|
319
|
-
'update:query': [value: string]
|
|
320
|
-
|
|
321
322
|
/** Fired when the resolved selected option changes. */
|
|
322
323
|
'update:selectedOption': [
|
|
323
324
|
option: ComboboxSelectableOption | ComboboxCustomOption | null,
|
|
@@ -30,6 +30,22 @@ export interface ContextMenuProps {
|
|
|
30
30
|
open?: boolean
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
// Exported for consumers to import, and deliberately **not** passed to
|
|
34
|
+
// `defineEmits`. `update:open` is declared by `defineModel('open')` in the SFC;
|
|
35
|
+
// declaring it a second time through `defineEmits` makes Vue's generated
|
|
36
|
+
// `__VLS_ModelEmit & __VLS_Emit` an intersection of two tuples, which collapses
|
|
37
|
+
// `$emit` and every listener to `(...args: unknown[]) => any` — the defect
|
|
38
|
+
// #1098 removed from `Combobox` and `MultiSelect`.
|
|
39
|
+
//
|
|
40
|
+
// Those two are wired because they carry events no model declares (`focus`,
|
|
41
|
+
// `blur`, `update:selectedOption`); this component has none, so `defineEmits`
|
|
42
|
+
// would add nothing and cost the typed listener. `DropdownEmits`, `SelectEmits`,
|
|
43
|
+
// `SettingsDialogEmits`, `RatingEmits`, `DurationEmits` and `RadioGroupEmits`
|
|
44
|
+
// are unwired for the same reason.
|
|
45
|
+
export interface ContextMenuEmits {
|
|
46
|
+
'update:open': [open: boolean]
|
|
47
|
+
}
|
|
48
|
+
|
|
33
49
|
export type ContextMenuSlots = Omit<MenuSlots, 'default' | 'trigger'> & {
|
|
34
50
|
/** The right-clickable region that opens the menu. */
|
|
35
51
|
default?: (props: ContextMenuTriggerSlotProps) => any
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
const slotsData = [
|
|
18
18
|
{
|
|
19
19
|
name: 'rail',
|
|
20
|
-
description: 'The icon column — usually a `
|
|
20
|
+
description: 'The icon column — usually a `SidebarRail`. Omit it on shells that don\'t use one.',
|
|
21
21
|
type: 'any'
|
|
22
22
|
},
|
|
23
23
|
{
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
# DesktopShell
|
|
2
2
|
|
|
3
|
-
The desktop app frame. `DesktopShell` arranges an app's `
|
|
4
|
-
alongside the main content, and owns the skeleton the content needs:
|
|
5
|
-
`PageHeaderTarget` and a registered scroll region. Its mobile
|
|
6
|
-
separate family —
|
|
7
|
-
|
|
3
|
+
The desktop app frame. `DesktopShell` arranges an app's `SidebarRail` and
|
|
4
|
+
`Sidebar` alongside the main content, and owns the skeleton the content needs:
|
|
5
|
+
a pinned `PageHeaderTarget` and a registered scroll region. Its mobile
|
|
6
|
+
counterpart is a separate family —
|
|
7
|
+
[`MobileShell`](/docs/components/mobileshell) — because the two are different
|
|
8
|
+
navigation models, not one responsive component.
|
|
8
9
|
|
|
9
10
|
<ComponentPreview name="DesktopShell-Default" />
|
|
10
11
|
|
|
@@ -55,7 +55,7 @@ withDefaults(
|
|
|
55
55
|
)
|
|
56
56
|
|
|
57
57
|
defineSlots<{
|
|
58
|
-
/** The icon column — usually a `
|
|
58
|
+
/** The icon column — usually a `SidebarRail`. Omit it on shells that don't use one. */
|
|
59
59
|
rail?: () => any
|
|
60
60
|
/** The navigation panel — usually a `Sidebar`. Render it conditionally to hide it on routes that don't need it. */
|
|
61
61
|
sidebar?: () => any
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
import { ref } from 'vue'
|
|
3
3
|
import {
|
|
4
4
|
DesktopShell,
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
SidebarRail,
|
|
6
|
+
SidebarRailItem,
|
|
7
7
|
Sidebar,
|
|
8
8
|
SidebarItem,
|
|
9
9
|
SidebarLabel,
|
|
@@ -65,8 +65,8 @@ const posts = (
|
|
|
65
65
|
>
|
|
66
66
|
<DesktopShell>
|
|
67
67
|
<template #rail>
|
|
68
|
-
<
|
|
69
|
-
<
|
|
68
|
+
<SidebarRail class="border-r">
|
|
69
|
+
<SidebarRailItem
|
|
70
70
|
label="Home"
|
|
71
71
|
variant="ghost"
|
|
72
72
|
icon="lucide-house"
|
|
@@ -74,7 +74,7 @@ const posts = (
|
|
|
74
74
|
@click="community = ''"
|
|
75
75
|
/>
|
|
76
76
|
<div class="flex w-full flex-1 flex-col items-center gap-3 pt-3">
|
|
77
|
-
<
|
|
77
|
+
<SidebarRailItem
|
|
78
78
|
v-for="c in communities"
|
|
79
79
|
:key="c.id"
|
|
80
80
|
:label="c.id"
|
|
@@ -84,10 +84,10 @@ const posts = (
|
|
|
84
84
|
<span class="text-2xs-medium uppercase text-ink-gray-5">{{
|
|
85
85
|
c.initials
|
|
86
86
|
}}</span>
|
|
87
|
-
</
|
|
87
|
+
</SidebarRailItem>
|
|
88
88
|
</div>
|
|
89
|
-
<
|
|
90
|
-
</
|
|
89
|
+
<SidebarRailItem label="Search" variant="ghost" icon="lucide-search" />
|
|
90
|
+
</SidebarRail>
|
|
91
91
|
</template>
|
|
92
92
|
|
|
93
93
|
<template #sidebar>
|
|
@@ -7,27 +7,27 @@
|
|
|
7
7
|
const propsData = [
|
|
8
8
|
{
|
|
9
9
|
name: 'orientation',
|
|
10
|
-
description: '',
|
|
10
|
+
description: 'Which way the rule runs.',
|
|
11
11
|
required: false,
|
|
12
12
|
type: '"horizontal" | "vertical"',
|
|
13
13
|
default: '"horizontal"'
|
|
14
14
|
},
|
|
15
15
|
{
|
|
16
16
|
name: 'position',
|
|
17
|
-
description: '',
|
|
17
|
+
description: 'Where the action sits along the rule. Ignored without an `action`, since a\nplain rule has nothing to place.',
|
|
18
18
|
required: false,
|
|
19
19
|
type: '"start" | "center" | "end"',
|
|
20
20
|
default: '"center"'
|
|
21
21
|
},
|
|
22
22
|
{
|
|
23
23
|
name: 'flexItem',
|
|
24
|
-
description: '',
|
|
24
|
+
description: 'Stretches the divider to the cross-axis size of its flex parent\n(`align-self: stretch`) instead of taking the full height of its own box.\nUse it for a vertical divider between flex items of differing heights.',
|
|
25
25
|
required: false,
|
|
26
26
|
type: 'boolean'
|
|
27
27
|
},
|
|
28
28
|
{
|
|
29
29
|
name: 'action',
|
|
30
|
-
description: '',
|
|
30
|
+
description: 'Renders a button on the rule instead of a bare `<hr>`. The rule is drawn\nbehind the button and runs the full length of the divider.',
|
|
31
31
|
required: false,
|
|
32
32
|
type: 'DividerAction'
|
|
33
33
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { default as Divider } from './Divider.vue'
|
|
2
|
-
export type { DividerProps } from './types'
|
|
2
|
+
export type { DividerAction, DividerProps } from './types'
|
|
@@ -1,12 +1,35 @@
|
|
|
1
|
-
|
|
1
|
+
/** The inline action a `Divider` can host on its rule. */
|
|
2
|
+
export interface DividerAction {
|
|
3
|
+
/** Text of the action button. */
|
|
2
4
|
label: string
|
|
5
|
+
|
|
6
|
+
/** Called when the action button is clicked. */
|
|
3
7
|
onClick?: () => any
|
|
8
|
+
|
|
9
|
+
/** Shows a spinner in the action button and blocks further clicks. */
|
|
4
10
|
loading?: boolean
|
|
5
11
|
}
|
|
6
12
|
|
|
7
13
|
export interface DividerProps {
|
|
14
|
+
/** Which way the rule runs. */
|
|
8
15
|
orientation?: 'horizontal' | 'vertical'
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Where the action sits along the rule. Ignored without an `action`, since a
|
|
19
|
+
* plain rule has nothing to place.
|
|
20
|
+
*/
|
|
9
21
|
position?: 'start' | 'center' | 'end'
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Stretches the divider to the cross-axis size of its flex parent
|
|
25
|
+
* (`align-self: stretch`) instead of taking the full height of its own box.
|
|
26
|
+
* Use it for a vertical divider between flex items of differing heights.
|
|
27
|
+
*/
|
|
10
28
|
flexItem?: boolean
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Renders a button on the rule instead of a bare `<hr>`. The rule is drawn
|
|
32
|
+
* behind the button and runs the full length of the divider.
|
|
33
|
+
*/
|
|
11
34
|
action?: DividerAction
|
|
12
35
|
}
|
|
@@ -32,10 +32,10 @@ const knobs: Knob[] = [
|
|
|
32
32
|
type: 'tabs',
|
|
33
33
|
default: 'sm',
|
|
34
34
|
options: [
|
|
35
|
+
{ label: 'xs', value: 'xs' },
|
|
35
36
|
{ label: 'sm', value: 'sm' },
|
|
36
37
|
{ label: 'md', value: 'md' },
|
|
37
38
|
{ label: 'lg', value: 'lg' },
|
|
38
|
-
{ label: 'xl', value: 'xl' },
|
|
39
39
|
],
|
|
40
40
|
},
|
|
41
41
|
{
|
|
@@ -7,9 +7,9 @@ const value = ref<number | null>(5445)
|
|
|
7
7
|
|
|
8
8
|
<template>
|
|
9
9
|
<div class="flex flex-col gap-4 items-start">
|
|
10
|
+
<Duration v-model="value" size="xs" />
|
|
10
11
|
<Duration v-model="value" size="sm" />
|
|
11
12
|
<Duration v-model="value" size="md" />
|
|
12
13
|
<Duration v-model="value" size="lg" />
|
|
13
|
-
<Duration v-model="value" size="xl" />
|
|
14
14
|
</div>
|
|
15
15
|
</template>
|
|
@@ -32,9 +32,9 @@
|
|
|
32
32
|
},
|
|
33
33
|
{
|
|
34
34
|
name: 'size',
|
|
35
|
-
description: 'Size of the input',
|
|
35
|
+
description: 'Size of the input. The full input scale — `xs` 24px, `sm` 28px, `md` 32px,\n`lg` 40px. `type="checkbox"` renders on the narrower toggle scale, so `lg`\nis clamped to that scale\'s largest value there.',
|
|
36
36
|
required: false,
|
|
37
|
-
type: '
|
|
37
|
+
type: 'InputSize',
|
|
38
38
|
default: '"sm"'
|
|
39
39
|
},
|
|
40
40
|
{
|
|
@@ -247,4 +247,44 @@ describe('FormControl', () => {
|
|
|
247
247
|
})
|
|
248
248
|
})
|
|
249
249
|
})
|
|
250
|
+
describe('size scale', () => {
|
|
251
|
+
const heights = { xs: 24, sm: 28, md: 32, lg: 40 } as const
|
|
252
|
+
|
|
253
|
+
for (const [size, height] of Object.entries(heights)) {
|
|
254
|
+
it(`forwards size="${size}" to a text input as ${height}px`, () => {
|
|
255
|
+
cy.mount(FormControl, { props: { type: 'text', size, label: 'Email' } })
|
|
256
|
+
cy.get('input')
|
|
257
|
+
.should('have.attr', 'data-size', size)
|
|
258
|
+
.and('have.css', 'height', `${height}px`)
|
|
259
|
+
cy.get('label').should('have.css', 'font-size', '13px')
|
|
260
|
+
})
|
|
261
|
+
|
|
262
|
+
it(`forwards size="${size}" to a textarea`, () => {
|
|
263
|
+
cy.mount(FormControl, {
|
|
264
|
+
props: { type: 'textarea', size, label: 'Notes' },
|
|
265
|
+
})
|
|
266
|
+
cy.get('textarea')
|
|
267
|
+
.should('have.attr', 'data-size', size)
|
|
268
|
+
.and('have.css', 'font-size', '13px')
|
|
269
|
+
})
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
it('clamps lg to md for a checkbox, which has no lg', () => {
|
|
273
|
+
// Checkbox renders on `ToggleSize` (`xs | sm | md`). Forwarding `lg`
|
|
274
|
+
// would fall off the end of its size chain and draw an `xs` control.
|
|
275
|
+
cy.mount(FormControl, {
|
|
276
|
+
props: { type: 'checkbox', size: 'lg', label: 'Subscribe' },
|
|
277
|
+
})
|
|
278
|
+
cy.get('input[type="checkbox"]').should('have.attr', 'data-size', 'md')
|
|
279
|
+
})
|
|
280
|
+
|
|
281
|
+
it('forwards the narrower sizes to a checkbox unchanged', () => {
|
|
282
|
+
for (const size of ['xs', 'sm', 'md'] as const) {
|
|
283
|
+
cy.mount(FormControl, {
|
|
284
|
+
props: { type: 'checkbox', size, label: 'Subscribe' },
|
|
285
|
+
})
|
|
286
|
+
cy.get('input[type="checkbox"]').should('have.attr', 'data-size', size)
|
|
287
|
+
}
|
|
288
|
+
})
|
|
289
|
+
})
|
|
250
290
|
})
|
|
@@ -105,7 +105,12 @@ const forwardedAttrs = computed(() => {
|
|
|
105
105
|
if (key !== 'class' && key !== 'style') out[key] = attrs[key]
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
|
|
108
|
+
// Checkbox is a toggle, not a text input: it renders on `ToggleSize`
|
|
109
|
+
// (`xs | sm | md`), which has no `lg`. Clamp rather than forward a value the
|
|
110
|
+
// control cannot draw — `FormControl` advertises the whole input scale, so
|
|
111
|
+
// `size="lg"` has to mean something for every type it dispatches to.
|
|
112
|
+
out.size =
|
|
113
|
+
props.type === 'checkbox' && props.size === 'lg' ? 'md' : props.size
|
|
109
114
|
out.variant = props.variant
|
|
110
115
|
|
|
111
116
|
if (props.label !== undefined) out.label = props.label
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { TextInputTypes } from '../types/TextInput'
|
|
2
|
+
import type { InputSize } from '../../composables/inputTypes'
|
|
2
3
|
import type { FrappeUIError } from '../../composables/useInputLabeling'
|
|
3
4
|
|
|
4
5
|
export interface FormControlProps {
|
|
@@ -29,8 +30,12 @@ export interface FormControlProps {
|
|
|
29
30
|
| 'daterange'
|
|
30
31
|
| 'datetime'
|
|
31
32
|
| 'time'
|
|
32
|
-
/**
|
|
33
|
-
|
|
33
|
+
/**
|
|
34
|
+
* Size of the input. The full input scale — `xs` 24px, `sm` 28px, `md` 32px,
|
|
35
|
+
* `lg` 40px. `type="checkbox"` renders on the narrower toggle scale, so `lg`
|
|
36
|
+
* is clamped to that scale's largest value there.
|
|
37
|
+
*/
|
|
38
|
+
size?: InputSize
|
|
34
39
|
/** Visual variant of the input */
|
|
35
40
|
variant?: 'subtle' | 'outline'
|
|
36
41
|
/** Whether the input is required */
|
|
@@ -11,13 +11,6 @@
|
|
|
11
11
|
required: true,
|
|
12
12
|
type: 'string'
|
|
13
13
|
},
|
|
14
|
-
{
|
|
15
|
-
name: 'size',
|
|
16
|
-
description: 'Size of the label text.',
|
|
17
|
-
required: false,
|
|
18
|
-
type: '"sm" | "md"',
|
|
19
|
-
default: '"sm"'
|
|
20
|
-
},
|
|
21
14
|
{
|
|
22
15
|
name: 'id',
|
|
23
16
|
description: 'id of the form control this label is for; wired to the `for` attribute.',
|