@licklist/design 0.78.35 → 0.78.36

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 (85) hide show
  1. package/dist/index.d.ts +4 -1
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +8 -2
  4. package/dist/v2/components/ActionMenu/ActionMenu.d.ts.map +1 -1
  5. package/dist/v2/components/ActionMenu/ActionMenu.js +6 -16
  6. package/dist/v2/components/ActionMenu/ActionMenu.scss.js +1 -1
  7. package/dist/v2/components/Button/Button.scss.js +1 -1
  8. package/dist/v2/components/Checkbox/Checkbox.scss.js +1 -1
  9. package/dist/v2/components/FormField/FormField.d.ts +3 -0
  10. package/dist/v2/components/FormField/FormField.d.ts.map +1 -1
  11. package/dist/v2/components/FormField/FormField.js +14 -3
  12. package/dist/v2/components/FormField/FormField.scss.js +1 -1
  13. package/dist/v2/components/IconButton/IconButton.d.ts +9 -0
  14. package/dist/v2/components/IconButton/IconButton.d.ts.map +1 -0
  15. package/dist/v2/components/IconButton/IconButton.js +137 -0
  16. package/dist/v2/components/IconButton/IconButton.scss.js +6 -0
  17. package/dist/v2/components/IconButton/index.d.ts +3 -0
  18. package/dist/v2/components/IconButton/index.d.ts.map +1 -0
  19. package/dist/v2/components/NewInput/NewInput.d.ts +1 -0
  20. package/dist/v2/components/NewInput/NewInput.d.ts.map +1 -1
  21. package/dist/v2/components/NewInput/NewInput.js +7 -4
  22. package/dist/v2/components/NewPageHeader/NewPageHeader.scss.js +1 -1
  23. package/dist/v2/components/ZoneCard/AddResourceButton.d.ts +8 -0
  24. package/dist/v2/components/ZoneCard/AddResourceButton.d.ts.map +1 -0
  25. package/dist/v2/components/ZoneCard/AddResourceButton.js +17 -0
  26. package/dist/v2/components/ZoneCard/ResourceRow.d.ts +28 -0
  27. package/dist/v2/components/ZoneCard/ResourceRow.d.ts.map +1 -0
  28. package/dist/v2/components/ZoneCard/ResourceRow.js +191 -0
  29. package/dist/v2/components/ZoneCard/ZoneCard.d.ts +25 -0
  30. package/dist/v2/components/ZoneCard/ZoneCard.d.ts.map +1 -0
  31. package/dist/v2/components/ZoneCard/ZoneCard.js +43 -0
  32. package/dist/v2/components/ZoneCard/ZoneCard.scss.js +6 -0
  33. package/dist/v2/components/ZoneCard/ZoneContainer.d.ts +12 -0
  34. package/dist/v2/components/ZoneCard/ZoneContainer.d.ts.map +1 -0
  35. package/dist/v2/components/ZoneCard/ZoneContainer.js +16 -0
  36. package/dist/v2/components/ZoneCard/ZoneHeader.d.ts +20 -0
  37. package/dist/v2/components/ZoneCard/ZoneHeader.d.ts.map +1 -0
  38. package/dist/v2/components/ZoneCard/ZoneHeader.js +149 -0
  39. package/dist/v2/components/ZoneCard/index.d.ts +11 -0
  40. package/dist/v2/components/ZoneCard/index.d.ts.map +1 -0
  41. package/dist/v2/components/index.d.ts +5 -1
  42. package/dist/v2/components/index.d.ts.map +1 -1
  43. package/dist/v2/icons/index.js +57 -1
  44. package/dist/v2/index.d.ts +10 -0
  45. package/dist/v2/index.d.ts.map +1 -1
  46. package/dist/v2/pages/Settings/components/SidebarCustomisation.js +5 -0
  47. package/dist/v2/pages/Settings/components/SidebarNavItem.js +5 -0
  48. package/dist/v2/pages/ZonesResources/ZonesResourcesPage.d.ts +12 -0
  49. package/dist/v2/pages/ZonesResources/ZonesResourcesPage.d.ts.map +1 -0
  50. package/dist/v2/pages/ZonesResources/ZonesResourcesPage.js +47 -0
  51. package/dist/v2/pages/ZonesResources/ZonesResourcesPage.scss.js +6 -0
  52. package/dist/v2/pages/ZonesResources/index.d.ts +3 -0
  53. package/dist/v2/pages/ZonesResources/index.d.ts.map +1 -0
  54. package/dist/v2/styles/form/NewInput.scss.js +1 -1
  55. package/package.json +1 -1
  56. package/src/index.ts +4 -1
  57. package/src/v2/components/ActionMenu/ActionMenu.scss +7 -20
  58. package/src/v2/components/ActionMenu/ActionMenu.stories.tsx +58 -0
  59. package/src/v2/components/ActionMenu/ActionMenu.tsx +3 -6
  60. package/src/v2/components/Checkbox/Checkbox.scss +3 -3
  61. package/src/v2/components/FormField/FormField.scss +20 -1
  62. package/src/v2/components/FormField/FormField.tsx +33 -19
  63. package/src/v2/components/IconButton/IconButton.scss +81 -0
  64. package/src/v2/components/IconButton/IconButton.stories.tsx +51 -0
  65. package/src/v2/components/IconButton/IconButton.tsx +50 -0
  66. package/src/v2/components/IconButton/index.ts +2 -0
  67. package/src/v2/components/NewInput/NewInput.tsx +5 -2
  68. package/src/v2/components/NewPageHeader/NewPageHeader.scss +1 -5
  69. package/src/v2/components/NewPageHeader/NewPageHeader.stories.tsx +28 -0
  70. package/src/v2/components/ZoneCard/AddResourceButton.tsx +20 -0
  71. package/src/v2/components/ZoneCard/ResourceRow.tsx +115 -0
  72. package/src/v2/components/ZoneCard/ZoneCard.scss +226 -0
  73. package/src/v2/components/ZoneCard/ZoneCard.stories.tsx +137 -0
  74. package/src/v2/components/ZoneCard/ZoneCard.tsx +69 -0
  75. package/src/v2/components/ZoneCard/ZoneContainer.tsx +26 -0
  76. package/src/v2/components/ZoneCard/ZoneHeader.tsx +84 -0
  77. package/src/v2/components/ZoneCard/index.ts +14 -0
  78. package/src/v2/components/index.ts +11 -1
  79. package/src/v2/index.ts +21 -0
  80. package/src/v2/pages/ZonesResources/ZonesResourcesPage.scss +43 -0
  81. package/src/v2/pages/ZonesResources/ZonesResourcesPage.stories.tsx +46 -0
  82. package/src/v2/pages/ZonesResources/ZonesResourcesPage.tsx +49 -0
  83. package/src/v2/pages/ZonesResources/index.ts +2 -0
  84. package/src/v2/styles/components/Button.scss +1 -0
  85. package/src/v2/styles/form/NewInput.scss +39 -6
@@ -3,9 +3,8 @@
3
3
  .checkbox {
4
4
  display: flex;
5
5
  max-width: 500px;
6
- align-items: flex-start;
6
+ align-items: center;
7
7
  gap: 11px;
8
- align-self: stretch;
9
8
 
10
9
  @media (min-width: 992px) {
11
10
  max-width: unset;
@@ -13,11 +12,12 @@
13
12
 
14
13
  &__wrapper {
15
14
  display: flex;
16
- align-items: flex-start;
15
+ align-items: center;
17
16
  gap: 11px;
18
17
  cursor: pointer;
19
18
  user-select: none;
20
19
  width: 100%;
20
+ margin: 0;
21
21
  }
22
22
 
23
23
  &__input {
@@ -33,7 +33,7 @@
33
33
  padding: 8px 10px;
34
34
  height: 40px;
35
35
  font-family: var(--font-family-sans, 'Geist', sans-serif);
36
- font-size: var(--text-regular-size, 15px);
36
+ font-size: var(--text-regular-size, 16px);
37
37
  line-height: var(--text-regular-line, 20px);
38
38
  color: var(--label-primary, #121e52);
39
39
  background-color: var(--surface-secondary, #f8f8fa);
@@ -66,6 +66,14 @@
66
66
  cursor: not-allowed;
67
67
  opacity: 0.6;
68
68
  }
69
+
70
+ &--on-surface {
71
+ background-color: var(--surface-primary, #fff);
72
+
73
+ &:focus {
74
+ background-color: var(--surface-primary, #fff);
75
+ }
76
+ }
69
77
  }
70
78
 
71
79
  &__help-text {
@@ -81,3 +89,14 @@
81
89
  }
82
90
  }
83
91
 
92
+ // Auto-detect: swap input backgrounds when inside a surface-secondary container
93
+ .surface-context--secondary {
94
+ .form-field__input {
95
+ background-color: var(--surface-primary, #fff);
96
+
97
+ &:focus {
98
+ background-color: var(--surface-primary, #fff);
99
+ }
100
+ }
101
+ }
102
+
@@ -4,30 +4,44 @@ import './FormField.scss'
4
4
  export interface FormFieldProps extends InputHTMLAttributes<HTMLInputElement> {
5
5
  label?: string
6
6
  error?: string
7
+ hasError?: boolean
8
+ success?: boolean
7
9
  helpText?: string
8
10
  required?: boolean
11
+ variant?: 'default' | 'on-surface'
9
12
  }
10
13
 
11
14
  export const FormField = forwardRef<HTMLInputElement, FormFieldProps>(
12
- ({ label, error, helpText, required, className = '', ...props }, ref) => (
13
- <div className={`form-field ${className}`}>
14
- {label && (
15
- <label className="form-field__label">
16
- {label}
17
- {required && <span className="form-field__required">*</span>}
18
- </label>
19
- )}
20
- <input
21
- ref={ref}
22
- className={`form-field__input ${error ? 'form-field__input--error' : ''}`}
23
- {...props}
24
- />
25
- {helpText && !error && (
26
- <span className="form-field__help-text">{helpText}</span>
27
- )}
28
- {error && <span className="form-field__error-text">{error}</span>}
29
- </div>
30
- )
15
+ ({ label, error, hasError, success, helpText, required, variant, className = '', ...props }, ref) => {
16
+ const showError = error || hasError
17
+ const showSuccess = success && !showError
18
+ const inputClassName = [
19
+ 'form-field__input',
20
+ showError ? 'form-field__input--error' : '',
21
+ showSuccess ? 'form-field__input--success' : '',
22
+ variant === 'on-surface' ? 'form-field__input--on-surface' : '',
23
+ ].filter(Boolean).join(' ')
24
+
25
+ return (
26
+ <div className={`form-field ${className}`}>
27
+ {label && (
28
+ <label className="form-field__label">
29
+ {label}
30
+ {required && <span className="form-field__required">*</span>}
31
+ </label>
32
+ )}
33
+ <input
34
+ ref={ref}
35
+ className={inputClassName}
36
+ {...props}
37
+ />
38
+ {helpText && !showError && (
39
+ <span className="form-field__help-text">{helpText}</span>
40
+ )}
41
+ {error && <span className="form-field__error-text">{error}</span>}
42
+ </div>
43
+ )
44
+ }
31
45
  )
32
46
 
33
47
  FormField.displayName = 'FormField'
@@ -0,0 +1,81 @@
1
+ .icon-button {
2
+ display: inline-flex;
3
+ align-items: center;
4
+ width: fit-content;
5
+ gap: var(--spacing-sm, 8px);
6
+ padding: 0 16px 0 12px;
7
+ height: 36px;
8
+ background: transparent;
9
+ border: 1px solid var(--border-primary);
10
+ border-radius: 8px;
11
+ cursor: pointer;
12
+ transition: all 0.2s ease;
13
+ font-family: var(--font-family-sans, 'Geist', sans-serif);
14
+
15
+ @media (max-width: 768px) {
16
+ height: 44px;
17
+ padding: 0 20px 0 16px;
18
+ }
19
+
20
+ &:hover {
21
+ background: var(--surface-secondary);
22
+ color: var(--label-primary);
23
+
24
+ .icon-button__icon {
25
+ color: var(--label-primary);
26
+ }
27
+
28
+ .icon-button__label {
29
+ color: var(--label-primary);
30
+ }
31
+ }
32
+
33
+ &:active {
34
+ background: var(--surface-tertiary);
35
+ }
36
+
37
+ &__icon {
38
+ display: flex;
39
+ align-items: center;
40
+ color: var(--label-primary);
41
+ }
42
+
43
+ &__label {
44
+ font-size: 13px;
45
+ font-weight: 500;
46
+ line-height: 16px;
47
+ color: var(--label-primary);
48
+ }
49
+ }
50
+
51
+ // Icon-only variant (no label)
52
+ .icon-button--icon-only {
53
+ display: inline-flex;
54
+ align-items: center;
55
+ justify-content: center;
56
+ width: 32px;
57
+ height: 32px;
58
+ border: 1px solid var(--border-primary);
59
+ border-radius: 8px;
60
+ background: transparent;
61
+ color: var(--label-primary);
62
+ cursor: pointer;
63
+ transition: all 0.2s ease;
64
+
65
+ &:hover {
66
+ background: var(--surface-primary, #fff);
67
+ }
68
+
69
+ &:active {
70
+ background: var(--surface-tertiary);
71
+ }
72
+ }
73
+
74
+ // Danger variant
75
+ .icon-button--danger {
76
+ color: var(--label-danger, #cc3c35);
77
+
78
+ &:hover {
79
+ background: var(--surface-primary, #fff);
80
+ }
81
+ }
@@ -0,0 +1,51 @@
1
+ import React from 'react'
2
+ import { Meta, StoryObj } from '@storybook/react'
3
+ import { IconButton } from './IconButton'
4
+
5
+ export default {
6
+ title: 'v2/Components/IconButton',
7
+ component: IconButton,
8
+ } as Meta<typeof IconButton>
9
+
10
+ type Story = StoryObj<typeof IconButton>
11
+
12
+ export const BackWithLabel: Story = {
13
+ args: {
14
+ label: 'Go Back',
15
+ icon: 'back',
16
+ },
17
+ }
18
+
19
+ export const AddWithLabel: Story = {
20
+ args: {
21
+ label: 'Add Item',
22
+ icon: 'add',
23
+ },
24
+ }
25
+
26
+ export const DeleteWithLabel: Story = {
27
+ args: {
28
+ label: 'Remove Item',
29
+ icon: 'delete',
30
+ variant: 'danger',
31
+ },
32
+ }
33
+
34
+ export const IconOnlyBack: Story = {
35
+ args: {
36
+ icon: 'back',
37
+ },
38
+ }
39
+
40
+ export const IconOnlyAdd: Story = {
41
+ args: {
42
+ icon: 'add',
43
+ },
44
+ }
45
+
46
+ export const IconOnlyDelete: Story = {
47
+ args: {
48
+ icon: 'delete',
49
+ variant: 'danger',
50
+ },
51
+ }
@@ -0,0 +1,50 @@
1
+ import { ButtonHTMLAttributes, ReactNode } from 'react'
2
+ import { ArrowLeftIcon, PlusIcon, DeleteIcon } from '../../icons'
3
+ import './IconButton.scss'
4
+
5
+ export interface IconButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
6
+ label?: string
7
+ icon?: 'back' | 'add' | 'delete' | ReactNode
8
+ variant?: 'default' | 'danger'
9
+ }
10
+
11
+ export function IconButton({
12
+ label,
13
+ icon = 'back',
14
+ variant = 'default',
15
+ className = '',
16
+ ...props
17
+ }: IconButtonProps) {
18
+ const isIconOnly = !label
19
+ const classes = [
20
+ isIconOnly ? 'icon-button--icon-only' : 'icon-button',
21
+ variant === 'danger' ? 'icon-button--danger' : '',
22
+ className,
23
+ ].filter(Boolean).join(' ')
24
+
25
+ const iconSize = isIconOnly ? 32 : 16
26
+
27
+ const renderIcon = () => {
28
+ if (icon === 'back') return ArrowLeftIcon({ width: iconSize, height: iconSize })
29
+ if (icon === 'add') return PlusIcon({ width: iconSize, height: iconSize })
30
+ if (icon === 'delete') return DeleteIcon({ width: iconSize, height: iconSize })
31
+ return icon
32
+ }
33
+
34
+ if (isIconOnly) {
35
+ return (
36
+ <button type="button" className={classes} {...props}>
37
+ {renderIcon()}
38
+ </button>
39
+ )
40
+ }
41
+
42
+ return (
43
+ <button className={classes} {...props}>
44
+ <span className="icon-button__icon">
45
+ {renderIcon()}
46
+ </span>
47
+ <span className="icon-button__label">{label}</span>
48
+ </button>
49
+ )
50
+ }
@@ -0,0 +1,2 @@
1
+ export { IconButton } from './IconButton'
2
+ export type { IconButtonProps } from './IconButton'
@@ -10,6 +10,7 @@ type CommonInputProps = {
10
10
  iconPosition?: 'left' | 'right';
11
11
  required?: boolean;
12
12
  onIconClick?: () => void;
13
+ variant?: 'default' | 'on-surface';
13
14
  }
14
15
 
15
16
  type InputProps = CommonInputProps & {
@@ -23,13 +24,14 @@ type TextareaProps = CommonInputProps & {
23
24
  export type NewInputProps = InputProps | TextareaProps
24
25
 
25
26
  export const NewInput = forwardRef<HTMLInputElement | HTMLTextAreaElement, NewInputProps>(
26
- ({label, optional, helperText, error, icon, iconPosition = 'left', as = 'input', required, className = '', onIconClick, ...props}, ref) => {
27
+ ({label, optional, helperText, error, icon, iconPosition = 'left', as = 'input', required, variant, className = '', onIconClick, ...props}, ref) => {
27
28
  const inputRef = React.useRef<HTMLInputElement>(null);
28
29
  const isError = !!error;
29
30
  const containerClasses = [
30
31
  'new-form-input',
31
32
  isError ? 'new-form-input--error' : '',
32
33
  props.disabled ? 'new-form-input--disabled' : '',
34
+ variant === 'on-surface' ? 'new-form-input--on-surface' : '',
33
35
  className
34
36
  ].filter(Boolean).join(' ');
35
37
 
@@ -94,7 +96,8 @@ export const NewInput = forwardRef<HTMLInputElement | HTMLTextAreaElement, NewIn
94
96
  <div className={containerClasses}>
95
97
  {(label || optional) && (
96
98
  <label className="new-form-input__label">
97
- {label} {optional && <span className="new-form-input__label-optional">(Optional)</span>}
99
+ <span>{label}</span>
100
+ {optional && <span className="new-form-input__label-optional">(optional)</span>}
98
101
  </label>
99
102
  )}
100
103
 
@@ -7,13 +7,9 @@
7
7
  padding: 32px 32px 0px 32px;
8
8
 
9
9
  @media (max-width: 768px) {
10
- padding: 16px 16px 0px 16px;
10
+ padding: 16px 0 0 0;
11
11
  gap: 0;
12
12
  }
13
-
14
- @media (max-width: 480px) {
15
- padding: 16px 12px 0px 12px;
16
- }
17
13
  }
18
14
 
19
15
  .new-page-header {
@@ -1,5 +1,6 @@
1
1
  import type { Meta, StoryObj } from '@storybook/react'
2
2
  import { NewPageHeader } from './NewPageHeader'
3
+ import { Button, ButtonText } from '../Button'
3
4
 
4
5
  const meta: Meta<typeof NewPageHeader> = {
5
6
  title: 'V2/Components/NewPageHeader',
@@ -42,3 +43,30 @@ export const CustomCancelLabel: Story = {
42
43
  onCancel: () => alert('Go Back clicked'),
43
44
  },
44
45
  }
46
+
47
+ export const WithAction: Story = {
48
+ args: {
49
+ title: 'Zones & Resources',
50
+ renderRight: () => (
51
+ <Button variant="primary" onClick={() => alert('Add Zone clicked')}>
52
+ <ButtonText color="white">+ Add Zone</ButtonText>
53
+ </Button>
54
+ ),
55
+ },
56
+ }
57
+
58
+ export const WithMultipleActions: Story = {
59
+ args: {
60
+ title: 'Zones & Resources',
61
+ renderRight: () => (
62
+ <>
63
+ <Button variant="secondary" onClick={() => alert('Export clicked')}>
64
+ <ButtonText color="action">Export</ButtonText>
65
+ </Button>
66
+ <Button variant="primary" onClick={() => alert('Add Zone clicked')}>
67
+ <ButtonText color="white">+ Add Zone</ButtonText>
68
+ </Button>
69
+ </>
70
+ ),
71
+ },
72
+ }
@@ -0,0 +1,20 @@
1
+ import React from 'react'
2
+ import { IconButton } from '../IconButton'
3
+
4
+ export interface AddResourceButtonProps {
5
+ label?: string
6
+ onClick: () => void
7
+ className?: string
8
+ }
9
+
10
+ export const AddResourceButton: React.FC<AddResourceButtonProps> = ({
11
+ label = 'Add Resource',
12
+ onClick,
13
+ className = '',
14
+ }) => {
15
+ return (
16
+ <div className={`zone-card__add-resource-wrapper ${className}`}>
17
+ <IconButton label={label} icon='add' onClick={onClick} />
18
+ </div>
19
+ )
20
+ }
@@ -0,0 +1,115 @@
1
+ import React from 'react'
2
+ import { ActionMenu, ActionMenuItem } from '../ActionMenu'
3
+ import { GripVerticalIcon, ArrowUpIcon, ArrowDownIcon } from '../../icons'
4
+ import { DragHandleProps } from './ZoneContainer'
5
+ import './ZoneCard.scss'
6
+
7
+ export interface ResourceRowLabels {
8
+ quantity?: string
9
+ capacity?: string
10
+ total?: string
11
+ }
12
+
13
+ export interface ResourceRowProps {
14
+ name: string
15
+ quantity: number
16
+ capacity: number
17
+ labels?: ResourceRowLabels
18
+ menuItems?: ActionMenuItem[]
19
+ draggable?: boolean
20
+ dragHandleProps?: DragHandleProps
21
+ onClick?: () => void
22
+ className?: string
23
+ style?: React.CSSProperties
24
+ isMobile?: boolean
25
+ isFirst?: boolean
26
+ isLast?: boolean
27
+ onMoveUp?: () => void
28
+ onMoveDown?: () => void
29
+ }
30
+
31
+ export const ResourceRow = React.forwardRef<HTMLDivElement, ResourceRowProps>(({
32
+ name,
33
+ quantity,
34
+ capacity,
35
+ labels = {},
36
+ menuItems = [],
37
+ draggable = true,
38
+ dragHandleProps,
39
+ onClick,
40
+ className = '',
41
+ style,
42
+ isMobile = false,
43
+ isFirst = false,
44
+ isLast = false,
45
+ onMoveUp,
46
+ onMoveDown,
47
+ }, ref) => {
48
+ const qtyLabel = labels.quantity ?? 'Qty'
49
+ const capLabel = labels.capacity ?? 'Cap'
50
+ const totalLabel = labels.total ?? 'Total'
51
+ const classes = [
52
+ 'zone-card__resource',
53
+ onClick ? 'zone-card__resource--clickable' : '',
54
+ isMobile ? 'zone-card__resource--mobile' : '',
55
+ className,
56
+ ].filter(Boolean).join(' ')
57
+
58
+ return (
59
+ <div ref={ref} className={classes} style={style} onClick={onClick}>
60
+ {draggable && !isMobile && (
61
+ <div
62
+ className="zone-card__drag-handle zone-card__drag-handle--resource"
63
+ {...dragHandleProps}
64
+ onClick={(e) => e.stopPropagation()}
65
+ >
66
+ {GripVerticalIcon({ width: 16, height: 16 })}
67
+ </div>
68
+ )}
69
+ {draggable && isMobile && (
70
+ <div className="zone-card__reorder-arrows zone-card__reorder-arrows--resource" onClick={(e) => e.stopPropagation()}>
71
+ <button
72
+ className="zone-card__arrow-btn"
73
+ disabled={isFirst}
74
+ onClick={(e) => { e.stopPropagation(); onMoveUp?.() }}
75
+ >
76
+ <ArrowUpIcon />
77
+ </button>
78
+ <button
79
+ className="zone-card__arrow-btn"
80
+ disabled={isLast}
81
+ onClick={(e) => { e.stopPropagation(); onMoveDown?.() }}
82
+ >
83
+ <ArrowDownIcon />
84
+ </button>
85
+ </div>
86
+ )}
87
+ {isMobile ? (
88
+ <div className="zone-card__resource-info">
89
+ <span className="zone-card__resource-name">{name}</span>
90
+ <span className="zone-card__resource-stats-mobile">
91
+ {qtyLabel}: {quantity} &middot; {capLabel}: {capacity} &middot; {totalLabel}: {quantity * capacity}
92
+ </span>
93
+ </div>
94
+ ) : (
95
+ <>
96
+ <span className="zone-card__resource-name">{name}</span>
97
+ <div className="zone-card__resource-stats">
98
+ <span className="zone-card__resource-stat">{qtyLabel}: {quantity}</span>
99
+ <span className="zone-card__resource-stat">{capLabel}: {capacity}</span>
100
+ <span className="zone-card__resource-stat zone-card__resource-stat--total">
101
+ {totalLabel}: {quantity * capacity}
102
+ </span>
103
+ </div>
104
+ </>
105
+ )}
106
+ {menuItems.length > 0 && (
107
+ <div className="zone-card__resource-actions" onClick={(e) => e.stopPropagation()}>
108
+ <ActionMenu items={menuItems} />
109
+ </div>
110
+ )}
111
+ </div>
112
+ )
113
+ })
114
+
115
+ ResourceRow.displayName = 'ResourceRow'