@camtomlabs/malix-design-system 0.1.2 → 0.1.4

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.
Files changed (58) hide show
  1. package/LICENSE +19 -5
  2. package/README.md +141 -10
  3. package/dist/index.cjs +2456 -0
  4. package/dist/index.d.cts +562 -0
  5. package/dist/index.d.ts +562 -0
  6. package/dist/index.js +2367 -0
  7. package/package.json +21 -8
  8. package/src/styles.css +186 -0
  9. package/src/components/Accordion.tsx +0 -52
  10. package/src/components/Avatar.tsx +0 -18
  11. package/src/components/Badge.tsx +0 -27
  12. package/src/components/Banner.tsx +0 -75
  13. package/src/components/Breadcrumb.tsx +0 -58
  14. package/src/components/Button.tsx +0 -47
  15. package/src/components/Card.tsx +0 -34
  16. package/src/components/ChatInput.tsx +0 -53
  17. package/src/components/Checkbox.tsx +0 -85
  18. package/src/components/CreditsIndicator.tsx +0 -41
  19. package/src/components/DataTable.tsx +0 -75
  20. package/src/components/DateInput.tsx +0 -57
  21. package/src/components/Divider.tsx +0 -12
  22. package/src/components/Dropzone.tsx +0 -94
  23. package/src/components/EmptyState.tsx +0 -65
  24. package/src/components/FileCard.tsx +0 -78
  25. package/src/components/FilterTabs.tsx +0 -49
  26. package/src/components/FlyoutMenu.tsx +0 -36
  27. package/src/components/GlassPopover.tsx +0 -38
  28. package/src/components/Header.tsx +0 -22
  29. package/src/components/Input.tsx +0 -18
  30. package/src/components/InputGroup.tsx +0 -37
  31. package/src/components/LanguageSelector.tsx +0 -81
  32. package/src/components/Modal.tsx +0 -104
  33. package/src/components/OnboardingPopover.tsx +0 -61
  34. package/src/components/OperationStatus.tsx +0 -73
  35. package/src/components/Overlay.tsx +0 -66
  36. package/src/components/Pagination.tsx +0 -89
  37. package/src/components/Pill.tsx +0 -19
  38. package/src/components/PricingCard.tsx +0 -74
  39. package/src/components/ProgressBar.tsx +0 -47
  40. package/src/components/Radio.tsx +0 -56
  41. package/src/components/SectionHeader.tsx +0 -32
  42. package/src/components/SegmentedControl.tsx +0 -42
  43. package/src/components/Select.tsx +0 -62
  44. package/src/components/SelectGroup.tsx +0 -32
  45. package/src/components/SelectionCard.tsx +0 -47
  46. package/src/components/SidebarItem.tsx +0 -27
  47. package/src/components/SidebarPanel.tsx +0 -84
  48. package/src/components/SplitPane.tsx +0 -85
  49. package/src/components/StatCard.tsx +0 -64
  50. package/src/components/StatusDot.tsx +0 -26
  51. package/src/components/Stepper.tsx +0 -40
  52. package/src/components/TabBar.tsx +0 -45
  53. package/src/components/Textarea.tsx +0 -43
  54. package/src/components/Toggle.tsx +0 -50
  55. package/src/components/Tooltip.tsx +0 -33
  56. package/src/components/UserProfilePopover.tsx +0 -100
  57. package/src/components/ValidationAlert.tsx +0 -72
  58. package/src/index.ts +0 -177
@@ -1,85 +0,0 @@
1
- import React from 'react';
2
-
3
- export type CheckboxProps = Omit<React.HTMLAttributes<HTMLButtonElement>, 'onChange'> & {
4
- checked?: boolean;
5
- onChange?: (checked: boolean) => void;
6
- disabled?: boolean;
7
- label?: string;
8
- indeterminate?: boolean;
9
- };
10
-
11
- export function Checkbox({
12
- checked = false,
13
- onChange,
14
- disabled = false,
15
- label,
16
- indeterminate = false,
17
- className,
18
- ...props
19
- }: CheckboxProps) {
20
- const handleClick = () => {
21
- if (!disabled && onChange) {
22
- onChange(!checked);
23
- }
24
- };
25
-
26
- const checkbox = (
27
- <button
28
- type="button"
29
- role="checkbox"
30
- className={`malix-checkbox${className ? ` ${className}` : ''}`}
31
- data-checked={checked}
32
- data-disabled={disabled}
33
- data-indeterminate={indeterminate}
34
- aria-checked={indeterminate ? 'mixed' : checked}
35
- disabled={disabled}
36
- onClick={handleClick}
37
- {...props}
38
- >
39
- {checked && !indeterminate ? (
40
- <svg
41
- className="malix-checkbox__icon"
42
- viewBox="0 0 12 12"
43
- fill="none"
44
- xmlns="http://www.w3.org/2000/svg"
45
- aria-hidden="true"
46
- >
47
- <path
48
- d="M2 6l3 3 5-5"
49
- stroke="currentColor"
50
- strokeWidth="2"
51
- strokeLinecap="round"
52
- strokeLinejoin="round"
53
- />
54
- </svg>
55
- ) : null}
56
- {indeterminate ? (
57
- <svg
58
- className="malix-checkbox__icon"
59
- viewBox="0 0 12 12"
60
- fill="none"
61
- xmlns="http://www.w3.org/2000/svg"
62
- aria-hidden="true"
63
- >
64
- <path
65
- d="M2 6h8"
66
- stroke="currentColor"
67
- strokeWidth="2"
68
- strokeLinecap="round"
69
- />
70
- </svg>
71
- ) : null}
72
- </button>
73
- );
74
-
75
- if (label) {
76
- return (
77
- <label className="malix-checkbox-row">
78
- {checkbox}
79
- <span className="malix-checkbox-row__label">{label}</span>
80
- </label>
81
- );
82
- }
83
-
84
- return checkbox;
85
- }
@@ -1,41 +0,0 @@
1
- import React from 'react';
2
-
3
- export type CreditsIndicatorProps = React.HTMLAttributes<HTMLDivElement> & {
4
- remaining: number;
5
- label?: string;
6
- };
7
-
8
- export function CreditsIndicator({
9
- remaining,
10
- label = 'Credits Remaining',
11
- className,
12
- ...props
13
- }: CreditsIndicatorProps) {
14
- return (
15
- <div
16
- className={`malix-credits-indicator${className ? ` ${className}` : ''}`}
17
- {...props}
18
- >
19
- <span className="malix-credits-indicator__icon" aria-hidden="true">
20
- <svg
21
- width="20"
22
- height="20"
23
- viewBox="0 0 24 24"
24
- fill="none"
25
- stroke="currentColor"
26
- strokeWidth="2"
27
- strokeLinecap="round"
28
- strokeLinejoin="round"
29
- >
30
- <ellipse cx="12" cy="6" rx="8" ry="3" />
31
- <path d="M4 6v6c0 1.66 3.58 3 8 3s8-1.34 8-3V6" />
32
- <path d="M4 12v6c0 1.66 3.58 3 8 3s8-1.34 8-3v-6" />
33
- </svg>
34
- </span>
35
- <div className="malix-credits-indicator__info">
36
- <span className="malix-credits-indicator__label">{label}</span>
37
- <span className="malix-credits-indicator__value">{remaining}</span>
38
- </div>
39
- </div>
40
- );
41
- }
@@ -1,75 +0,0 @@
1
- import React from 'react';
2
-
3
- export type TableColumn = {
4
- key: string;
5
- header: string;
6
- width?: string;
7
- render?: (value: any, row: TableRow) => React.ReactNode;
8
- };
9
-
10
- export type TableRow = Record<string, any>;
11
-
12
- export type DataTableProps = React.HTMLAttributes<HTMLTableElement> & {
13
- columns: TableColumn[];
14
- data: TableRow[];
15
- onRowClick?: (row: TableRow) => void;
16
- emptyMessage?: string;
17
- className?: string;
18
- };
19
-
20
- export function DataTable({
21
- columns,
22
- data,
23
- onRowClick,
24
- emptyMessage,
25
- className,
26
- ...props
27
- }: DataTableProps) {
28
- return (
29
- <table
30
- className={`malix-data-table${className ? ` ${className}` : ''}`}
31
- {...props}
32
- >
33
- <thead>
34
- <tr className="malix-data-table__header-row">
35
- {columns.map((col) => (
36
- <th
37
- key={col.key}
38
- className="malix-data-table__header-cell"
39
- style={col.width ? { width: col.width } : undefined}
40
- >
41
- {col.header}
42
- </th>
43
- ))}
44
- </tr>
45
- </thead>
46
- <tbody className="malix-data-table__body">
47
- {data.length > 0 ? (
48
- data.map((row, rowIndex) => (
49
- <tr
50
- key={rowIndex}
51
- className="malix-data-table__data-row"
52
- data-clickable={onRowClick ? true : undefined}
53
- onClick={onRowClick ? () => onRowClick(row) : undefined}
54
- >
55
- {columns.map((col) => (
56
- <td key={col.key} className="malix-data-table__cell">
57
- {col.render ? col.render(row[col.key], row) : row[col.key]}
58
- </td>
59
- ))}
60
- </tr>
61
- ))
62
- ) : (
63
- <tr className="malix-data-table__data-row">
64
- <td
65
- className="malix-data-table__cell"
66
- colSpan={columns.length}
67
- >
68
- {emptyMessage || 'No data available'}
69
- </td>
70
- </tr>
71
- )}
72
- </tbody>
73
- </table>
74
- );
75
- }
@@ -1,57 +0,0 @@
1
- import React from 'react';
2
-
3
- export type DateInputProps = Omit<React.InputHTMLAttributes<HTMLInputElement>, 'type' | 'onChange'> & {
4
- value?: string;
5
- placeholder?: string;
6
- onChange?: (value: string) => void;
7
- disabled?: boolean;
8
- };
9
-
10
- export function DateInput({
11
- value,
12
- placeholder = 'dd/mm/yyyy',
13
- onChange,
14
- disabled,
15
- className,
16
- id,
17
- ...props
18
- }: DateInputProps) {
19
- const inputId = id || `date-input-${React.useId()}`;
20
-
21
- const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
22
- onChange?.(e.target.value);
23
- };
24
-
25
- return (
26
- <div
27
- className={`malix-date-input${className ? ` ${className}` : ''}`}
28
- data-filled={value ? true : undefined}
29
- data-disabled={disabled || undefined}
30
- >
31
- <input
32
- id={inputId}
33
- type="date"
34
- className="malix-date-input__native"
35
- value={value}
36
- onChange={handleChange}
37
- disabled={disabled}
38
- aria-label={placeholder}
39
- {...props}
40
- />
41
- <span className="malix-date-input__value">
42
- {value || placeholder}
43
- </span>
44
- <span className="malix-date-input__icon" aria-hidden="true">
45
- <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
46
- <path
47
- d="M12.667 2.667H3.333C2.597 2.667 2 3.264 2 4v9.333c0 .737.597 1.334 1.333 1.334h9.334c.736 0 1.333-.597 1.333-1.334V4c0-.736-.597-1.333-1.333-1.333ZM10.667 1.333V4M5.333 1.333V4M2 6.667h12"
48
- stroke="currentColor"
49
- strokeWidth="1.33"
50
- strokeLinecap="round"
51
- strokeLinejoin="round"
52
- />
53
- </svg>
54
- </span>
55
- </div>
56
- );
57
- }
@@ -1,12 +0,0 @@
1
- import React from 'react';
2
-
3
- export type DividerProps = React.HTMLAttributes<HTMLHRElement>;
4
-
5
- export function Divider({ className, ...props }: DividerProps) {
6
- return (
7
- <hr
8
- className={`malix-divider${className ? ` ${className}` : ''}`}
9
- {...props}
10
- />
11
- );
12
- }
@@ -1,94 +0,0 @@
1
- import React, { useRef, useState } from 'react';
2
-
3
- export type DropzoneProps = React.HTMLAttributes<HTMLDivElement> & {
4
- onDrop?: (files: File[]) => void;
5
- accept?: string;
6
- title?: string;
7
- hint?: string;
8
- disabled?: boolean;
9
- };
10
-
11
- export function Dropzone({
12
- onDrop,
13
- accept,
14
- title = 'Drop files here',
15
- hint = 'or click to browse',
16
- disabled = false,
17
- className,
18
- ...props
19
- }: DropzoneProps) {
20
- const [dragging, setDragging] = useState(false);
21
- const inputRef = useRef<HTMLInputElement | null>(null);
22
-
23
- function handleDragOver(event: React.DragEvent<HTMLDivElement>) {
24
- event.preventDefault();
25
- if (!disabled) setDragging(true);
26
- }
27
-
28
- function handleDragLeave(event: React.DragEvent<HTMLDivElement>) {
29
- event.preventDefault();
30
- setDragging(false);
31
- }
32
-
33
- function handleDrop(event: React.DragEvent<HTMLDivElement>) {
34
- event.preventDefault();
35
- setDragging(false);
36
- if (disabled) return;
37
- const files = Array.from(event.dataTransfer.files);
38
- onDrop?.(files);
39
- }
40
-
41
- function handleInputChange(event: React.ChangeEvent<HTMLInputElement>) {
42
- const files = event.target.files ? Array.from(event.target.files) : [];
43
- if (files.length > 0) onDrop?.(files);
44
- event.target.value = '';
45
- }
46
-
47
- function handleClick() {
48
- if (!disabled) inputRef.current?.click();
49
- }
50
-
51
- return (
52
- <div
53
- className={`malix-dropzone${className ? ` ${className}` : ''}`}
54
- data-dragging={dragging}
55
- data-disabled={disabled}
56
- onDragOver={handleDragOver}
57
- onDragLeave={handleDragLeave}
58
- onDrop={handleDrop}
59
- onClick={handleClick}
60
- role="button"
61
- tabIndex={disabled ? -1 : 0}
62
- aria-disabled={disabled || undefined}
63
- {...props}
64
- >
65
- <input
66
- ref={inputRef}
67
- type="file"
68
- accept={accept}
69
- onChange={handleInputChange}
70
- hidden
71
- aria-hidden="true"
72
- />
73
- <svg
74
- className="malix-dropzone__icon"
75
- width="32"
76
- height="32"
77
- viewBox="0 0 24 24"
78
- fill="none"
79
- stroke="currentColor"
80
- strokeWidth="1.5"
81
- strokeLinecap="round"
82
- strokeLinejoin="round"
83
- aria-hidden="true"
84
- >
85
- <path d="M4 14.899A7 7 0 1 1 15.71 8h1.79a4.5 4.5 0 0 1 2.5 8.242" />
86
- <path d="M12 12v9" />
87
- <path d="m16 16-4-4-4 4" />
88
- </svg>
89
- <span className="malix-dropzone__title">{title}</span>
90
- <span className="malix-dropzone__hint">{hint}</span>
91
- <span className="malix-dropzone__browse-btn">Browse files</span>
92
- </div>
93
- );
94
- }
@@ -1,65 +0,0 @@
1
- import React from 'react';
2
-
3
- export type EmptyStateProps = React.HTMLAttributes<HTMLDivElement> & {
4
- icon?: React.ReactNode;
5
- title: string;
6
- description?: string;
7
- action?: React.ReactNode;
8
- };
9
-
10
- const DefaultIcon = () => (
11
- <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
12
- <path
13
- d="M3 7V5C3 3.895 3.895 3 5 3H9L11 5H19C20.105 5 21 5.895 21 7V9"
14
- stroke="currentColor"
15
- strokeWidth="1.5"
16
- strokeLinecap="round"
17
- strokeLinejoin="round"
18
- />
19
- <rect
20
- x="2"
21
- y="9"
22
- width="20"
23
- height="12"
24
- rx="2"
25
- stroke="currentColor"
26
- strokeWidth="1.5"
27
- strokeLinecap="round"
28
- strokeLinejoin="round"
29
- />
30
- <path
31
- d="M9 15H15"
32
- stroke="currentColor"
33
- strokeWidth="1.5"
34
- strokeLinecap="round"
35
- strokeLinejoin="round"
36
- />
37
- </svg>
38
- );
39
-
40
- export function EmptyState({
41
- icon,
42
- title,
43
- description,
44
- action,
45
- className,
46
- ...props
47
- }: EmptyStateProps) {
48
- return (
49
- <div
50
- className={`malix-empty-state${className ? ` ${className}` : ''}`}
51
- {...props}
52
- >
53
- <div className="malix-empty-state__icon-wrap">
54
- {icon ?? <DefaultIcon />}
55
- </div>
56
- <h3 className="malix-empty-state__title">{title}</h3>
57
- {description ? (
58
- <p className="malix-empty-state__description">{description}</p>
59
- ) : null}
60
- {action ? (
61
- <div className="malix-empty-state__action">{action}</div>
62
- ) : null}
63
- </div>
64
- );
65
- }
@@ -1,78 +0,0 @@
1
- import React from 'react';
2
-
3
- export type FileCardProps = React.HTMLAttributes<HTMLDivElement> & {
4
- fileName: string;
5
- meta?: string;
6
- icon?: React.ReactNode;
7
- onAction?: React.MouseEventHandler<HTMLButtonElement>;
8
- };
9
-
10
- const DefaultFileIcon = () => (
11
- <svg
12
- width="20"
13
- height="20"
14
- viewBox="0 0 24 24"
15
- fill="none"
16
- stroke="currentColor"
17
- strokeWidth="2"
18
- strokeLinecap="round"
19
- strokeLinejoin="round"
20
- >
21
- <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
22
- <polyline points="14 2 14 8 20 8" />
23
- <line x1="16" y1="13" x2="8" y2="13" />
24
- <line x1="16" y1="17" x2="8" y2="17" />
25
- <polyline points="10 9 9 9 8 9" />
26
- </svg>
27
- );
28
-
29
- const MoreVerticalIcon = () => (
30
- <svg
31
- width="16"
32
- height="16"
33
- viewBox="0 0 24 24"
34
- fill="currentColor"
35
- >
36
- <circle cx="12" cy="5" r="1.5" />
37
- <circle cx="12" cy="12" r="1.5" />
38
- <circle cx="12" cy="19" r="1.5" />
39
- </svg>
40
- );
41
-
42
- export function FileCard({
43
- fileName,
44
- meta,
45
- icon,
46
- onAction,
47
- className,
48
- ...props
49
- }: FileCardProps) {
50
- return (
51
- <div
52
- className={`malix-file-card${className ? ` ${className}` : ''}`}
53
- {...props}
54
- >
55
- <div className="malix-file-card__icon-wrap">
56
- <span className="malix-file-card__icon">
57
- {icon ?? <DefaultFileIcon />}
58
- </span>
59
- </div>
60
- <div className="malix-file-card__info">
61
- <span className="malix-file-card__name">{fileName}</span>
62
- {meta ? (
63
- <span className="malix-file-card__meta">{meta}</span>
64
- ) : null}
65
- </div>
66
- {onAction ? (
67
- <button
68
- type="button"
69
- className="malix-file-card__action"
70
- onClick={onAction}
71
- aria-label="More actions"
72
- >
73
- <MoreVerticalIcon />
74
- </button>
75
- ) : null}
76
- </div>
77
- );
78
- }
@@ -1,49 +0,0 @@
1
- import React from 'react';
2
-
3
- export type FilterTabItem = {
4
- label: string;
5
- count?: number;
6
- value: string;
7
- };
8
-
9
- export type FilterTabsProps = Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange'> & {
10
- items: FilterTabItem[];
11
- value: string;
12
- onChange: (value: string) => void;
13
- };
14
-
15
- export function FilterTabs({
16
- items,
17
- value,
18
- onChange,
19
- className,
20
- ...props
21
- }: FilterTabsProps) {
22
- return (
23
- <div
24
- className={`malix-filter-tabs${className ? ` ${className}` : ''}`}
25
- role="tablist"
26
- {...props}
27
- >
28
- {items.map((item) => {
29
- const isActive = item.value === value;
30
- return (
31
- <button
32
- key={item.value}
33
- type="button"
34
- role="tab"
35
- className="malix-filter-tabs__tab"
36
- data-active={isActive || undefined}
37
- aria-selected={isActive}
38
- onClick={() => onChange(item.value)}
39
- >
40
- <span className="malix-filter-tabs__tab-label">{item.label}</span>
41
- {item.count != null ? (
42
- <span className="malix-filter-tabs__tab-count">{item.count}</span>
43
- ) : null}
44
- </button>
45
- );
46
- })}
47
- </div>
48
- );
49
- }
@@ -1,36 +0,0 @@
1
- import React from 'react';
2
-
3
- export type FlyoutMenuItem = {
4
- icon?: React.ReactNode;
5
- label: string;
6
- active?: boolean;
7
- onClick?: () => void;
8
- };
9
-
10
- export type FlyoutMenuProps = React.HTMLAttributes<HTMLDivElement> & {
11
- items: FlyoutMenuItem[];
12
- };
13
-
14
- export function FlyoutMenu({ items, className, ...props }: FlyoutMenuProps) {
15
- return (
16
- <nav
17
- className={`malix-flyout-menu${className ? ` ${className}` : ''}`}
18
- role="menu"
19
- {...props}
20
- >
21
- {items.map((item, i) => (
22
- <button
23
- key={i}
24
- type="button"
25
- className="malix-flyout-menu__item"
26
- data-active={item.active || undefined}
27
- role="menuitem"
28
- onClick={item.onClick}
29
- >
30
- {item.icon ? <span className="malix-flyout-menu__icon">{item.icon}</span> : null}
31
- <span className="malix-flyout-menu__label">{item.label}</span>
32
- </button>
33
- ))}
34
- </nav>
35
- );
36
- }
@@ -1,38 +0,0 @@
1
- import React from 'react';
2
-
3
- export type GlassPopoverProps = React.HTMLAttributes<HTMLDivElement> & {
4
- title?: string;
5
- onClose?: () => void;
6
- };
7
-
8
- export function GlassPopover({
9
- title,
10
- onClose,
11
- children,
12
- className,
13
- ...props
14
- }: GlassPopoverProps) {
15
- return (
16
- <div
17
- className={`malix-glass-popover${className ? ` ${className}` : ''}`}
18
- {...props}
19
- >
20
- {(title || onClose) ? (
21
- <div className="malix-glass-popover__header">
22
- {title ? <span className="malix-glass-popover__title">{title}</span> : null}
23
- {onClose ? (
24
- <button
25
- type="button"
26
- className="malix-glass-popover__close"
27
- onClick={onClose}
28
- aria-label="Close"
29
- >
30
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><line x1="18" y1="6" x2="6" y2="18" /><line x1="6" y1="6" x2="18" y2="18" /></svg>
31
- </button>
32
- ) : null}
33
- </div>
34
- ) : null}
35
- <div className="malix-glass-popover__body">{children}</div>
36
- </div>
37
- );
38
- }
@@ -1,22 +0,0 @@
1
- import React from 'react';
2
-
3
- export type HeaderProps = React.HTMLAttributes<HTMLElement> & {
4
- pageTitle: string;
5
- actions?: React.ReactNode;
6
- };
7
-
8
- export function Header({ pageTitle, actions, className, ...props }: HeaderProps) {
9
- return (
10
- <header
11
- className={`malix-header${className ? ` ${className}` : ''}`}
12
- {...props}
13
- >
14
- <div className="malix-header__left">
15
- <h1 className="malix-header__title">{pageTitle}</h1>
16
- </div>
17
- {actions ? (
18
- <div className="malix-header__right">{actions}</div>
19
- ) : null}
20
- </header>
21
- );
22
- }
@@ -1,18 +0,0 @@
1
- import React from 'react';
2
-
3
- export type InputProps = React.InputHTMLAttributes<HTMLInputElement> & {
4
- leadingIcon?: React.ReactNode;
5
- };
6
-
7
- export function Input({ leadingIcon, ...props }: InputProps) {
8
- return (
9
- <label className="malix-input-wrap">
10
- {leadingIcon ? <span className="malix-button__icon">{leadingIcon}</span> : null}
11
- <input className="malix-input" {...props} />
12
- </label>
13
- );
14
- }
15
-
16
- export function SearchInput(props: Omit<InputProps, 'type'>) {
17
- return <Input type="search" placeholder="Search" {...props} />;
18
- }