@lowdefy/blocks-antd 5.5.0 → 5.6.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.
@@ -13,7 +13,7 @@
13
13
  See the License for the specific language governing permissions and
14
14
  limitations under the License.
15
15
  */ import React from 'react';
16
- import { Checkbox, ConfigProvider, Space } from 'antd';
16
+ import { Checkbox, Col, ConfigProvider, Row, Space, theme } from 'antd';
17
17
  import { type } from '@lowdefy/helpers';
18
18
  import { renderHtml, withBlockDefaults } from '@lowdefy/block-utils';
19
19
  import Label from '../Label/Label.js';
@@ -21,6 +21,7 @@ import getSelectedIndex from '../../getSelectedIndex.js';
21
21
  import useSelectorOptions from '../../useSelectorOptions.js';
22
22
  import withTheme from '../withTheme.js';
23
23
  const CheckboxSelector = ({ blockId, classNames = {}, components, events, loading, properties, required, styles = {}, validation, value, methods })=>{
24
+ const { token } = theme.useToken();
24
25
  const uniqueValueOptions = useSelectorOptions({
25
26
  properties,
26
27
  methods
@@ -29,33 +30,17 @@ const CheckboxSelector = ({ blockId, classNames = {}, components, events, loadin
29
30
  properties,
30
31
  multiple: true
31
32
  }));
32
- const checkboxGroup = /*#__PURE__*/ React.createElement(Checkbox.Group, {
33
- id: `${blockId}_input`,
34
- className: classNames.element,
35
- disabled: properties.disabled || loading,
36
- style: styles.element,
37
- onChange: (newVal)=>{
38
- const val = [];
39
- newVal.forEach((nv)=>{
40
- val.push(type.isPrimitive(uniqueValueOptions[nv]) ? uniqueValueOptions[nv] : uniqueValueOptions[nv].value);
41
- });
42
- methods.setValue(val);
43
- methods.triggerEvent({
44
- name: 'onChange',
45
- event: {
46
- value: val
47
- }
48
- });
49
- },
50
- value: getSelectedIndex(value, uniqueValueOptions, {
51
- properties,
52
- multiple: true
53
- })
54
- }, /*#__PURE__*/ React.createElement(Space, {
55
- direction: properties.direction,
56
- wrap: type.isNone(properties.wrap) ? true : properties.wrap,
57
- align: type.isNone(properties.align) ? 'start' : properties.align
58
- }, uniqueValueOptions.map((opt, i)=>{
33
+ const grid = !type.isNone(properties.columns);
34
+ // Col takes spans, not counts, so a breakpoint map of counts converts per key.
35
+ const colProps = type.isObject(properties.columns) ? Object.fromEntries(Object.entries(properties.columns).map(([breakpoint, columns])=>[
36
+ breakpoint,
37
+ {
38
+ span: 24 / columns
39
+ }
40
+ ])) : {
41
+ span: 24 / properties.columns
42
+ };
43
+ const renderOption = (opt, i)=>{
59
44
  if (type.isPrimitive(opt)) {
60
45
  return /*#__PURE__*/ React.createElement(Checkbox, {
61
46
  id: `${blockId}_${i}`,
@@ -95,7 +80,47 @@ const CheckboxSelector = ({ blockId, classNames = {}, components, events, loadin
95
80
  }
96
81
  }
97
82
  }, checkbox);
98
- })));
83
+ };
84
+ const checkboxGroup = /*#__PURE__*/ React.createElement(Checkbox.Group, {
85
+ id: `${blockId}_input`,
86
+ className: classNames.element,
87
+ disabled: properties.disabled || loading,
88
+ // Checkbox.Group is inline-flex and shrink-wraps, so the Row inside it can
89
+ // only fill a group that has been given a width.
90
+ style: grid ? {
91
+ width: '100%',
92
+ ...styles.element
93
+ } : styles.element,
94
+ onChange: (newVal)=>{
95
+ const val = [];
96
+ newVal.forEach((nv)=>{
97
+ val.push(type.isPrimitive(uniqueValueOptions[nv]) ? uniqueValueOptions[nv] : uniqueValueOptions[nv].value);
98
+ });
99
+ methods.setValue(val);
100
+ methods.triggerEvent({
101
+ name: 'onChange',
102
+ event: {
103
+ value: val
104
+ }
105
+ });
106
+ },
107
+ value: getSelectedIndex(value, uniqueValueOptions, {
108
+ properties,
109
+ multiple: true
110
+ })
111
+ }, grid ? /*#__PURE__*/ React.createElement(Row, {
112
+ gutter: type.isNone(properties.gutter) ? [
113
+ token.paddingXS,
114
+ 0
115
+ ] : properties.gutter
116
+ }, uniqueValueOptions.map((opt, i)=>/*#__PURE__*/ React.createElement(Col, {
117
+ key: i,
118
+ ...colProps
119
+ }, renderOption(opt, i)))) : /*#__PURE__*/ React.createElement(Space, {
120
+ direction: properties.direction,
121
+ wrap: type.isNone(properties.wrap) ? true : properties.wrap,
122
+ align: type.isNone(properties.align) ? 'start' : properties.align
123
+ }, uniqueValueOptions.map(renderOption)));
99
124
  return /*#__PURE__*/ React.createElement(Label, {
100
125
  blockId: blockId,
101
126
  methods: methods,
@@ -51,7 +51,7 @@ export default {
51
51
  'baseline'
52
52
  ],
53
53
  default: 'start',
54
- description: 'Align options.'
54
+ description: "Align options. Ignored when 'columns' is set."
55
55
  },
56
56
  color: {
57
57
  type: 'string',
@@ -60,6 +60,16 @@ export default {
60
60
  displayType: 'color'
61
61
  }
62
62
  },
63
+ columns: {
64
+ type: [
65
+ 'integer',
66
+ 'object'
67
+ ],
68
+ description: 'Number of columns to lay the options out in, or a responsive breakpoint object. Use a count that divides 24 evenly.',
69
+ docs: {
70
+ displayType: 'yaml'
71
+ }
72
+ },
63
73
  disabled,
64
74
  direction: {
65
75
  type: 'string',
@@ -68,12 +78,22 @@ export default {
68
78
  'vertical'
69
79
  ],
70
80
  default: 'horizontal',
71
- description: 'List options horizontally or vertical.'
81
+ description: "List options horizontally or vertical. Ignored when 'columns' is set."
82
+ },
83
+ gutter: {
84
+ type: [
85
+ 'number',
86
+ 'array'
87
+ ],
88
+ description: "Gap between options in the grid. Number or [horizontal, vertical] array. Applies when 'columns' is set.",
89
+ docs: {
90
+ displayType: 'yaml'
91
+ }
72
92
  },
73
93
  wrap: {
74
94
  type: 'boolean',
75
95
  default: true,
76
- description: "Specifies wrapping of options. Applies when 'direction' is 'horizontal'."
96
+ description: "Specifies wrapping of options. Applies when 'direction' is 'horizontal'. Ignored when 'columns' is set."
77
97
  },
78
98
  options,
79
99
  data,
@@ -142,7 +162,7 @@ export default {
142
162
  marginXS: {
143
163
  type: 'number',
144
164
  default: 8,
145
- description: 'Horizontal gap between checkboxes in a group.'
165
+ description: "Column gap on the checkbox group element. Does not space the options apart; set 'gutter' for a grid."
146
166
  },
147
167
  paddingXS: {
148
168
  type: 'number',
@@ -28,7 +28,7 @@ const validationKeyMap = {
28
28
  };
29
29
  let iconMap;
30
30
  const Label = ({ blockId, classNames: blockClassNames = {}, components: { Icon }, content, methods, properties, required, styles = {}, validation })=>{
31
- const { extraClassName, extraStyle, feedbackClassName, feedbackStyle, iconClassName, label, labelClassName, labelCol, labelColClassName, labelColStyle, labelStyle, rowClassName, rowStyle, showExtra, showFeedback, wrapperCol } = labelLogic({
31
+ const { extraClassName, extraStyle, feedbackClassName, feedbackStyle, iconClassName, label, labelClassName, labelCol, labelColClassName, labelColStyle, labelStyle, rowClassName, rowStyle, showExtra, showFeedback, showFeedbackIcon, wrapperCol } = labelLogic({
32
32
  blockId,
33
33
  blockClassNames,
34
34
  content,
@@ -53,8 +53,8 @@ const Label = ({ blockId, classNames: blockClassNames = {}, components: { Icon }
53
53
  })
54
54
  };
55
55
  }
56
- const IconNode = validation.status && iconMap[validation.status];
57
- const icon = validation.status && IconNode ? /*#__PURE__*/ React.createElement("span", {
56
+ const IconNode = showFeedbackIcon && iconMap[validation.status];
57
+ const icon = IconNode ? /*#__PURE__*/ React.createElement("span", {
58
58
  className: iconClassName
59
59
  }, /*#__PURE__*/ React.createElement(IconNode, null)) : null;
60
60
  // tooltip is either a string (the tooltip text) or an object that also
@@ -78,8 +78,10 @@ const labelLogic = ({ blockId, blockClassNames = {}, content, properties = {}, r
78
78
  'ant-form-item-feedback-icon-validating': validation.status === 'validating',
79
79
  'ldf-feedback-icon': true
80
80
  });
81
+ const hasFeedback = properties.hasFeedback !== false;
81
82
  const showExtra = !!properties.extra && (!validation.status || validation.status === 'success');
82
- const showFeedback = validation.status === 'warning' || validation.status === 'error';
83
+ const showFeedback = hasFeedback && (validation.status === 'warning' || validation.status === 'error');
84
+ const showFeedbackIcon = hasFeedback && !type.isNone(validation.status);
83
85
  return {
84
86
  extraClassName,
85
87
  extraStyle,
@@ -96,6 +98,7 @@ const labelLogic = ({ blockId, blockClassNames = {}, content, properties = {}, r
96
98
  rowStyle,
97
99
  showExtra,
98
100
  showFeedback,
101
+ showFeedbackIcon,
99
102
  wrapperCol
100
103
  };
101
104
  };
@@ -20,6 +20,7 @@ import { withBlockDefaults } from '@lowdefy/block-utils';
20
20
  import withTheme from '../withTheme.js';
21
21
  import useItemShortcuts from '../useItemShortcuts.js';
22
22
  import { buildMenuItems } from '../buildMenuItems.js';
23
+ import './style.module.css';
23
24
  const getDefaultMenu = (menus, menuId = 'default', links)=>{
24
25
  if (type.isArray(links)) return links;
25
26
  if (!type.isArray(menus)) return [];
@@ -0,0 +1,26 @@
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
+ /* Cap side-opening flyouts (collapsed sidebar) to half the viewport so a long popup near a
19
+ low trigger stays on-screen and scrolls (50vh always fits the taller side of the trigger).
20
+ !important beats antd's unlayered, more-specific cap. */
21
+ :global(.ant-menu-submenu-popup[class*='-placement-right'] .ant-menu-sub),
22
+ :global(.ant-menu-submenu-popup[class*='-placement-left'] .ant-menu-sub) {
23
+ max-height: 50vh !important;
24
+ overflow-y: auto !important;
25
+ }
26
+ }
@@ -187,7 +187,13 @@ const PageSidebarLayout = ({ basePath, blockId, classNames = {}, components: { I
187
187
  onClick: 'onToggleSider'
188
188
  }
189
189
  }
190
- }), /*#__PURE__*/ React.createElement(Menu, {
190
+ }), /*#__PURE__*/ React.createElement("div", {
191
+ style: {
192
+ flex: '1 1 auto',
193
+ minHeight: 0,
194
+ overflowY: 'auto'
195
+ }
196
+ }, /*#__PURE__*/ React.createElement(Menu, {
191
197
  blockId: `${blockId}_menu`,
192
198
  components: {
193
199
  Icon,
@@ -228,16 +234,11 @@ const PageSidebarLayout = ({ basePath, blockId, classNames = {}, components: { I
228
234
  style: {
229
235
  flex: '0 0 auto'
230
236
  }
231
- }, content.siderClosed()), /*#__PURE__*/ React.createElement("div", {
232
- style: {
233
- flex: '1 0 auto'
234
- }
235
- }), /*#__PURE__*/ React.createElement("div", {
237
+ }, content.siderClosed())), /*#__PURE__*/ React.createElement("div", {
236
238
  style: {
237
- position: 'sticky',
238
- bottom: 0,
239
- background: 'linear-gradient(to bottom, transparent 0%, color-mix(in srgb, var(--ant-color-bg-container) 85%, transparent) 32px, color-mix(in srgb, var(--ant-color-bg-container) 95%, transparent) 100%)',
240
- padding: '40px 8px 8px',
239
+ flex: '0 0 auto',
240
+ background: 'var(--ant-color-bg-container)',
241
+ padding: '8px',
241
242
  display: 'flex',
242
243
  flexDirection: 'column',
243
244
  alignItems: 'center',
@@ -13,7 +13,7 @@
13
13
  See the License for the specific language governing permissions and
14
14
  limitations under the License.
15
15
  */ import React from 'react';
16
- import { ConfigProvider, Radio, Space } from 'antd';
16
+ import { Col, ConfigProvider, Radio, Row, Space, theme } from 'antd';
17
17
  import { renderHtml, withBlockDefaults } from '@lowdefy/block-utils';
18
18
  import { type } from '@lowdefy/helpers';
19
19
  import Label from '../Label/Label.js';
@@ -22,6 +22,7 @@ import useSelectorOptions from '../../useSelectorOptions.js';
22
22
  import withTheme from '../withTheme.js';
23
23
  const RadioGroup = Radio.Group;
24
24
  const RadioSelector = ({ blockId, classNames = {}, components, events, loading, properties, required, styles = {}, validation, value, methods })=>{
25
+ const { token } = theme.useToken();
25
26
  const uniqueValueOptions = useSelectorOptions({
26
27
  properties,
27
28
  methods
@@ -29,29 +30,17 @@ const RadioSelector = ({ blockId, classNames = {}, components, events, loading,
29
30
  const selectedIndex = getSelectedIndex(value, uniqueValueOptions, {
30
31
  properties
31
32
  });
32
- const radioGroup = /*#__PURE__*/ React.createElement(RadioGroup, {
33
- id: `${blockId}_input`,
34
- className: classNames.element,
35
- disabled: properties.disabled || loading,
36
- style: styles.element,
37
- onChange: (event)=>{
38
- const val = type.isPrimitive(uniqueValueOptions[event.target.value]) ? uniqueValueOptions[event.target.value] : uniqueValueOptions[event.target.value].value;
39
- methods.setValue(val);
40
- methods.triggerEvent({
41
- name: 'onChange',
42
- event: {
43
- value: val
44
- }
45
- });
46
- },
47
- value: `${getSelectedIndex(value, uniqueValueOptions, {
48
- properties
49
- })}`
50
- }, /*#__PURE__*/ React.createElement(Space, {
51
- direction: properties.direction,
52
- wrap: type.isNone(properties.wrap) ? true : properties.wrap,
53
- align: type.isNone(properties.align) ? 'start' : properties.align
54
- }, uniqueValueOptions.map((opt, i)=>{
33
+ const grid = !type.isNone(properties.columns);
34
+ // Col takes spans, not counts, so a breakpoint map of counts converts per key.
35
+ const colProps = type.isObject(properties.columns) ? Object.fromEntries(Object.entries(properties.columns).map(([breakpoint, columns])=>[
36
+ breakpoint,
37
+ {
38
+ span: 24 / columns
39
+ }
40
+ ])) : {
41
+ span: 24 / properties.columns
42
+ };
43
+ const renderOption = (opt, i)=>{
55
44
  if (type.isPrimitive(opt)) {
56
45
  return /*#__PURE__*/ React.createElement(Radio, {
57
46
  id: `${blockId}_${opt}`,
@@ -91,7 +80,43 @@ const RadioSelector = ({ blockId, classNames = {}, components, events, loading,
91
80
  }
92
81
  }
93
82
  }, radio);
94
- })));
83
+ };
84
+ const radioGroup = /*#__PURE__*/ React.createElement(RadioGroup, {
85
+ id: `${blockId}_input`,
86
+ className: classNames.element,
87
+ disabled: properties.disabled || loading,
88
+ // Radio.Group is inline-block and shrink-wraps, so the Row inside it can
89
+ // only fill a group that has been given a width.
90
+ style: grid ? {
91
+ width: '100%',
92
+ ...styles.element
93
+ } : styles.element,
94
+ onChange: (event)=>{
95
+ const val = type.isPrimitive(uniqueValueOptions[event.target.value]) ? uniqueValueOptions[event.target.value] : uniqueValueOptions[event.target.value].value;
96
+ methods.setValue(val);
97
+ methods.triggerEvent({
98
+ name: 'onChange',
99
+ event: {
100
+ value: val
101
+ }
102
+ });
103
+ },
104
+ value: `${getSelectedIndex(value, uniqueValueOptions, {
105
+ properties
106
+ })}`
107
+ }, grid ? /*#__PURE__*/ React.createElement(Row, {
108
+ gutter: type.isNone(properties.gutter) ? [
109
+ token.paddingXS,
110
+ 0
111
+ ] : properties.gutter
112
+ }, uniqueValueOptions.map((opt, i)=>/*#__PURE__*/ React.createElement(Col, {
113
+ key: i,
114
+ ...colProps
115
+ }, renderOption(opt, i)))) : /*#__PURE__*/ React.createElement(Space, {
116
+ direction: properties.direction,
117
+ wrap: type.isNone(properties.wrap) ? true : properties.wrap,
118
+ align: type.isNone(properties.align) ? 'start' : properties.align
119
+ }, uniqueValueOptions.map(renderOption)));
95
120
  return /*#__PURE__*/ React.createElement(Label, {
96
121
  blockId: blockId,
97
122
  methods: methods,
@@ -51,7 +51,7 @@ export default {
51
51
  'baseline'
52
52
  ],
53
53
  default: 'start',
54
- description: 'Align options.'
54
+ description: "Align options. Ignored when 'columns' is set."
55
55
  },
56
56
  color: {
57
57
  type: 'string',
@@ -60,6 +60,16 @@ export default {
60
60
  displayType: 'color'
61
61
  }
62
62
  },
63
+ columns: {
64
+ type: [
65
+ 'integer',
66
+ 'object'
67
+ ],
68
+ description: 'Number of columns to lay the options out in, or a responsive breakpoint object. Use a count that divides 24 evenly.',
69
+ docs: {
70
+ displayType: 'yaml'
71
+ }
72
+ },
63
73
  disabled,
64
74
  direction: {
65
75
  type: 'string',
@@ -68,12 +78,22 @@ export default {
68
78
  'vertical'
69
79
  ],
70
80
  default: 'horizontal',
71
- description: 'List options horizontally or vertical.'
81
+ description: "List options horizontally or vertical. Ignored when 'columns' is set."
82
+ },
83
+ gutter: {
84
+ type: [
85
+ 'number',
86
+ 'array'
87
+ ],
88
+ description: "Gap between options in the grid. Number or [horizontal, vertical] array. Applies when 'columns' is set.",
89
+ docs: {
90
+ displayType: 'yaml'
91
+ }
72
92
  },
73
93
  wrap: {
74
94
  type: 'boolean',
75
95
  default: true,
76
- description: "Specifies wrapping of options. Applies when 'direction' is 'horizontal'."
96
+ description: "Specifies wrapping of options. Applies when 'direction' is 'horizontal'. Ignored when 'columns' is set."
77
97
  },
78
98
  label,
79
99
  options,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lowdefy/blocks-antd",
3
- "version": "5.5.0",
3
+ "version": "5.6.0",
4
4
  "license": "Apache-2.0",
5
5
  "description": "Lowdefy Ant Design Blocks",
6
6
  "homepage": "https://lowdefy.com",
@@ -45,9 +45,9 @@
45
45
  ],
46
46
  "dependencies": {
47
47
  "@ant-design/icons": "6.1.0",
48
- "@lowdefy/block-utils": "5.5.0",
49
- "@lowdefy/helpers": "5.5.0",
50
- "@lowdefy/nunjucks": "5.5.0",
48
+ "@lowdefy/block-utils": "5.6.0",
49
+ "@lowdefy/helpers": "5.6.0",
50
+ "@lowdefy/nunjucks": "5.6.0",
51
51
  "@rc-component/motion": "1.3.1",
52
52
  "classnames": "2.3.2",
53
53
  "dayjs": "1.11.19",
@@ -60,9 +60,9 @@
60
60
  "react-dom": ">=18"
61
61
  },
62
62
  "devDependencies": {
63
- "@lowdefy/block-dev-e2e": "5.5.0",
64
- "@lowdefy/e2e-utils": "5.5.0",
65
- "@lowdefy/node-utils": "5.5.0",
63
+ "@lowdefy/block-dev-e2e": "5.6.0",
64
+ "@lowdefy/e2e-utils": "5.6.0",
65
+ "@lowdefy/node-utils": "5.6.0",
66
66
  "@playwright/test": "1.50.1",
67
67
  "@swc/cli": "0.8.0",
68
68
  "@swc/core": "1.15.18",