@lowdefy/blocks-antd 5.0.0 → 5.2.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.
@@ -40,10 +40,16 @@ const AutoCompleteInput = ({ blockId, classNames = {}, components, events, loadi
40
40
  backfill: properties.backfill,
41
41
  variant: properties.bordered === false ? 'borderless' : properties.variant,
42
42
  className: classNames.element,
43
+ classNames: {
44
+ content: classNames.selector
45
+ },
43
46
  style: {
44
47
  width: '100%',
45
48
  ...styles.element
46
49
  },
50
+ styles: {
51
+ content: styles.selector
52
+ },
47
53
  defaultOpen: properties.defaultOpen,
48
54
  disabled: properties.disabled || loading,
49
55
  placeholder: properties.placeholder ?? 'Type or select item',
@@ -23,6 +23,7 @@ export default {
23
23
  valueType: 'string',
24
24
  cssKeys: {
25
25
  element: 'The AutoComplete element.',
26
+ selector: 'The inner value container of the AutoComplete (antd `content` semantic slot).',
26
27
  label: 'The AutoComplete label.',
27
28
  extra: 'The AutoComplete extra content.',
28
29
  feedback: 'The AutoComplete validation feedback.',
@@ -22,21 +22,58 @@ import withTheme from '../withTheme.js';
22
22
  import disabledDate from '../../disabledDate.js';
23
23
  dayjs.extend(utc);
24
24
  function buildDateMap(dateCellData) {
25
- if (!type.isArray(dateCellData)) return {};
25
+ if (!type.isArray(dateCellData)) return {
26
+ map: {},
27
+ earliest: undefined
28
+ };
26
29
  const map = {};
30
+ let earliest;
27
31
  dateCellData.forEach((item)=>{
28
32
  if (type.isNone(item?.date)) return;
29
- const key = dayjs(item.date).format('YYYY-MM-DD');
33
+ const parsed = dayjs(item.date);
34
+ if (!parsed.isValid()) return;
35
+ const key = parsed.format('YYYY-MM-DD');
30
36
  if (!map[key]) map[key] = [];
31
37
  map[key].push(item);
38
+ if (!earliest || parsed.isBefore(earliest)) earliest = parsed;
32
39
  });
33
- return map;
40
+ return {
41
+ map,
42
+ earliest
43
+ };
44
+ }
45
+ // Pick a default month to open on when `value` is unset: earliest event date
46
+ // from dateCellData, or the earliest specific disabled date, or the min of a
47
+ // disabledDates range. Without this, a calendar whose data lives in a past or
48
+ // future month opens on "today" and shows nothing, confusing the user.
49
+ function pickDefaultValue({ earliestCellData, disabledDates }) {
50
+ if (earliestCellData) return earliestCellData;
51
+ if (type.isObject(disabledDates)) {
52
+ if (type.isArray(disabledDates.dates) && disabledDates.dates.length > 0) {
53
+ let earliest;
54
+ disabledDates.dates.forEach((d)=>{
55
+ const parsed = dayjs(d);
56
+ if (parsed.isValid() && (!earliest || parsed.isBefore(earliest))) earliest = parsed;
57
+ });
58
+ if (earliest) return earliest;
59
+ }
60
+ if (!type.isNone(disabledDates.min)) {
61
+ const parsed = dayjs(disabledDates.min);
62
+ if (parsed.isValid()) return parsed;
63
+ }
64
+ }
65
+ return undefined;
34
66
  }
35
67
  const CalendarBlock = ({ blockId, classNames = {}, events, methods, properties, styles = {}, value })=>{
36
- const dateMap = buildDateMap(properties.dateCellData);
68
+ const { map: dateMap, earliest: earliestCellData } = buildDateMap(properties.dateCellData);
37
69
  const hasDateData = Object.keys(dateMap).length > 0;
38
- return /*#__PURE__*/ React.createElement(Calendar, {
39
- id: blockId,
70
+ const defaultPanel = pickDefaultValue({
71
+ earliestCellData,
72
+ disabledDates: properties.disabledDates
73
+ });
74
+ return /*#__PURE__*/ React.createElement("div", {
75
+ id: blockId
76
+ }, /*#__PURE__*/ React.createElement(Calendar, {
40
77
  className: classNames.element,
41
78
  style: styles.element,
42
79
  fullscreen: properties.fullscreen !== false,
@@ -47,21 +84,24 @@ const CalendarBlock = ({ blockId, classNames = {}, events, methods, properties,
47
84
  dayjs(properties.validRange[1])
48
85
  ] : undefined,
49
86
  value: type.isDate(value) ? dayjs(value) : undefined,
87
+ defaultValue: !type.isDate(value) ? defaultPanel : undefined,
50
88
  onSelect: (date, selectInfo)=>{
51
89
  // Wrap with our dayjs — antd v6's internal dayjs may lack the utc plugin.
52
90
  const d = dayjs(date);
53
91
  const val = d.toDate();
54
- if (selectInfo?.source === 'date') {
55
- methods.setValue(val);
56
- methods.triggerEvent({
57
- name: 'onSelect',
58
- event: {
59
- value: val,
60
- date: d.format('YYYY-MM-DD'),
61
- source: selectInfo.source
62
- }
63
- });
64
- }
92
+ const source = selectInfo?.source ?? 'date';
93
+ // Only fire for date cell clicks — year/month panel navigation emits
94
+ // onSelect too but shouldn't push a value through the event chain.
95
+ if (source !== 'date' && source !== 'customize') return;
96
+ methods.setValue(val);
97
+ methods.triggerEvent({
98
+ name: 'onSelect',
99
+ event: {
100
+ value: val,
101
+ date: d.format('YYYY-MM-DD'),
102
+ source
103
+ }
104
+ });
65
105
  },
66
106
  onChange: (date)=>{
67
107
  const d = dayjs(date);
@@ -90,6 +130,8 @@ const CalendarBlock = ({ blockId, classNames = {}, events, methods, properties,
90
130
  if (info.type !== 'date') return info.originNode;
91
131
  const key = dayjs(current).format('YYYY-MM-DD');
92
132
  const items = dateMap[key];
133
+ // cellRender output is appended to the default date cell — return
134
+ // null for days with no events to avoid double-rendering the date.
93
135
  if (!items) return null;
94
136
  return /*#__PURE__*/ React.createElement("ul", {
95
137
  className: "ant-picker-calendar-events",
@@ -106,6 +148,6 @@ const CalendarBlock = ({ blockId, classNames = {}, events, methods, properties,
106
148
  text: item.content
107
149
  }))));
108
150
  } : undefined
109
- });
151
+ }));
110
152
  };
111
153
  export default withTheme('Calendar', withBlockDefaults(CalendarBlock));
@@ -14,7 +14,7 @@
14
14
  limitations under the License.
15
15
  */ import disabledDates from '../../schemas/disabledDates.js';
16
16
  export default {
17
- category: 'display',
17
+ category: 'input',
18
18
  icons: [],
19
19
  valueType: 'date',
20
20
  cssKeys: {
@@ -15,10 +15,11 @@
15
15
  */ import React, { useEffect } from 'react';
16
16
  import { get } from '@lowdefy/helpers';
17
17
  import { List, Typography } from 'antd';
18
- import { withBlockDefaults } from '@lowdefy/block-utils';
18
+ import { cn, withBlockDefaults } from '@lowdefy/block-utils';
19
19
  import Button from '../Button/Button.js';
20
20
  import withTheme from '../withTheme.js';
21
- const ControlledListBlock = ({ blockId, classNames = {}, components: { Icon, Link, ShortcutBadge }, events, list, methods, properties, styles = {} })=>{
21
+ import './style.module.css';
22
+ const ControlledListBlock = ({ blockId, classNames = {}, components: { Icon, Link, ShortcutBadge }, events, list, methods, properties, styles = {}, value = [] })=>{
22
23
  useEffect(()=>{
23
24
  methods.registerMethod('moveItemDown', methods.moveItemDown);
24
25
  methods.registerMethod('moveItemUp', methods.moveItemUp);
@@ -31,6 +32,38 @@ const ControlledListBlock = ({ blockId, classNames = {}, components: { Icon, Lin
31
32
  methods.pushItem({});
32
33
  }
33
34
  }
35
+ const addItemToFront = ()=>{
36
+ methods.unshiftItem();
37
+ methods.triggerEvent({
38
+ name: 'onAdd',
39
+ event: {
40
+ index: 0,
41
+ item: undefined
42
+ }
43
+ });
44
+ };
45
+ const addItemToBack = ()=>{
46
+ const index = value.length;
47
+ methods.pushItem();
48
+ methods.triggerEvent({
49
+ name: 'onAdd',
50
+ event: {
51
+ index,
52
+ item: undefined
53
+ }
54
+ });
55
+ };
56
+ const removeItemAt = (index)=>{
57
+ const item = value[index];
58
+ methods.removeItem(index);
59
+ methods.triggerEvent({
60
+ name: 'onRemove',
61
+ event: {
62
+ index,
63
+ item
64
+ }
65
+ });
66
+ };
34
67
  return /*#__PURE__*/ React.createElement(List, {
35
68
  id: blockId,
36
69
  className: classNames.element,
@@ -61,7 +94,7 @@ const ControlledListBlock = ({ blockId, classNames = {}, components: { Icon, Lin
61
94
  type: 'default',
62
95
  ...properties.addItemButton
63
96
  },
64
- onClick: ()=>methods.unshiftItem()
97
+ onClick: addItemToFront
65
98
  })),
66
99
  footer: !properties.addToFront && !properties.hideAddButton && /*#__PURE__*/ React.createElement("div", {
67
100
  style: {
@@ -86,7 +119,7 @@ const ControlledListBlock = ({ blockId, classNames = {}, components: { Icon, Lin
86
119
  type: 'dashed',
87
120
  ...properties.addItemButton
88
121
  },
89
- onClick: ()=>methods.pushItem()
122
+ onClick: addItemToBack
90
123
  })),
91
124
  bordered: true,
92
125
  locale: {
@@ -102,24 +135,16 @@ const ControlledListBlock = ({ blockId, classNames = {}, components: { Icon, Lin
102
135
  extra: !properties.hideRemoveButton && list.length > (properties.minItems ?? 0) && [
103
136
  // eslint-disable-next-line react/jsx-key
104
137
  /*#__PURE__*/ React.createElement("span", {
105
- style: {
106
- paddingLeft: properties.size === 'small' ? 2 : properties.size === 'large' ? 6 : 4,
107
- fontSize: properties.size === 'small' ? 16 : properties.size === 'large' ? 20 : 18
108
- }
138
+ className: cn('lf-controlled-list-remove', classNames.removeIcon),
139
+ style: styles.removeIcon
109
140
  }, /*#__PURE__*/ React.createElement(Icon, {
110
141
  blockId: `${blockId}_${i}_remove_icon`,
111
- classNames: {
112
- element: classNames.removeIcon
113
- },
114
142
  events: events,
115
143
  properties: {
116
144
  name: 'AiOutlineMinusCircle',
117
145
  ...properties.removeItemIcon
118
146
  },
119
- styles: {
120
- element: styles.removeIcon
121
- },
122
- onClick: ()=>methods.removeItem(i)
147
+ onClick: ()=>removeItemAt(i)
123
148
  }))
124
149
  ]
125
150
  }, item.content && item.content({
@@ -22,12 +22,16 @@
22
22
  slots: {
23
23
  content: 'Blocks rendered for each list item.'
24
24
  },
25
+ events: {
26
+ onAdd: 'Triggered after a new item is added via the add button. The event payload is `{ index, item }`, where `item` is the newly added value (`undefined` for an empty row).',
27
+ onRemove: 'Triggered after an item is removed via the remove icon. The event payload is `{ index, item }`, where `item` is the removed value captured before removal.'
28
+ },
25
29
  cssKeys: {
26
30
  element: 'The ControlledList element.',
27
31
  header: 'The ControlledList header.',
28
32
  footer: 'The ControlledList footer.',
29
33
  item: 'The ControlledList item.',
30
- removeIcon: 'The remove item icon in the ControlledList.'
34
+ removeIcon: 'The remove-item icon wrapper. Defaults to the antd error color at `fontSizeLG`; override `color`, `font-size`, or spacing here.'
31
35
  },
32
36
  properties: {
33
37
  type: 'object',
@@ -69,7 +73,7 @@
69
73
  'string',
70
74
  'object'
71
75
  ],
72
- description: 'Custom remove item icon properties.',
76
+ description: 'Custom remove item icon properties. Defaults to `AiOutlineMinusCircle` at a standard size with the antd error color inherited from the icon wrapper — override via `class.removeIcon` / `style.removeIcon` for visual tweaks, or via this property to change the icon name itself.',
73
77
  docs: {
74
78
  displayType: 'icon'
75
79
  }
@@ -0,0 +1,35 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ @layer components {
18
+ :global(.lf-controlled-list-remove) {
19
+ display: inline-flex;
20
+ align-items: center;
21
+ padding-left: var(--ant-padding-xs);
22
+ font-size: var(--ant-font-size-lg);
23
+ color: var(--ant-color-error);
24
+ cursor: pointer;
25
+ transition: color 0.2s;
26
+ }
27
+
28
+ :global(.lf-controlled-list-remove:hover) {
29
+ color: var(--ant-color-error-hover);
30
+ }
31
+
32
+ :global(.lf-controlled-list-remove:active) {
33
+ color: var(--ant-color-error-active);
34
+ }
35
+ }
@@ -112,6 +112,7 @@ const DrawerBlock = ({ blockId, classNames = {}, content, properties, methods, r
112
112
  id: blockId,
113
113
  closable: properties.closable,
114
114
  extra: content.extra && content.extra(),
115
+ footer: content.footer && content.footer(),
115
116
  getContainer: properties.getContainer,
116
117
  mask: properties.mask,
117
118
  maskClosable: properties.maskClosable,
@@ -145,6 +146,7 @@ const DrawerBlock = ({ blockId, classNames = {}, content, properties, methods, r
145
146
  styles: {
146
147
  header: styles.header,
147
148
  body: styles.body,
149
+ footer: styles.footer,
148
150
  mask: styles.mask,
149
151
  wrapper: styles.wrapper,
150
152
  content: styles.content
@@ -18,7 +18,8 @@
18
18
  valueType: null,
19
19
  slots: {
20
20
  content: 'Main Drawer body.',
21
- extra: 'Extra content in the header.'
21
+ extra: 'Extra content in the header.',
22
+ footer: 'The Drawer footer.'
22
23
  },
23
24
  cssKeys: {
24
25
  element: 'The Drawer element.',
@@ -62,10 +62,12 @@ function DropdownButtonBlock({ blockId, classNames = {}, components: { Icon, Sho
62
62
  key: `divider-${i}`
63
63
  };
64
64
  }
65
+ const eventShortcut = item.eventName ? events[item.eventName]?.shortcut : undefined;
66
+ const itemShortcut = eventShortcut ?? item.shortcut;
65
67
  return {
66
68
  key: item.eventName ?? `item-${i}`,
67
- label: /*#__PURE__*/ React.createElement("span", null, item.title, item.shortcut && /*#__PURE__*/ React.createElement(ShortcutBadge, {
68
- shortcut: item.shortcut
69
+ label: /*#__PURE__*/ React.createElement("span", null, item.title, itemShortcut && /*#__PURE__*/ React.createElement(ShortcutBadge, {
70
+ shortcut: itemShortcut
69
71
  })),
70
72
  icon: item.icon ? /*#__PURE__*/ React.createElement(Icon, {
71
73
  blockId: `${blockId}_icon_${i}`,
@@ -82,7 +84,10 @@ function DropdownButtonBlock({ blockId, classNames = {}, components: { Icon, Sho
82
84
  disabled: item.disabled
83
85
  };
84
86
  });
85
- const shortcutItems = (properties.items ?? []).filter((item)=>item.shortcut && item.eventName && !item.disabled).map((item)=>({
87
+ // Item-level shortcut fallback for items that declare `shortcut` on properties
88
+ // rather than via `events.<eventName>.shortcut`. Skip items whose event already
89
+ // owns the shortcut — the framework-level shortcut manager handles those.
90
+ const propertyShortcutItems = (properties.items ?? []).filter((item)=>item.shortcut && item.eventName && !item.disabled && !events[item.eventName]?.shortcut).map((item)=>({
86
91
  key: item.eventName,
87
92
  shortcut: item.shortcut
88
93
  }));
@@ -94,12 +99,13 @@ function DropdownButtonBlock({ blockId, classNames = {}, components: { Icon, Sho
94
99
  methods
95
100
  ]);
96
101
  useItemShortcuts({
97
- items: shortcutItems,
102
+ items: propertyShortcutItems,
98
103
  onMatch: onShortcutMatch
99
104
  });
100
105
  const onClickActionName = get(rename, 'events.onClick', {
101
106
  default: 'onClick'
102
107
  });
108
+ const onClickShortcut = events[onClickActionName]?.shortcut;
103
109
  const dropdownProps = {
104
110
  menu: {
105
111
  items,
@@ -162,7 +168,9 @@ function DropdownButtonBlock({ blockId, classNames = {}, components: { Icon, Sho
162
168
  onClick: ()=>methods.triggerEvent({
163
169
  name: onClickActionName
164
170
  })
165
- }, properties.title), /*#__PURE__*/ React.createElement(Dropdown, dropdownProps, /*#__PURE__*/ React.createElement(Button, {
171
+ }, properties.title, onClickShortcut && /*#__PURE__*/ React.createElement(ShortcutBadge, {
172
+ shortcut: onClickShortcut
173
+ })), /*#__PURE__*/ React.createElement(Dropdown, dropdownProps, /*#__PURE__*/ React.createElement(Button, {
166
174
  color: resolvedColor,
167
175
  variant: variant,
168
176
  type: buttonType,
@@ -26,7 +26,7 @@
26
26
  arrow: 'Dropdown arrow indicator.'
27
27
  },
28
28
  events: {
29
- onClick: 'Trigger action when the button is clicked (split mode).',
29
+ onClick: 'Trigger action when the button is clicked (split mode). Renders a shortcut badge when a shortcut is configured on the event.',
30
30
  onOpenChange: 'Trigger action when dropdown opens or closes.'
31
31
  },
32
32
  properties: {
@@ -162,7 +162,7 @@
162
162
  },
163
163
  items: {
164
164
  type: 'array',
165
- description: 'Menu items. Each with an eventName that triggers a named event.',
165
+ description: 'Menu items. Each with an eventName that triggers a named event. Keyboard shortcuts can be configured via the standard `events.<eventName>.shortcut` schema (preferred) or via the item-level `shortcut` property — both render a badge next to the item label. The event-level shortcut takes precedence when both are set.',
166
166
  items: {
167
167
  type: 'object',
168
168
  properties: {
@@ -201,7 +201,7 @@
201
201
  },
202
202
  shortcut: {
203
203
  type: 'string',
204
- description: 'Keyboard shortcut. Binds the key and renders the badge.'
204
+ description: 'Keyboard shortcut. Binds the key and renders the badge. Prefer configuring this via `events.<eventName>.shortcut` to follow the standard Lowdefy event schema — the event-level shortcut takes precedence when both are set.'
205
205
  }
206
206
  }
207
207
  }
@@ -12,9 +12,10 @@
12
12
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
13
  See the License for the specific language governing permissions and
14
14
  limitations under the License.
15
- */ import React, { useCallback } from 'react';
16
- import { Menu } from 'antd';
15
+ */ import React, { useCallback, useContext } from 'react';
16
+ import { Layout, Menu } from 'antd';
17
17
  import { type, get } from '@lowdefy/helpers';
18
+ const SiderContext = Layout._InternalSiderContext;
18
19
  import { withBlockDefaults } from '@lowdefy/block-utils';
19
20
  import withTheme from '../withTheme.js';
20
21
  import useItemShortcuts from '../useItemShortcuts.js';
@@ -133,6 +134,8 @@ function MenuComp({ blockId, classNames = {}, components: { Icon, Link, Shortcut
133
134
  }
134
135
  const menu = getDefaultMenu(menus, properties.menuId, properties.links);
135
136
  const theme = properties.theme;
137
+ const { siderCollapsed } = useContext(SiderContext) ?? {};
138
+ const isCollapsed = properties.collapsed === true || siderCollapsed === true;
136
139
  const items = buildMenuItems({
137
140
  links: menu,
138
141
  events,
@@ -185,7 +188,7 @@ function MenuComp({ blockId, classNames = {}, components: { Icon, Link, Shortcut
185
188
  mode: properties.mode,
186
189
  selectable: true,
187
190
  theme: theme,
188
- defaultOpenKeys: properties.defaultOpenKeys ?? (properties.mode === 'inline' && properties.collapsed !== true && [
191
+ defaultOpenKeys: properties.defaultOpenKeys ?? (properties.mode === 'inline' && !isCollapsed && [
189
192
  (menu.find((link)=>(link.links || []).map((subLink)=>subLink.links ? subLink.links.map((subSubLink)=>subSubLink.pageId) : [
190
193
  subLink.pageId
191
194
  ]).flat().some((link)=>(properties.selectedKeys ?? [
@@ -18,7 +18,8 @@ import { withBlockDefaults } from '@lowdefy/block-utils';
18
18
  import Button from '../Button/Button.js';
19
19
  import Drawer from '../Drawer/Drawer.js';
20
20
  import Menu from '../Menu/Menu.js';
21
- const MobileMenu = ({ basePath, blockId, classNames = {}, components, events, methods, menus, pageId, properties, rename, styles = {} })=>{
21
+ import { getDarkMode } from '../headerActions.js';
22
+ const MobileMenu = ({ basePath, blockId, classNames = {}, components, content, events, methods, menus, pageId, properties, rename, styles = {} })=>{
22
23
  const [openState, setOpen] = useState(false);
23
24
  useEffect(()=>{
24
25
  methods.registerMethod(get(rename, 'methods.toggleOpen', {
@@ -88,7 +89,18 @@ const MobileMenu = ({ basePath, blockId, classNames = {}, components, events, me
88
89
  default: 'toggleOpen'
89
90
  })](),
90
91
  content: {
91
- content: ()=>/*#__PURE__*/ React.createElement(Menu, {
92
+ extra: properties.logo ? ()=>/*#__PURE__*/ React.createElement("div", {
93
+ style: {
94
+ flex: '1 0 auto'
95
+ }
96
+ }, /*#__PURE__*/ React.createElement(components.Link, {
97
+ home: true
98
+ }, /*#__PURE__*/ React.createElement("img", {
99
+ src: properties.logo?.srcMobile ?? properties.logo?.src ?? `${basePath}/logo-square-${getDarkMode() ? 'dark' : 'light'}-theme.png`,
100
+ alt: properties.logo?.alt ?? 'Lowdefy',
101
+ style: properties.logo?.style
102
+ }))) : undefined,
103
+ content: ()=>/*#__PURE__*/ React.createElement(React.Fragment, null, /*#__PURE__*/ React.createElement(Menu, {
92
104
  basePath: basePath,
93
105
  components: components,
94
106
  blockId: `${blockId}_menu`,
@@ -113,7 +125,8 @@ const MobileMenu = ({ basePath, blockId, classNames = {}, components, events, me
113
125
  onSelect: 'onMenuItemSelect'
114
126
  }
115
127
  }
116
- })
128
+ }), content?.drawerContent && content.drawerContent()),
129
+ footer: content?.drawerFooter ? ()=>content.drawerFooter() : undefined
117
130
  }
118
131
  }));
119
132
  };
@@ -13,12 +13,16 @@
13
13
  See the License for the specific language governing permissions and
14
14
  limitations under the License.
15
15
  */ export default {
16
- category: 'display',
16
+ category: 'container',
17
17
  icons: [
18
18
  'AiOutlineMenuUnfold',
19
19
  'AiOutlineMenuFold'
20
20
  ],
21
21
  valueType: null,
22
+ slots: {
23
+ drawerContent: 'Additional content below the menu in the drawer.',
24
+ drawerFooter: 'Footer content in the drawer.'
25
+ },
22
26
  cssKeys: {
23
27
  element: 'The MobileMenu element.'
24
28
  },
@@ -48,6 +52,33 @@
48
52
  displayType: 'yaml'
49
53
  }
50
54
  },
55
+ logo: {
56
+ type: 'object',
57
+ description: 'Logo settings for the mobile menu drawer header.',
58
+ additionalProperties: false,
59
+ properties: {
60
+ src: {
61
+ type: 'string',
62
+ description: 'Logo source url.'
63
+ },
64
+ srcMobile: {
65
+ type: 'string',
66
+ description: 'Mobile logo source url.'
67
+ },
68
+ alt: {
69
+ type: 'string',
70
+ default: 'Lowdefy',
71
+ description: 'Logo alternative text.'
72
+ },
73
+ style: {
74
+ type: 'object',
75
+ description: 'Css style object to apply to logo.',
76
+ docs: {
77
+ displayType: 'yaml'
78
+ }
79
+ }
80
+ }
81
+ },
51
82
  menuId: {
52
83
  type: 'string',
53
84
  description: 'App menu id used to get menu links.'
@@ -41,7 +41,7 @@ const tagRender = (props, option, methods, components)=>{
41
41
  }
42
42
  });
43
43
  };
44
- const MultipleSelector = ({ blockId, classNames = {}, components: { Icon }, events, loading, methods, properties, required, styles = {}, validation, value })=>{
44
+ const MultipleSelector = ({ blockId, classNames = {}, components: { Icon, ShortcutBadge }, events, loading, methods, properties, required, styles = {}, validation, value })=>{
45
45
  const [fetchState, setFetch] = useState(false);
46
46
  const [elementId] = useState((0 | Math.random() * 9e2) + 1e2);
47
47
  const uniqueValueOptions = getUniqueValues(properties.options ?? []);
@@ -73,15 +73,22 @@ const MultipleSelector = ({ blockId, classNames = {}, components: { Icon }, even
73
73
  autoFocus: properties.autoFocus,
74
74
  variant: properties.bordered === false ? 'borderless' : properties.variant,
75
75
  className: classNames.element,
76
+ classNames: {
77
+ content: classNames.selector
78
+ },
76
79
  style: {
77
80
  width: '100%',
78
81
  ...styles.element
79
82
  },
83
+ styles: {
84
+ content: styles.selector
85
+ },
80
86
  disabled: properties.disabled || loading,
81
87
  getPopupContainer: ()=>document.getElementById(`${blockId}_${elementId}_popup`),
82
88
  mode: "multiple",
83
89
  tagRender: properties.renderTags && ((props)=>tagRender(props, uniqueValueOptions[props.value], methods, {
84
- Icon
90
+ Icon,
91
+ ShortcutBadge
85
92
  })),
86
93
  maxTagCount: properties.maxTagCount,
87
94
  notFoundContent: fetchState ? properties.loadingPlaceholder || 'Loading' : properties.notFoundContent || 'Not found',
@@ -27,6 +27,7 @@ export default {
27
27
  valueType: 'array',
28
28
  cssKeys: {
29
29
  element: 'The MultipleSelector element.',
30
+ selector: 'The inner tag/value container of the MultipleSelector (antd `content` semantic slot). Use for capping the tag area height and enabling internal scroll.',
30
31
  clearIcon: 'The clear icon in the MultipleSelector.',
31
32
  label: 'The MultipleSelector label.',
32
33
  extra: 'The MultipleSelector extra content.',