@lumx/react 3.11.4-alpha.0 → 3.12.1-alpha.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.
@@ -32,7 +32,7 @@ export interface UserBlockProps extends GenericProps, HasTheme {
32
32
  /** Multiple action toolbar content. */
33
33
  multipleActions?: ReactNode;
34
34
  /** User name. */
35
- name?: string;
35
+ name?: React.ReactNode;
36
36
  /** Props to pass to the name block. */
37
37
  nameProps?: GenericProps;
38
38
  /** Orientation. */
@@ -47,6 +47,10 @@ export interface UserBlockProps extends GenericProps, HasTheme {
47
47
  onMouseEnter?(): void;
48
48
  /** On mouse leave callback. */
49
49
  onMouseLeave?(): void;
50
+ /** Display additional fields below the original name and fields */
51
+ additionalFields?: React.ReactNode;
52
+ /** Display an additional element after the entire component. (to the right if orientation is horizontal, at the bottom if orientation is vertical) */
53
+ after?: React.ReactNode;
50
54
  }
51
55
 
52
56
  /**
@@ -92,6 +96,9 @@ export const UserBlock = forwardRef<UserBlockProps, HTMLDivElement>((props, ref)
92
96
  simpleAction,
93
97
  size,
94
98
  theme = defaultTheme,
99
+ children,
100
+ additionalFields,
101
+ after,
95
102
  ...forwardedProps
96
103
  } = props;
97
104
  let componentSize = size;
@@ -131,7 +138,9 @@ export const UserBlock = forwardRef<UserBlockProps, HTMLDivElement>((props, ref)
131
138
  return <NameComponent {...nProps}>{name}</NameComponent>;
132
139
  }, [avatarProps, isClickable, linkAs, linkProps, name, nameProps, onClick]);
133
140
 
134
- const fieldsBlock: ReactNode = fields && componentSize !== Size.s && componentSize !== Size.xs && (
141
+ const shouldDisplayFields = componentSize !== Size.s && componentSize !== Size.xs;
142
+
143
+ const fieldsBlock: ReactNode = fields && shouldDisplayFields && (
135
144
  <div className={`${CLASSNAME}__fields`}>
136
145
  {fields.map((field: string, idx: number) => (
137
146
  <span key={idx} className={`${CLASSNAME}__field`}>
@@ -164,16 +173,18 @@ export const UserBlock = forwardRef<UserBlockProps, HTMLDivElement>((props, ref)
164
173
  theme={theme}
165
174
  />
166
175
  )}
167
- {(fields || name) && (
176
+ {(fields || name || children || additionalFields) && (
168
177
  <div className={`${CLASSNAME}__wrapper`}>
169
- {nameBlock}
178
+ {children || nameBlock}
170
179
  {fieldsBlock}
180
+ {shouldDisplayFields ? additionalFields : null}
171
181
  </div>
172
182
  )}
173
183
  {shouldDisplayActions && simpleAction && <div className={`${CLASSNAME}__action`}>{simpleAction}</div>}
174
184
  {shouldDisplayActions && multipleActions && (
175
185
  <div className={`${CLASSNAME}__actions`}>{multipleActions}</div>
176
186
  )}
187
+ {after ? <div className={`${CLASSNAME}__after`}>{after}</div> : null}
177
188
  </div>
178
189
  );
179
190
  });
@@ -63,6 +63,7 @@ import {
63
63
  export const iconArgType = {
64
64
  control: { type: 'select' },
65
65
  options: {
66
+ undefined,
66
67
  mdiAbTesting,
67
68
  mdiAbjadArabic,
68
69
  mdiAccount,
@@ -30,6 +30,8 @@ interface Options<S extends CommonSetup> {
30
30
  viaContext: boolean;
31
31
  /** Apply a default theme if no prop or context was provided */
32
32
  defaultTheme?: Theme;
33
+ /** Default props to apply when testing theme */
34
+ defaultProps?: S['props'];
33
35
  };
34
36
  }
35
37
 
@@ -128,7 +130,7 @@ export function commonTestsSuiteRTL<S extends CommonSetup>(setup: SetupFunction<
128
130
  it.each(testElements)(
129
131
  `should $apply default theme (${defaultTheme}) to \`$element\``,
130
132
  async (affectedElement) => {
131
- const wrappers = await setup();
133
+ const wrappers = await setup(applyTheme.defaultProps);
132
134
  expectTheme(wrappers, affectedElement, defaultTheme);
133
135
  },
134
136
  );
@@ -140,7 +142,7 @@ export function commonTestsSuiteRTL<S extends CommonSetup>(setup: SetupFunction<
140
142
  it.each(affectedElements)(
141
143
  `should not apply default theme (${defaultTheme}) to \`$element\``,
142
144
  async (affectedElement) => {
143
- const wrappers = await setup();
145
+ const wrappers = await setup(applyTheme.defaultProps);
144
146
  expectTheme(wrappers, affectedElement, Theme.light, { shouldHaveModifier: false });
145
147
  expectTheme(wrappers, affectedElement, Theme.dark, { shouldHaveModifier: false });
146
148
  },
@@ -152,7 +154,7 @@ export function commonTestsSuiteRTL<S extends CommonSetup>(setup: SetupFunction<
152
154
  it.each(testElements)(
153
155
  `should $apply prop theme=${theme} to \`$element\``,
154
156
  async (affectedElement) => {
155
- const wrappers = await setup({ theme });
157
+ const wrappers = await setup({ ...applyTheme.defaultProps, theme });
156
158
  expectTheme(wrappers, affectedElement, theme);
157
159
  },
158
160
  );
@@ -0,0 +1,40 @@
1
+ import React from 'react';
2
+
3
+ import { Icon } from '@lumx/react';
4
+ import { mdiEarth, mdiFoodApple, mdiPencil } from '@lumx/icons';
5
+ import { wrapChildrenIconWithSpaces } from './wrapChildrenIconWithSpaces';
6
+
7
+ describe(wrapChildrenIconWithSpaces, () => {
8
+ it('should ignore null or undefined children', () => {
9
+ expect(wrapChildrenIconWithSpaces(undefined)).toBeUndefined();
10
+ expect(wrapChildrenIconWithSpaces(null)).toBeUndefined();
11
+ });
12
+
13
+ it('should wrap icons with spaces', () => {
14
+ expect(
15
+ wrapChildrenIconWithSpaces(
16
+ <>
17
+ <Icon icon={mdiEarth} />a string
18
+ <>
19
+ some more string with
20
+ <Icon icon={mdiFoodApple} />
21
+ </>
22
+ {['array with', <Icon key="custom-key" icon={mdiPencil} />]}
23
+ </>,
24
+ ),
25
+ ).toEqual([
26
+ ' ',
27
+ <Icon key=".0" icon={mdiEarth} />,
28
+ ' ',
29
+ 'a string',
30
+ 'some more string with',
31
+ ' ',
32
+ <Icon key=".1" icon={mdiFoodApple} />,
33
+ ' ',
34
+ 'array with',
35
+ ' ',
36
+ <Icon key=".3:$custom-key" icon={mdiPencil} />,
37
+ ' ',
38
+ ]);
39
+ });
40
+ });
@@ -0,0 +1,22 @@
1
+ import React, { Children } from 'react';
2
+ import { isComponentType } from '@lumx/react/utils/type';
3
+ import { Icon } from '@lumx/react';
4
+
5
+ /** Force wrap spaces around icons to make sure they are never stuck against text. */
6
+ export function wrapChildrenIconWithSpaces(children: React.ReactNode): React.ReactNode {
7
+ if (children === null || children === undefined) return undefined;
8
+ return Children.toArray(children).flatMap((child) => {
9
+ if (!React.isValidElement(child)) {
10
+ return child;
11
+ }
12
+
13
+ if (child.type === React.Fragment) {
14
+ return wrapChildrenIconWithSpaces(child.props.children);
15
+ }
16
+
17
+ if (isComponentType(Icon)(child)) {
18
+ return [' ', child, ' '];
19
+ }
20
+ return child;
21
+ });
22
+ }