@tuturuuu/ui 0.4.1 → 0.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.
Files changed (107) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/package.json +41 -34
  3. package/src/components/ui/currency-input.tsx +65 -23
  4. package/src/components/ui/custom/__tests__/sidebar-context.test.tsx +64 -0
  5. package/src/components/ui/custom/__tests__/sidebar-remote-behavior-bridge.test.tsx +109 -0
  6. package/src/components/ui/custom/combobox.test.tsx +141 -0
  7. package/src/components/ui/custom/combobox.tsx +105 -36
  8. package/src/components/ui/custom/settings/task-settings.tsx +126 -0
  9. package/src/components/ui/custom/settings/task-sound-settings.test.tsx +146 -0
  10. package/src/components/ui/custom/sidebar-context.tsx +68 -6
  11. package/src/components/ui/custom/sidebar-remote-behavior-bridge.tsx +21 -2
  12. package/src/components/ui/finance/finance-layout.tsx +2 -4
  13. package/src/components/ui/finance/shared/balance-mode-toggle.tsx +35 -0
  14. package/src/components/ui/finance/shared/finance-layout-controls.tsx +43 -0
  15. package/src/components/ui/finance/shared/quick-actions.tsx +14 -6
  16. package/src/components/ui/finance/shared/use-finance-balance-mode.ts +72 -0
  17. package/src/components/ui/finance/shared/wallet-balance-mode.test.ts +66 -0
  18. package/src/components/ui/finance/shared/wallet-balance-mode.ts +42 -0
  19. package/src/components/ui/finance/transactions/form-types.ts +23 -0
  20. package/src/components/ui/finance/transactions/form.tsx +81 -22
  21. package/src/components/ui/finance/transactions/infinite-transactions-list.tsx +29 -18
  22. package/src/components/ui/finance/transactions/transaction-card.tsx +75 -43
  23. package/src/components/ui/finance/transactions/transfer-merge.test.ts +90 -0
  24. package/src/components/ui/finance/transactions/transfer-merge.ts +52 -0
  25. package/src/components/ui/finance/transactions/wallet-filter.tsx +21 -2
  26. package/src/components/ui/finance/wallets/checkpoints/wallet-checkpoint-adjustment-dialog.tsx +219 -0
  27. package/src/components/ui/finance/wallets/checkpoints/wallet-checkpoint-amount.tsx +32 -0
  28. package/src/components/ui/finance/wallets/checkpoints/wallet-checkpoint-delete-dialog.tsx +50 -0
  29. package/src/components/ui/finance/wallets/checkpoints/wallet-checkpoint-dialog.tsx +138 -0
  30. package/src/components/ui/finance/wallets/checkpoints/wallet-checkpoint-history-dialog.tsx +617 -0
  31. package/src/components/ui/finance/wallets/checkpoints/wallet-checkpoint-panel.tsx +197 -0
  32. package/src/components/ui/finance/wallets/checkpoints/wallet-checkpoint-sections.tsx +201 -0
  33. package/src/components/ui/finance/wallets/checkpoints/wallet-checkpoints.test.tsx +541 -0
  34. package/src/components/ui/finance/wallets/checkpoints/wallet-total-check-dialog.tsx +362 -0
  35. package/src/components/ui/finance/wallets/columns-rendering.test.tsx +125 -0
  36. package/src/components/ui/finance/wallets/columns.test.ts +56 -0
  37. package/src/components/ui/finance/wallets/columns.tsx +196 -43
  38. package/src/components/ui/finance/wallets/form.test.tsx +79 -14
  39. package/src/components/ui/finance/wallets/form.tsx +41 -197
  40. package/src/components/ui/finance/wallets/query-invalidation.ts +3 -0
  41. package/src/components/ui/finance/wallets/wallet-basics-fields.tsx +141 -0
  42. package/src/components/ui/finance/wallets/wallet-credit-fields.tsx +136 -0
  43. package/src/components/ui/finance/wallets/walletId/credit-wallet-summary.tsx +143 -68
  44. package/src/components/ui/finance/wallets/walletId/wallet-details-actions.test.tsx +105 -0
  45. package/src/components/ui/finance/wallets/walletId/wallet-details-actions.tsx +120 -16
  46. package/src/components/ui/finance/wallets/walletId/wallet-details-amount.test.tsx +64 -0
  47. package/src/components/ui/finance/wallets/walletId/wallet-details-amount.tsx +226 -6
  48. package/src/components/ui/finance/wallets/walletId/wallet-details-page.test.tsx +71 -5
  49. package/src/components/ui/finance/wallets/walletId/wallet-details-page.tsx +52 -35
  50. package/src/components/ui/finance/wallets/wallets-data-table.test.tsx +171 -0
  51. package/src/components/ui/finance/wallets/wallets-data-table.tsx +132 -29
  52. package/src/components/ui/finance/wallets/wallets-page.test.tsx +117 -36
  53. package/src/components/ui/finance/wallets/wallets-page.tsx +40 -64
  54. package/src/components/ui/storefront/accent-button.tsx +33 -0
  55. package/src/components/ui/storefront/cart-summary.tsx +140 -0
  56. package/src/components/ui/storefront/empty-listings.tsx +32 -0
  57. package/src/components/ui/storefront/hero-panel.tsx +70 -0
  58. package/src/components/ui/storefront/image-panel.tsx +40 -0
  59. package/src/components/ui/storefront/index.ts +12 -0
  60. package/src/components/ui/storefront/listing-card.tsx +129 -0
  61. package/src/components/ui/storefront/storefront-surface.test.tsx +85 -0
  62. package/src/components/ui/storefront/storefront-surface.tsx +235 -0
  63. package/src/components/ui/storefront/types.ts +99 -0
  64. package/src/components/ui/storefront/utils.ts +90 -0
  65. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/__tests__/bulk-mutations-move.test.tsx +14 -0
  66. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-operations.ts +29 -0
  67. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-open-options.test.ts +134 -0
  68. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-open-options.ts +127 -0
  69. package/src/components/ui/tu-do/boards/boardId/task-card/task-card.tsx +17 -42
  70. package/src/components/ui/tu-do/boards/boardId/timeline-board-open-task.test.tsx +164 -0
  71. package/src/components/ui/tu-do/boards/boardId/timeline-board.tsx +25 -16
  72. package/src/components/ui/tu-do/hooks/useTaskDialog.ts +15 -1
  73. package/src/components/ui/tu-do/my-tasks/__tests__/use-task-context-actions.test.ts +11 -0
  74. package/src/components/ui/tu-do/my-tasks/use-my-tasks-state.ts +2 -0
  75. package/src/components/ui/tu-do/my-tasks/use-task-context-actions.ts +124 -7
  76. package/src/components/ui/tu-do/providers/__tests__/task-dialog-provider.test.tsx +217 -5
  77. package/src/components/ui/tu-do/providers/task-dialog-provider.tsx +180 -35
  78. package/src/components/ui/tu-do/shared/__tests__/task-dialog-manager.test.tsx +222 -26
  79. package/src/components/ui/tu-do/shared/board-client.tsx +1 -3
  80. package/src/components/ui/tu-do/shared/list-view-context-menu.test.tsx +55 -2
  81. package/src/components/ui/tu-do/shared/list-view.tsx +23 -16
  82. package/src/components/ui/tu-do/shared/task-dialog-manager.tsx +93 -76
  83. package/src/components/ui/tu-do/shared/task-dialog-presentation.ts +11 -0
  84. package/src/components/ui/tu-do/shared/task-edit-dialog/components/compact-task-create-popover.test.tsx +268 -0
  85. package/src/components/ui/tu-do/shared/task-edit-dialog/components/compact-task-create-popover.tsx +243 -0
  86. package/src/components/ui/tu-do/shared/task-edit-dialog/components/quick-settings-popover.tsx +26 -0
  87. package/src/components/ui/tu-do/shared/task-edit-dialog/components/smart-task-suggestions-panel.test.tsx +129 -0
  88. package/src/components/ui/tu-do/shared/task-edit-dialog/components/smart-task-suggestions-panel.tsx +358 -0
  89. package/src/components/ui/tu-do/shared/task-edit-dialog/components/task-description-editor.tsx +1 -1
  90. package/src/components/ui/tu-do/shared/task-edit-dialog/components/task-dialog-header.tsx +6 -2
  91. package/src/components/ui/tu-do/shared/task-edit-dialog/components/task-list-selector.tsx +36 -20
  92. package/src/components/ui/tu-do/shared/task-edit-dialog/components/task-name-input.test.tsx +41 -1
  93. package/src/components/ui/tu-do/shared/task-edit-dialog/components/task-name-input.tsx +157 -102
  94. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-form-reset.ts +18 -2
  95. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-realtime-sync.ts +1 -2
  96. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-save.test.ts +84 -1
  97. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-save.ts +5 -1
  98. package/src/components/ui/tu-do/shared/task-edit-dialog/task-dialog-actions.tsx +5 -3
  99. package/src/components/ui/tu-do/shared/task-edit-dialog/task-properties-section.tsx +300 -172
  100. package/src/components/ui/tu-do/shared/task-edit-dialog.tsx +959 -340
  101. package/src/components/ui/tu-do/shared/task-sound-effects.test.ts +189 -0
  102. package/src/components/ui/tu-do/shared/task-sound-effects.tsx +468 -0
  103. package/src/hooks/__tests__/use-task-actions.test.tsx +61 -0
  104. package/src/hooks/use-task-actions.ts +45 -0
  105. package/src/hooks/useBoardRealtime.ts +54 -1
  106. package/src/hooks/useBoardRealtimeEventHandler.ts +169 -4
  107. package/src/hooks/useTaskUserRealtime.ts +338 -0
@@ -36,11 +36,19 @@ import { Label } from '@tuturuuu/ui/label';
36
36
  import { Popover, PopoverContent, PopoverTrigger } from '@tuturuuu/ui/popover';
37
37
  import { Progress } from '@tuturuuu/ui/progress';
38
38
  import { Switch } from '@tuturuuu/ui/switch';
39
+ import { Tooltip, TooltipContent, TooltipTrigger } from '@tuturuuu/ui/tooltip';
39
40
  import { cn } from '@tuturuuu/utils/format';
40
41
  import { computeAccessibleLabelStyles } from '@tuturuuu/utils/label-colors';
41
42
  import dayjs from 'dayjs';
42
43
  import { useTranslations } from 'next-intl';
43
- import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
44
+ import {
45
+ type ReactNode,
46
+ useCallback,
47
+ useEffect,
48
+ useMemo,
49
+ useRef,
50
+ useState,
51
+ } from 'react';
44
52
  import { PRIORITY_BADGE_COLORS } from '../../utils/taskConstants';
45
53
  import { getPriorityIcon } from '../../utils/taskPriorityUtils';
46
54
  import { ClearMenuItem } from '../clear-menu-item';
@@ -139,6 +147,31 @@ interface TaskPropertiesSectionProps {
139
147
  disabled?: boolean;
140
148
  /** When true, hides fields not supported by drafts (projects, scheduling) */
141
149
  isDraftMode?: boolean;
150
+ /** Compact icon-only controls for the create popover. */
151
+ variant?: 'default' | 'compact';
152
+ }
153
+
154
+ function TaskPropertyPopoverTrigger({
155
+ children,
156
+ compact,
157
+ label,
158
+ }: {
159
+ children: ReactNode;
160
+ compact: boolean;
161
+ label: ReactNode;
162
+ }) {
163
+ if (!compact) {
164
+ return <PopoverTrigger asChild>{children}</PopoverTrigger>;
165
+ }
166
+
167
+ return (
168
+ <Tooltip>
169
+ <TooltipTrigger asChild>
170
+ <PopoverTrigger asChild>{children}</PopoverTrigger>
171
+ </TooltipTrigger>
172
+ <TooltipContent side="bottom">{label}</TooltipContent>
173
+ </Tooltip>
174
+ );
142
175
  }
143
176
 
144
177
  // Calendar hours type options
@@ -325,9 +358,18 @@ export function TaskPropertiesSection(props: TaskPropertiesSectionProps) {
325
358
  schedulingSaving,
326
359
  disabled = false,
327
360
  isDraftMode = false,
361
+ variant = 'default',
328
362
  } = props;
329
363
 
330
364
  const t = useTranslations();
365
+ const isCompact = variant === 'compact';
366
+ const triggerBaseClass = cn(
367
+ 'inline-flex shrink-0 items-center border font-medium text-xs transition-colors',
368
+ isCompact
369
+ ? 'h-9 w-9 justify-center rounded-md p-0'
370
+ : 'h-8 gap-1.5 rounded-lg px-3'
371
+ );
372
+ const compactLabelClass = isCompact ? 'sr-only' : undefined;
331
373
 
332
374
  const selectedListForSummary = useMemo(
333
375
  () =>
@@ -670,163 +712,184 @@ export function TaskPropertiesSection(props: TaskPropertiesSectionProps) {
670
712
  );
671
713
 
672
714
  return (
673
- <div className="border-y bg-muted/30">
715
+ <div className={cn(!isCompact && 'border-y bg-muted/30')}>
674
716
  {/* Header with toggle button */}
675
- <button
676
- type="button"
677
- onClick={() => setIsMetadataExpanded(!isMetadataExpanded)}
678
- className="flex w-full items-center justify-between px-4 py-2.5 text-left transition-colors hover:bg-muted/50 md:px-8"
679
- >
680
- <div className="flex min-w-0 flex-1 items-center gap-2">
681
- <ChevronDown
682
- className={cn(
683
- 'h-4 w-4 shrink-0 text-muted-foreground transition-transform duration-200',
684
- !isMetadataExpanded && '-rotate-90'
685
- )}
686
- />
687
- <span className="shrink-0 font-semibold text-foreground text-sm">
688
- {t('ws-task-boards.dialog.properties')}
689
- </span>
690
-
691
- {/* Summary badges when collapsed */}
692
- {!isMetadataExpanded && (
693
- <div className="scrollbar-hide ml-2 flex min-w-0 flex-1 items-center gap-1.5 overflow-x-auto">
694
- {priority && (
695
- <Badge
696
- variant="secondary"
697
- className={cn(
698
- 'h-5 shrink-0 gap-1 border px-2 font-medium text-[10px]',
699
- PRIORITY_BADGE_COLORS[priority]
700
- )}
701
- >
702
- {getPriorityIcon(priority, 'h-2.5 w-2.5')}
703
- {t(`tasks.priority_${priority}`)}
704
- </Badge>
705
- )}
706
- {selectedListId && (
707
- <Badge
708
- variant="secondary"
709
- className={cn(
710
- 'h-5 shrink-0 gap-1 border px-2 font-medium text-[10px]',
711
- listSummarySurfaceClass ??
712
- 'border-border bg-muted/50 text-muted-foreground'
713
- )}
714
- >
715
- <ListSummaryTriggerIcon className="h-2.5 w-2.5" />
716
- {selectedListForSummary
717
- ? translateTaskListNameForDisplay(
718
- selectedListForSummary.name,
719
- listNameLabels
720
- )
721
- : t('ws-task-boards.dialog.field.list')}
722
- </Badge>
723
- )}
724
- {(startDate || endDate) && (
725
- <Badge
726
- variant="secondary"
727
- className="h-5 shrink-0 gap-1 border border-dynamic-orange/30 bg-dynamic-orange/15 px-2 font-medium text-[10px] text-dynamic-orange"
728
- >
729
- <Calendar className="h-2.5 w-2.5" />
730
- {startDate || endDate
731
- ? `${startDate ? new Date(startDate).toLocaleDateString(t('common.locale', { defaultValue: 'en-US' }), { month: 'short', day: 'numeric' }) : t('ws-task-boards.dialog.field.start_date')} → ${endDate ? new Date(endDate).toLocaleDateString(t('common.locale', { defaultValue: 'en-US' }), { month: 'short', day: 'numeric' }) : t('ws-task-boards.dialog.field.end_date')}`
732
- : t('ws-task-boards.dialog.field.end_date')}
733
- </Badge>
734
- )}
735
- {estimationPoints != null && (
736
- <Badge
737
- variant="secondary"
738
- className="h-5 shrink-0 gap-1 border border-dynamic-purple/30 bg-dynamic-purple/15 px-2 font-medium text-[10px] text-dynamic-purple"
739
- >
740
- <Timer className="h-2.5 w-2.5" />
741
- {boardConfig?.estimation_type
742
- ? mapEstimationPoints(
743
- estimationPoints,
744
- boardConfig.estimation_type
745
- )
746
- : t('ws-task-boards.dialog.field.estimation')}
747
- </Badge>
748
- )}
749
- {selectedLabels.length > 0 && (
750
- <Badge
751
- variant="secondary"
752
- className="h-5 shrink-0 gap-1 border border-dynamic-indigo/30 bg-dynamic-indigo/15 px-2 font-medium text-[10px] text-dynamic-indigo"
753
- >
754
- <Tag className="h-2.5 w-2.5" />
755
- {selectedLabels.length === 1
756
- ? selectedLabels[0]?.name
757
- : t('common.n_labels', { count: selectedLabels.length })}
758
- </Badge>
759
- )}
760
- {selectedProjects.length > 0 && (
761
- <Badge
762
- variant="secondary"
763
- className="h-5 shrink-0 gap-1 border border-dynamic-sky/30 bg-dynamic-sky/15 px-2 font-medium text-[10px] text-dynamic-sky"
764
- >
765
- <Box className="h-2.5 w-2.5" />
766
- {selectedProjects.length === 1
767
- ? selectedProjects[0]?.name
768
- : t('common.n_projects', {
769
- count: selectedProjects.length,
770
- })}
771
- </Badge>
772
- )}
773
- {selectedAssignees.length > 0 && !isPersonalWorkspace && (
774
- <Badge
775
- variant="secondary"
776
- className="h-5 shrink-0 gap-1 border border-dynamic-cyan/30 bg-dynamic-cyan/15 px-2 font-medium text-[10px] text-dynamic-cyan"
777
- >
778
- <Users className="h-2.5 w-2.5" />
779
- {selectedAssignees.length === 1
780
- ? selectedAssignees[0]?.display_name ||
781
- t('ws-task-boards.dialog.unknown_user')
782
- : t('common.n_assignees', {
783
- count: selectedAssignees.length,
784
- })}
785
- </Badge>
786
- )}
787
- {totalMinutes > 0 && (
788
- <Badge
789
- variant="secondary"
790
- className={cn(
791
- 'h-5 shrink-0 gap-1 border px-2 font-medium text-[10px]',
792
- !disabled && hasUnsavedSchedulingChanges
793
- ? 'border-dynamic-yellow/50 border-dashed bg-dynamic-yellow/15 text-dynamic-yellow'
794
- : 'border-dynamic-teal/30 bg-dynamic-teal/15 text-dynamic-teal'
795
- )}
796
- >
797
- {!disabled && hasUnsavedSchedulingChanges ? (
798
- <AlertCircle className="h-2.5 w-2.5" />
799
- ) : (
800
- <CalendarClock className="h-2.5 w-2.5" />
801
- )}
802
- {formatDuration(totalMinutes, t)}
803
- {!disabled && hasUnsavedSchedulingChanges && (
804
- <span className="text-[8px] opacity-75">
805
- {t('ws-task-boards.dialog.unsaved')}
806
- </span>
807
- )}
808
- </Badge>
717
+ {!isCompact && (
718
+ <button
719
+ type="button"
720
+ onClick={() => setIsMetadataExpanded(!isMetadataExpanded)}
721
+ className="flex w-full items-center justify-between px-4 py-2.5 text-left transition-colors hover:bg-muted/50 md:px-8"
722
+ >
723
+ <div className="flex min-w-0 flex-1 items-center gap-2">
724
+ <ChevronDown
725
+ className={cn(
726
+ 'h-4 w-4 shrink-0 text-muted-foreground transition-transform duration-200',
727
+ !isMetadataExpanded && '-rotate-90'
809
728
  )}
810
- </div>
811
- )}
812
- </div>
813
- </button>
729
+ />
730
+ <span className="shrink-0 font-semibold text-foreground text-sm">
731
+ {t('ws-task-boards.dialog.properties')}
732
+ </span>
733
+
734
+ {/* Summary badges when collapsed */}
735
+ {!isMetadataExpanded && (
736
+ <div className="scrollbar-hide ml-2 flex min-w-0 flex-1 items-center gap-1.5 overflow-x-auto">
737
+ {priority && (
738
+ <Badge
739
+ variant="secondary"
740
+ className={cn(
741
+ 'h-5 shrink-0 gap-1 border px-2 font-medium text-[10px]',
742
+ PRIORITY_BADGE_COLORS[priority]
743
+ )}
744
+ >
745
+ {getPriorityIcon(priority, 'h-2.5 w-2.5')}
746
+ {t(`tasks.priority_${priority}`)}
747
+ </Badge>
748
+ )}
749
+ {selectedListId && (
750
+ <Badge
751
+ variant="secondary"
752
+ className={cn(
753
+ 'h-5 shrink-0 gap-1 border px-2 font-medium text-[10px]',
754
+ listSummarySurfaceClass ??
755
+ 'border-border bg-muted/50 text-muted-foreground'
756
+ )}
757
+ >
758
+ <ListSummaryTriggerIcon className="h-2.5 w-2.5" />
759
+ {selectedListForSummary
760
+ ? translateTaskListNameForDisplay(
761
+ selectedListForSummary.name,
762
+ listNameLabels
763
+ )
764
+ : t('ws-task-boards.dialog.field.list')}
765
+ </Badge>
766
+ )}
767
+ {(startDate || endDate) && (
768
+ <Badge
769
+ variant="secondary"
770
+ className="h-5 shrink-0 gap-1 border border-dynamic-orange/30 bg-dynamic-orange/15 px-2 font-medium text-[10px] text-dynamic-orange"
771
+ >
772
+ <Calendar className="h-2.5 w-2.5" />
773
+ {startDate || endDate
774
+ ? `${startDate ? new Date(startDate).toLocaleDateString(t('common.locale', { defaultValue: 'en-US' }), { month: 'short', day: 'numeric' }) : t('ws-task-boards.dialog.field.start_date')} → ${endDate ? new Date(endDate).toLocaleDateString(t('common.locale', { defaultValue: 'en-US' }), { month: 'short', day: 'numeric' }) : t('ws-task-boards.dialog.field.end_date')}`
775
+ : t('ws-task-boards.dialog.field.end_date')}
776
+ </Badge>
777
+ )}
778
+ {estimationPoints != null && (
779
+ <Badge
780
+ variant="secondary"
781
+ className="h-5 shrink-0 gap-1 border border-dynamic-purple/30 bg-dynamic-purple/15 px-2 font-medium text-[10px] text-dynamic-purple"
782
+ >
783
+ <Timer className="h-2.5 w-2.5" />
784
+ {boardConfig?.estimation_type
785
+ ? mapEstimationPoints(
786
+ estimationPoints,
787
+ boardConfig.estimation_type
788
+ )
789
+ : t('ws-task-boards.dialog.field.estimation')}
790
+ </Badge>
791
+ )}
792
+ {selectedLabels.length > 0 && (
793
+ <Badge
794
+ variant="secondary"
795
+ className="h-5 shrink-0 gap-1 border border-dynamic-indigo/30 bg-dynamic-indigo/15 px-2 font-medium text-[10px] text-dynamic-indigo"
796
+ >
797
+ <Tag className="h-2.5 w-2.5" />
798
+ {selectedLabels.length === 1
799
+ ? selectedLabels[0]?.name
800
+ : t('common.n_labels', { count: selectedLabels.length })}
801
+ </Badge>
802
+ )}
803
+ {selectedProjects.length > 0 && (
804
+ <Badge
805
+ variant="secondary"
806
+ className="h-5 shrink-0 gap-1 border border-dynamic-sky/30 bg-dynamic-sky/15 px-2 font-medium text-[10px] text-dynamic-sky"
807
+ >
808
+ <Box className="h-2.5 w-2.5" />
809
+ {selectedProjects.length === 1
810
+ ? selectedProjects[0]?.name
811
+ : t('common.n_projects', {
812
+ count: selectedProjects.length,
813
+ })}
814
+ </Badge>
815
+ )}
816
+ {selectedAssignees.length > 0 && !isPersonalWorkspace && (
817
+ <Badge
818
+ variant="secondary"
819
+ className="h-5 shrink-0 gap-1 border border-dynamic-cyan/30 bg-dynamic-cyan/15 px-2 font-medium text-[10px] text-dynamic-cyan"
820
+ >
821
+ <Users className="h-2.5 w-2.5" />
822
+ {selectedAssignees.length === 1
823
+ ? selectedAssignees[0]?.display_name ||
824
+ t('ws-task-boards.dialog.unknown_user')
825
+ : t('common.n_assignees', {
826
+ count: selectedAssignees.length,
827
+ })}
828
+ </Badge>
829
+ )}
830
+ {totalMinutes > 0 && (
831
+ <Badge
832
+ variant="secondary"
833
+ className={cn(
834
+ 'h-5 shrink-0 gap-1 border px-2 font-medium text-[10px]',
835
+ !disabled && hasUnsavedSchedulingChanges
836
+ ? 'border-dynamic-yellow/50 border-dashed bg-dynamic-yellow/15 text-dynamic-yellow'
837
+ : 'border-dynamic-teal/30 bg-dynamic-teal/15 text-dynamic-teal'
838
+ )}
839
+ >
840
+ {!disabled && hasUnsavedSchedulingChanges ? (
841
+ <AlertCircle className="h-2.5 w-2.5" />
842
+ ) : (
843
+ <CalendarClock className="h-2.5 w-2.5" />
844
+ )}
845
+ {formatDuration(totalMinutes, t)}
846
+ {!disabled && hasUnsavedSchedulingChanges && (
847
+ <span className="text-[8px] opacity-75">
848
+ {t('ws-task-boards.dialog.unsaved')}
849
+ </span>
850
+ )}
851
+ </Badge>
852
+ )}
853
+ </div>
854
+ )}
855
+ </div>
856
+ </button>
857
+ )}
814
858
 
815
859
  {/* Expandable badges section */}
816
- {isMetadataExpanded && (
817
- <div className="border-t px-4 py-3 md:px-8">
818
- <div className="flex flex-wrap items-center gap-2">
860
+ {(isCompact || isMetadataExpanded) && (
861
+ <div
862
+ className={cn(isCompact ? 'px-0 py-0' : 'border-t px-4 py-3 md:px-8')}
863
+ >
864
+ <div
865
+ className={cn(
866
+ 'flex flex-wrap items-center',
867
+ isCompact ? 'gap-1.5' : 'gap-2'
868
+ )}
869
+ >
819
870
  {/* Priority Badge */}
820
871
  <Popover
821
872
  open={isPriorityPopoverOpen}
822
873
  onOpenChange={setIsPriorityPopoverOpen}
823
874
  >
824
- <PopoverTrigger asChild>
875
+ <TaskPropertyPopoverTrigger
876
+ compact={isCompact}
877
+ label={
878
+ priority
879
+ ? t(`tasks.priority_${priority}`)
880
+ : t('common.priority')
881
+ }
882
+ >
825
883
  <button
826
884
  type="button"
827
885
  disabled={disabled}
886
+ aria-label={
887
+ priority
888
+ ? t(`tasks.priority_${priority}`)
889
+ : t('common.priority')
890
+ }
828
891
  className={cn(
829
- 'inline-flex h-8 shrink-0 items-center gap-1.5 rounded-lg border px-3 font-medium text-xs transition-colors',
892
+ triggerBaseClass,
830
893
  priority
831
894
  ? PRIORITY_BADGE_COLORS[priority]
832
895
  : 'border-input bg-background text-foreground hover:bg-muted',
@@ -838,13 +901,13 @@ export function TaskPropertiesSection(props: TaskPropertiesSectionProps) {
838
901
  ) : (
839
902
  <Flag className="h-3.5 w-3.5" />
840
903
  )}
841
- <span>
904
+ <span className={compactLabelClass}>
842
905
  {priority
843
906
  ? t(`tasks.priority_${priority}`)
844
907
  : t('common.priority')}
845
908
  </span>
846
909
  </button>
847
- </PopoverTrigger>
910
+ </TaskPropertyPopoverTrigger>
848
911
  <PopoverContent align="start" className="w-56 p-0">
849
912
  <div className="p-1">
850
913
  {[
@@ -910,6 +973,7 @@ export function TaskPropertiesSection(props: TaskPropertiesSectionProps) {
910
973
  selectedListId={selectedListId}
911
974
  availableLists={availableLists}
912
975
  disabled={disabled}
976
+ compact={isCompact}
913
977
  onListChange={onListChange}
914
978
  />
915
979
 
@@ -918,12 +982,20 @@ export function TaskPropertiesSection(props: TaskPropertiesSectionProps) {
918
982
  open={isDueDatePopoverOpen}
919
983
  onOpenChange={setIsDueDatePopoverOpen}
920
984
  >
921
- <PopoverTrigger asChild>
985
+ <TaskPropertyPopoverTrigger
986
+ compact={isCompact}
987
+ label={
988
+ startDate || endDate
989
+ ? `${startDate ? new Date(startDate).toLocaleDateString(t('common.locale', { defaultValue: 'en-US' }), { month: 'short', day: 'numeric' }) : t('ws-task-boards.dialog.no_start_date')} → ${endDate ? new Date(endDate).toLocaleDateString(t('common.locale', { defaultValue: 'en-US' }), { month: 'short', day: 'numeric' }) : t('ws-task-boards.dialog.no_due_date')}`
990
+ : t('ws-task-boards.dialog.dates')
991
+ }
992
+ >
922
993
  <button
923
994
  type="button"
924
995
  disabled={disabled}
996
+ aria-label={t('ws-task-boards.dialog.dates')}
925
997
  className={cn(
926
- 'inline-flex h-8 shrink-0 items-center gap-1.5 rounded-lg border px-3 font-medium text-xs transition-colors',
998
+ triggerBaseClass,
927
999
  startDate || endDate
928
1000
  ? 'border-dynamic-orange/30 bg-dynamic-orange/15 text-dynamic-orange hover:border-dynamic-orange/50 hover:bg-dynamic-orange/20'
929
1001
  : 'border-border bg-background text-muted-foreground hover:border-primary/30 hover:bg-muted hover:text-foreground',
@@ -931,13 +1003,13 @@ export function TaskPropertiesSection(props: TaskPropertiesSectionProps) {
931
1003
  )}
932
1004
  >
933
1005
  <Calendar className="h-3.5 w-3.5" />
934
- <span>
1006
+ <span className={compactLabelClass}>
935
1007
  {startDate || endDate
936
1008
  ? `${startDate ? new Date(startDate).toLocaleDateString(t('common.locale', { defaultValue: 'en-US' }), { month: 'short', day: 'numeric' }) : t('ws-task-boards.dialog.no_start_date')} → ${endDate ? new Date(endDate).toLocaleDateString(t('common.locale', { defaultValue: 'en-US' }), { month: 'short', day: 'numeric' }) : t('ws-task-boards.dialog.no_due_date')}`
937
1009
  : t('ws-task-boards.dialog.dates')}
938
1010
  </span>
939
1011
  </button>
940
- </PopoverTrigger>
1012
+ </TaskPropertyPopoverTrigger>
941
1013
  <PopoverContent align="start" className="w-80 p-0">
942
1014
  <div className="rounded-lg p-3.5">
943
1015
  <div className="space-y-3">
@@ -1057,12 +1129,23 @@ export function TaskPropertiesSection(props: TaskPropertiesSectionProps) {
1057
1129
  open={isEstimationPopoverOpen}
1058
1130
  onOpenChange={setIsEstimationPopoverOpen}
1059
1131
  >
1060
- <PopoverTrigger asChild>
1132
+ <TaskPropertyPopoverTrigger
1133
+ compact={isCompact}
1134
+ label={
1135
+ boardConfig?.estimation_type && estimationPoints != null
1136
+ ? mapEstimationPoints(
1137
+ estimationPoints,
1138
+ boardConfig.estimation_type
1139
+ )
1140
+ : t('ws-task-boards.dialog.estimate')
1141
+ }
1142
+ >
1061
1143
  <button
1062
1144
  type="button"
1063
1145
  disabled={disabled}
1146
+ aria-label={t('ws-task-boards.dialog.estimate')}
1064
1147
  className={cn(
1065
- 'inline-flex h-8 shrink-0 items-center gap-1.5 rounded-lg border px-3 font-medium text-xs transition-colors',
1148
+ triggerBaseClass,
1066
1149
  estimationPoints != null
1067
1150
  ? 'border-dynamic-purple/30 bg-dynamic-purple/15 text-dynamic-purple hover:border-dynamic-purple/50 hover:bg-dynamic-purple/20'
1068
1151
  : 'border-border bg-background text-muted-foreground hover:border-primary/30 hover:bg-muted hover:text-foreground',
@@ -1070,7 +1153,7 @@ export function TaskPropertiesSection(props: TaskPropertiesSectionProps) {
1070
1153
  )}
1071
1154
  >
1072
1155
  <Timer className="h-3.5 w-3.5" />
1073
- <span>
1156
+ <span className={compactLabelClass}>
1074
1157
  {boardConfig?.estimation_type
1075
1158
  ? estimationPoints != null
1076
1159
  ? mapEstimationPoints(
@@ -1081,7 +1164,7 @@ export function TaskPropertiesSection(props: TaskPropertiesSectionProps) {
1081
1164
  : t('ws-task-boards.dialog.estimate')}
1082
1165
  </span>
1083
1166
  </button>
1084
- </PopoverTrigger>
1167
+ </TaskPropertyPopoverTrigger>
1085
1168
  <PopoverContent align="start" className="w-64 p-0">
1086
1169
  {!boardConfig?.estimation_type ? (
1087
1170
  <EmptyStateCard
@@ -1145,12 +1228,24 @@ export function TaskPropertiesSection(props: TaskPropertiesSectionProps) {
1145
1228
  if (!open) setLabelSearchQuery('');
1146
1229
  }}
1147
1230
  >
1148
- <PopoverTrigger asChild>
1231
+ <TaskPropertyPopoverTrigger
1232
+ compact={isCompact}
1233
+ label={
1234
+ selectedLabels.length === 0
1235
+ ? t('common.labels')
1236
+ : selectedLabels.length === 1
1237
+ ? selectedLabels[0]?.name
1238
+ : t('common.n_labels', {
1239
+ count: selectedLabels.length,
1240
+ })
1241
+ }
1242
+ >
1149
1243
  <button
1150
1244
  type="button"
1151
1245
  disabled={disabled}
1246
+ aria-label={t('common.labels')}
1152
1247
  className={cn(
1153
- 'inline-flex h-8 shrink-0 items-center gap-1.5 rounded-lg border px-3 font-medium text-xs transition-colors',
1248
+ triggerBaseClass,
1154
1249
  selectedLabels.length > 0
1155
1250
  ? 'border-dynamic-indigo/30 bg-dynamic-indigo/15 text-dynamic-indigo hover:border-dynamic-indigo/50 hover:bg-dynamic-indigo/20'
1156
1251
  : 'border-border bg-background text-muted-foreground hover:border-primary/30 hover:bg-muted hover:text-foreground',
@@ -1158,7 +1253,7 @@ export function TaskPropertiesSection(props: TaskPropertiesSectionProps) {
1158
1253
  )}
1159
1254
  >
1160
1255
  <Tag className="h-3.5 w-3.5" />
1161
- <span>
1256
+ <span className={compactLabelClass}>
1162
1257
  {selectedLabels.length === 0
1163
1258
  ? t('common.labels')
1164
1259
  : selectedLabels.length === 1
@@ -1168,7 +1263,7 @@ export function TaskPropertiesSection(props: TaskPropertiesSectionProps) {
1168
1263
  })}
1169
1264
  </span>
1170
1265
  </button>
1171
- </PopoverTrigger>
1266
+ </TaskPropertyPopoverTrigger>
1172
1267
  <PopoverContent align="start" className="w-72 p-0">
1173
1268
  {availableLabels.length === 0 ? (
1174
1269
  <EmptyStateCard
@@ -1283,12 +1378,24 @@ export function TaskPropertiesSection(props: TaskPropertiesSectionProps) {
1283
1378
  if (!open) setProjectSearchQuery('');
1284
1379
  }}
1285
1380
  >
1286
- <PopoverTrigger asChild>
1381
+ <TaskPropertyPopoverTrigger
1382
+ compact={isCompact}
1383
+ label={
1384
+ selectedProjects.length === 0
1385
+ ? t('common.projects')
1386
+ : selectedProjects.length === 1
1387
+ ? selectedProjects[0]?.name
1388
+ : t('common.n_projects', {
1389
+ count: selectedProjects.length,
1390
+ })
1391
+ }
1392
+ >
1287
1393
  <button
1288
1394
  type="button"
1289
1395
  disabled={disabled}
1396
+ aria-label={t('common.projects')}
1290
1397
  className={cn(
1291
- 'inline-flex h-8 shrink-0 items-center gap-1.5 rounded-lg border px-3 font-medium text-xs transition-colors',
1398
+ triggerBaseClass,
1292
1399
  selectedProjects.length > 0
1293
1400
  ? 'border-dynamic-sky/30 bg-dynamic-sky/15 text-dynamic-sky hover:border-dynamic-sky/50 hover:bg-dynamic-sky/20'
1294
1401
  : 'border-border bg-background text-muted-foreground hover:border-primary/30 hover:bg-muted hover:text-foreground',
@@ -1296,7 +1403,7 @@ export function TaskPropertiesSection(props: TaskPropertiesSectionProps) {
1296
1403
  )}
1297
1404
  >
1298
1405
  <Box className="h-3.5 w-3.5" />
1299
- <span>
1406
+ <span className={compactLabelClass}>
1300
1407
  {selectedProjects.length === 0
1301
1408
  ? t('common.projects')
1302
1409
  : selectedProjects.length === 1
@@ -1306,7 +1413,7 @@ export function TaskPropertiesSection(props: TaskPropertiesSectionProps) {
1306
1413
  })}
1307
1414
  </span>
1308
1415
  </button>
1309
- </PopoverTrigger>
1416
+ </TaskPropertyPopoverTrigger>
1310
1417
  <PopoverContent align="start" className="w-72 p-0">
1311
1418
  {taskProjects.length === 0 ? (
1312
1419
  <EmptyStateCard
@@ -1411,12 +1518,25 @@ export function TaskPropertiesSection(props: TaskPropertiesSectionProps) {
1411
1518
  if (!open) setAssigneeSearchQuery('');
1412
1519
  }}
1413
1520
  >
1414
- <PopoverTrigger asChild>
1521
+ <TaskPropertyPopoverTrigger
1522
+ compact={isCompact}
1523
+ label={
1524
+ selectedAssignees.length === 0
1525
+ ? t('common.assignees')
1526
+ : selectedAssignees.length === 1
1527
+ ? selectedAssignees[0]?.display_name ||
1528
+ t('ws-task-boards.dialog.unknown_user')
1529
+ : t('common.n_assignees', {
1530
+ count: selectedAssignees.length,
1531
+ })
1532
+ }
1533
+ >
1415
1534
  <button
1416
1535
  type="button"
1417
1536
  disabled={disabled}
1537
+ aria-label={t('common.assignees')}
1418
1538
  className={cn(
1419
- 'inline-flex h-8 shrink-0 items-center gap-1.5 rounded-lg border px-3 font-medium text-xs transition-colors',
1539
+ triggerBaseClass,
1420
1540
  selectedAssignees.length > 0
1421
1541
  ? 'border-dynamic-cyan/30 bg-dynamic-cyan/15 text-dynamic-cyan hover:border-dynamic-cyan/50 hover:bg-dynamic-cyan/20'
1422
1542
  : 'border-border bg-background text-muted-foreground hover:border-primary/30 hover:bg-muted hover:text-foreground',
@@ -1424,7 +1544,7 @@ export function TaskPropertiesSection(props: TaskPropertiesSectionProps) {
1424
1544
  )}
1425
1545
  >
1426
1546
  <Users className="h-3.5 w-3.5" />
1427
- <span>
1547
+ <span className={compactLabelClass}>
1428
1548
  {selectedAssignees.length === 0
1429
1549
  ? t('common.assignees')
1430
1550
  : selectedAssignees.length === 1
@@ -1435,7 +1555,7 @@ export function TaskPropertiesSection(props: TaskPropertiesSectionProps) {
1435
1555
  })}
1436
1556
  </span>
1437
1557
  </button>
1438
- </PopoverTrigger>
1558
+ </TaskPropertyPopoverTrigger>
1439
1559
  <PopoverContent align="start" className="w-72 p-0">
1440
1560
  {workspaceMembers.length === 0 ? (
1441
1561
  <div className="p-4 text-center text-muted-foreground text-sm">
@@ -1526,12 +1646,20 @@ export function TaskPropertiesSection(props: TaskPropertiesSectionProps) {
1526
1646
  open={isSchedulingPopoverOpen}
1527
1647
  onOpenChange={setIsSchedulingPopoverOpen}
1528
1648
  >
1529
- <PopoverTrigger asChild>
1649
+ <TaskPropertyPopoverTrigger
1650
+ compact={isCompact}
1651
+ label={
1652
+ totalMinutes > 0
1653
+ ? formatDuration(totalMinutes, t)
1654
+ : t('ws-task-boards.dialog.schedule')
1655
+ }
1656
+ >
1530
1657
  <button
1531
1658
  type="button"
1532
1659
  disabled={disabled}
1660
+ aria-label={t('ws-task-boards.dialog.schedule')}
1533
1661
  className={cn(
1534
- 'inline-flex h-8 shrink-0 items-center gap-1.5 rounded-lg border px-3 font-medium text-xs transition-colors',
1662
+ triggerBaseClass,
1535
1663
  hasUnsavedSchedulingChanges
1536
1664
  ? 'border-dynamic-yellow/50 border-dashed bg-dynamic-yellow/10 text-dynamic-yellow hover:border-dynamic-yellow/70 hover:bg-dynamic-yellow/15'
1537
1665
  : totalDuration
@@ -1545,18 +1673,18 @@ export function TaskPropertiesSection(props: TaskPropertiesSectionProps) {
1545
1673
  ) : (
1546
1674
  <CalendarClock className="h-3.5 w-3.5" />
1547
1675
  )}
1548
- <span>
1676
+ <span className={compactLabelClass}>
1549
1677
  {totalMinutes > 0
1550
1678
  ? formatDuration(totalMinutes, t)
1551
1679
  : t('ws-task-boards.dialog.schedule')}
1552
1680
  </span>
1553
- {!disabled && hasUnsavedSchedulingChanges && (
1681
+ {!isCompact && !disabled && hasUnsavedSchedulingChanges && (
1554
1682
  <span className="text-[10px] opacity-75">
1555
1683
  {t('ws-task-boards.dialog.unsaved')}
1556
1684
  </span>
1557
1685
  )}
1558
1686
  </button>
1559
- </PopoverTrigger>
1687
+ </TaskPropertyPopoverTrigger>
1560
1688
  <PopoverContent align="start" className="w-72 p-0">
1561
1689
  <div className="rounded-lg p-3">
1562
1690
  <div className="space-y-3">