Explorar o código

feat(admin-ui): Add more native React UI components

Michael Bromley %!s(int64=2) %!d(string=hai) anos
pai
achega
04e03f8e1c

+ 1 - 1
packages/admin-ui/src/lib/core/src/shared/components/action-bar/action-bar.component.scss

@@ -1,6 +1,6 @@
 @import 'variables';
 
-:host {
+:host, .vdr-action-bar {
     display: flex;
     justify-content: space-between;
     align-items: baseline;

+ 1 - 1
packages/admin-ui/src/lib/core/src/shared/components/data-table-2/data-table2.component.ts

@@ -42,7 +42,7 @@ import { DataTable2SearchComponent } from './data-table-search.component';
  * extend the {@link BaseListComponent} or {@link TypedBaseListComponent} class.
  *
  * @example
- * ```HTML
+ * ```html
  * <vdr-data-table-2
  *     id="product-review-list"
  *     [items]="items$ | async"

+ 9 - 1
packages/admin-ui/src/lib/core/src/shared/components/form-field/form-field.component.scss

@@ -20,7 +20,7 @@
 }
 
 ::ng-deep .input-row {
-    input,
+    input:not([type='checkbox']),
     select,
     textarea,
     vdr-zone-selector,
@@ -32,3 +32,11 @@
         width: 100%;
     }
 }
+
+.input-row {
+    input:not([type='checkbox']),
+    select,
+    textarea {
+        width: 100%;
+    }
+}

+ 1 - 1
packages/admin-ui/src/lib/core/src/shared/components/page-detail-layout/page-detail-layout.component.scss

@@ -1,7 +1,7 @@
 @import "variables";
 
 
-:host {
+:host, .vdr-page-detail-layout {
     display: grid;
     grid-template-columns: 3fr 1fr;
 }

+ 13 - 1
packages/admin-ui/src/lib/react/src/components/react-global-styles.scss

@@ -1 +1,13 @@
-@import "../../core/src/shared/components/card/card.component";
+@import '../../core/src/shared/components/card/card.component';
+@import '../../core/src/shared/components/form-field/form-field.component';
+@import '../../core/src/shared/components/action-bar/action-bar.component.scss';
+@import '../../core/src/shared/components/page-detail-layout/page-detail-layout.component';
+
+.form-group .input-row.invalid {
+    input:not([type='checkbox']):not([type='radio']),
+    select,
+    textarea {
+        color: var(--color-error-700);
+        border-color: var(--color-error-300);
+    }
+}

+ 5 - 0
packages/admin-ui/src/lib/react/src/public_api.ts

@@ -4,8 +4,13 @@ export * from './components/react-custom-detail.component';
 export * from './components/react-form-input.component';
 export * from './components/react-route.component';
 export * from './directives/react-component-host.directive';
+export * from './react-components/ActionBar';
 export * from './react-components/Card';
+export * from './react-components/CdsIcon';
+export * from './react-components/FormField';
 export * from './react-components/Link';
+export * from './react-components/PageBlock';
+export * from './react-components/PageDetailLayout';
 export * from './react-hooks/use-detail-component-data';
 export * from './react-hooks/use-form-control';
 export * from './react-hooks/use-injector';

+ 29 - 0
packages/admin-ui/src/lib/react/src/react-components/ActionBar.tsx

@@ -0,0 +1,29 @@
+import React, { PropsWithChildren, ReactNode } from 'react';
+
+/**
+ * @description
+ * A container for the primary actions on a list or detail page
+ *
+ * @example
+ * ```ts
+ * import { ActionBar } from '@vendure/admin-ui/react';
+ *
+ * export function MyComponent() {
+ *   return (
+ *     <ActionBar leftContent={<div>Optional left content</div>}>
+ *       <button className='button primary'>Primary action</button>
+ *     </ActionBar>
+ *   );
+ * }
+ * ```
+ *
+ * @docsCategory react-components
+ */
+export function ActionBar(props: PropsWithChildren<{ leftContent?: ReactNode }>) {
+    return (
+        <div className={'vdr-action-bar'}>
+            <div className="left-content">{props.leftContent}</div>
+            <div className="right-content">{props.children}</div>
+        </div>
+    );
+}

+ 53 - 0
packages/admin-ui/src/lib/react/src/react-components/CdsIcon.tsx

@@ -0,0 +1,53 @@
+import { ClarityIcons } from '@cds/core/icon';
+import { IconShapeTuple } from '@cds/core/icon/interfaces/icon.interfaces';
+import React, { DOMAttributes, useEffect } from 'react';
+
+type CustomElement<T> = Partial<T & DOMAttributes<T> & { children: any }>;
+
+export interface CdsIconProps {
+    shape: string;
+    size: string | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxl';
+    direction: 'up' | 'down' | 'left' | 'right';
+    flip: 'horizontal' | 'vertical';
+    solid: boolean;
+    status: 'info' | 'success' | 'warning' | 'danger';
+    inverse: boolean;
+    badge: 'info' | 'success' | 'warning' | 'danger';
+}
+
+declare global {
+    namespace JSX {
+        interface IntrinsicElements {
+            ['cds-icon']: CustomElement<CdsIconProps>;
+        }
+    }
+}
+
+export function registerCdsIcon(icon: IconShapeTuple) {
+    ClarityIcons.addIcons(icon);
+}
+
+/**
+ * @description
+ * A React wrapper for the Clarity UI icon component.
+ *
+ * @example
+ * ```ts
+ * import { userIcon } from '@cds/core/icon';
+ * import { CdsIcon } from '@vendure/admin-ui/react';
+ *
+ * registerCdsIcon(userIcon);
+ * export function MyComponent() {
+ *    return <CdsIcon icon={userIcon} badge="warning" solid size="lg"></CdsIcon>;
+ * }
+ * ```
+ *
+ * @docsCategory react-components
+ */
+export function CdsIcon(props: { icon: IconShapeTuple; className?: string } & Partial<CdsIconProps>) {
+    const { icon, ...rest } = props;
+    useEffect(() => {
+        ClarityIcons.addIcons(icon);
+    }, [icon]);
+    return <cds-icon {...rest} shape={icon[0]}></cds-icon>;
+}

+ 41 - 0
packages/admin-ui/src/lib/react/src/react-components/FormField.tsx

@@ -0,0 +1,41 @@
+import React, { PropsWithChildren } from 'react';
+
+/**
+ * @description
+ * A wrapper around form fields which provides a label, tooltip and error message.
+ *
+ * @example
+ * ```ts
+ * import { FormField } from '@vendure/admin-ui/react';
+ *
+ * export function MyReactComponent() {
+ *     return (
+ *        <FormField label="My field" tooltip="This is a tooltip" invalid errorMessage="This field is invalid">
+ *            <input type="text" />
+ *        </FormField>
+ *     );
+ * }
+ * ```
+ *
+ * @docsCategory react-components
+ */
+export function FormField(
+    props: PropsWithChildren<{
+        for?: string;
+        label?: string;
+        tooltip?: string;
+        invalid?: boolean;
+        errorMessage?: string;
+    }>,
+) {
+    return (
+        <div
+            className={`form-group ` + (!props.label ? 'no-label' : '') + (props.invalid ? 'clr-error' : '')}
+        >
+            {props.label && <label htmlFor={props.for ?? ''}>{props.label}</label>}
+            {props.tooltip && <div className="tooltip-text">{props.tooltip}</div>}
+            <div className={`input-row ` + (props.invalid ? 'invalid' : '')}>{props.children}</div>
+            {props.errorMessage && <div className="error-message">{props.errorMessage}</div>}
+        </div>
+    );
+}

+ 24 - 0
packages/admin-ui/src/lib/react/src/react-components/PageBlock.tsx

@@ -0,0 +1,24 @@
+import React, { PropsWithChildren } from 'react';
+
+/**
+ * @description
+ * A container for page content which provides a consistent width and spacing.
+ *
+ * @example
+ * ```ts
+ * import { PageBlock } from '@vendure/admin-ui/react';
+ *
+ * export function MyComponent() {
+ *   return (
+ *     <PageBlock>
+ *       ...
+ *     </PageBlock>
+ *   );
+ * }
+ * ```
+ *
+ * @docsCategory react-components
+ */
+export function PageBlock(props: PropsWithChildren) {
+    return <div className="page-block">{props.children}</div>;
+}

+ 29 - 0
packages/admin-ui/src/lib/react/src/react-components/PageDetailLayout.tsx

@@ -0,0 +1,29 @@
+import React, { PropsWithChildren, ReactNode } from 'react';
+
+/**
+ * @description
+ * A responsive container for detail views with a main content area and an optional sidebar.
+ *
+ * @example
+ * ```ts
+ * import { PageDetailLayout } from '@vendure/admin-ui/react';
+ *
+ * export function MyComponent() {
+ *   return (
+ *     <PageDetailLayout sidebar={<div>Sidebar content</div>}>
+ *       <div>Main content</div>
+ *     </PageDetailLayout>
+ *   );
+ * }
+ * ```
+ *
+ * @docsCategory react-components
+ */
+export function PageDetailLayout(props: PropsWithChildren<{ sidebar?: ReactNode }>) {
+    return (
+        <div className={'vdr-page-detail-layout'}>
+            <div className="main">{props.children}</div>
+            <div className="sidebar">{props.sidebar}</div>
+        </div>
+    );
+}