-
Notifications
You must be signed in to change notification settings - Fork 3.2k
[73968, 73089] Extend and improve design of WorkPackageCardComponent #23175
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -30,20 +30,44 @@ See COPYRIGHT and LICENSE files for more details. | |||||||||||||||||||||||||||||||||||||||||||||
| <%= grid_layout( | ||||||||||||||||||||||||||||||||||||||||||||||
| "op-work-package-card", | ||||||||||||||||||||||||||||||||||||||||||||||
| tag: :article, | ||||||||||||||||||||||||||||||||||||||||||||||
| classes: { "op-work-package-card_with-metric": metric? } | ||||||||||||||||||||||||||||||||||||||||||||||
| classes: { | ||||||||||||||||||||||||||||||||||||||||||||||
| "op-work-package-card_with-drag-handle": show_drag_handle, | ||||||||||||||||||||||||||||||||||||||||||||||
| "op-work-package-card_with-footer": show_footer? | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| ) do |grid| %> | ||||||||||||||||||||||||||||||||||||||||||||||
| <% if show_drag_handle %> | ||||||||||||||||||||||||||||||||||||||||||||||
| <% grid.with_area(:drag_handle) do %> | ||||||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||
| <%= render(Primer::Beta::Octicon.new(icon: :grabber, "aria-label": t(".drag_handle.label"))) %> | ||||||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||||||||||||||||||||||||||||||||||||||||||
| <% end %> | ||||||||||||||||||||||||||||||||||||||||||||||
| <% end %> | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| <% grid.with_area(:info_line) do %> | ||||||||||||||||||||||||||||||||||||||||||||||
| <%# TODO(73089): allow callers to pass arguments through to InfoLineComponent (e.g. status presentation, variants). %> | ||||||||||||||||||||||||||||||||||||||||||||||
| <%= render(WorkPackages::InfoLineComponent.new(work_package:)) %> | ||||||||||||||||||||||||||||||||||||||||||||||
| <%= render(WorkPackages::InfoLineComponent.new(work_package:, status_scheme:)) %> | ||||||||||||||||||||||||||||||||||||||||||||||
| <% end %> | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| <% if metric? %> | ||||||||||||||||||||||||||||||||||||||||||||||
| <% grid.with_area(:metric) do %> | ||||||||||||||||||||||||||||||||||||||||||||||
| <%= metric %> | ||||||||||||||||||||||||||||||||||||||||||||||
| <% grid.with_area(:actions) do %> | ||||||||||||||||||||||||||||||||||||||||||||||
| <% if show_assignee && work_package.assigned_to %> | ||||||||||||||||||||||||||||||||||||||||||||||
| <%= render( | ||||||||||||||||||||||||||||||||||||||||||||||
| Users::AvatarComponent.new( | ||||||||||||||||||||||||||||||||||||||||||||||
| user: work_package.assigned_to, size: "mini", link: false, show_name: true, | ||||||||||||||||||||||||||||||||||||||||||||||
| name_classes: "op-work-package-card--assignee-name" | ||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||
| ) %> | ||||||||||||||||||||||||||||||||||||||||||||||
| <% end %> | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| <% if metric? %> | ||||||||||||||||||||||||||||||||||||||||||||||
| <span class="op-work-package-card--metric"><%= metric %></span> | ||||||||||||||||||||||||||||||||||||||||||||||
| <% end %> | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| <% if show_priority && work_package.priority %> | ||||||||||||||||||||||||||||||||||||||||||||||
| <%= render( | ||||||||||||||||||||||||||||||||||||||||||||||
| Primer::Beta::Text.new( | ||||||||||||||||||||||||||||||||||||||||||||||
| tag: :span, | ||||||||||||||||||||||||||||||||||||||||||||||
| classes: "__hl_inline_priority_#{work_package.priority.id} __hl_inline__small_dot" | ||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||
| ) { work_package.priority.name } %> | ||||||||||||||||||||||||||||||||||||||||||||||
| <% end %> | ||||||||||||||||||||||||||||||||||||||||||||||
| <% end %> | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| <% grid.with_area(:menu) do %> | ||||||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To make the as intutitve as possible, I'd like to keep to try to keep the slot names aligned with That said, I quite like keeping the
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I realise now that was mixing up slot naming and grid area naming. 🤦🏻 .. However, we should probably still try to keep things aligned if possible. |
||||||||||||||||||||||||||||||||||||||||||||||
| <% if menu? %> | ||||||||||||||||||||||||||||||||||||||||||||||
| <%= menu %> | ||||||||||||||||||||||||||||||||||||||||||||||
| <% else %> | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -58,6 +82,33 @@ See COPYRIGHT and LICENSE files for more details. | |||||||||||||||||||||||||||||||||||||||||||||
| <% end %> | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| <% grid.with_area(:subject) do %> | ||||||||||||||||||||||||||||||||||||||||||||||
| <%= render(Primer::Beta::Text.new(font_weight: :semibold)) { work_package.subject } %> | ||||||||||||||||||||||||||||||||||||||||||||||
| <%= render(Primer::Beta::Link.new(href: work_package_path(work_package), font_weight: :semibold)) { work_package.subject } %> | ||||||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can this link be optional? I'm not sure what is specified, but for the Backlogs use case (i.e. where the whole card is draggable), I would not want to make the subject a link. |
||||||||||||||||||||||||||||||||||||||||||||||
| <% end %> | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| <% if show_footer? %> | ||||||||||||||||||||||||||||||||||||||||||||||
| <% grid.with_area(:footer) do %> | ||||||||||||||||||||||||||||||||||||||||||||||
| <% flex_layout do |flex| %> | ||||||||||||||||||||||||||||||||||||||||||||||
| <% if show_parent_link && work_package.parent.present? %> | ||||||||||||||||||||||||||||||||||||||||||||||
| <% flex.with_row do %> | ||||||||||||||||||||||||||||||||||||||||||||||
| <%= render( | ||||||||||||||||||||||||||||||||||||||||||||||
| Primer::Beta::Link.new( | ||||||||||||||||||||||||||||||||||||||||||||||
| href: work_package_path(work_package.parent), | ||||||||||||||||||||||||||||||||||||||||||||||
| underline: false, | ||||||||||||||||||||||||||||||||||||||||||||||
| color: :default | ||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||
| ) do %> | ||||||||||||||||||||||||||||||||||||||||||||||
| <%= render(Primer::Beta::Text.new(color: :subtle)) { "#{t('.parent')}: " } %> | ||||||||||||||||||||||||||||||||||||||||||||||
| <%= work_package.parent.subject %> | ||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+91
to
+101
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. True (I think).. I missed this one. |
||||||||||||||||||||||||||||||||||||||||||||||
| <% end %> | ||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+93
to
+102
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
e.g.
|
||||||||||||||||||||||||||||||||||||||||||||||
| <% end %> | ||||||||||||||||||||||||||||||||||||||||||||||
| <% end %> | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| <% if bottom_line? %> | ||||||||||||||||||||||||||||||||||||||||||||||
| <% flex.with_row do %> | ||||||||||||||||||||||||||||||||||||||||||||||
| <%= bottom_line %> | ||||||||||||||||||||||||||||||||||||||||||||||
| <% end %> | ||||||||||||||||||||||||||||||||||||||||||||||
| <% end %> | ||||||||||||||||||||||||||||||||||||||||||||||
| <% end %> | ||||||||||||||||||||||||||||||||||||||||||||||
| <% end %> | ||||||||||||||||||||||||||||||||||||||||||||||
| <% end %> | ||||||||||||||||||||||||||||||||||||||||||||||
| <% end %> | ||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -43,17 +43,38 @@ class WorkPackageCardComponent < ApplicationComponent | |
| **system_arguments | ||
| ) | ||
| } | ||
| renders_one :bottom_line, Primer::Content | ||
|
|
||
| attr_reader :work_package, :menu_src | ||
| attr_reader :work_package, :menu_src, :show_drag_handle, :show_assignee, :show_priority, | ||
| :show_parent_link, :status_scheme | ||
|
|
||
| # @param work_package [WorkPackage] the work package this card represents. | ||
| # @param menu_src [String, NilClass] optional lazy menu source. Prefer the | ||
| # `with_menu(src:)` slot for new call sites. | ||
| def initialize(work_package:, menu_src: nil) | ||
| # @param show_drag_handle [Boolean] whether to show a drag handle icon. | ||
| # @param show_assignee [Boolean] whether to show the assignee (icon + name when space allows). | ||
| # @param show_priority [Boolean] whether to show the priority badge. | ||
| # @param show_parent_link [Boolean] whether to show a link to the parent work package in row 3. | ||
| # Only rendered when the work package actually has a parent. | ||
|
Comment on lines
+57
to
+58
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nitpick: could shorten this parameter to just |
||
| # @param status_scheme [Symbol] status label scheme for the info line. One of :default or :secondary. | ||
| def initialize(work_package:, menu_src: nil, show_drag_handle: false, | ||
| show_assignee: false, show_priority: false, show_parent_link: false, | ||
| status_scheme: :default) | ||
| super() | ||
|
|
||
| @work_package = work_package | ||
| @menu_src = menu_src | ||
| @show_drag_handle = show_drag_handle | ||
| @show_assignee = show_assignee | ||
| @show_priority = show_priority | ||
| @show_parent_link = show_parent_link | ||
| @status_scheme = status_scheme | ||
| end | ||
|
|
||
| private | ||
|
|
||
| def show_footer? | ||
| bottom_line? || (show_parent_link && work_package.parent.present?) | ||
| end | ||
| end | ||
| end | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -30,24 +30,61 @@ | |||||||||||||||||
| display: grid | ||||||||||||||||||
| grid-template-columns: 1fr auto | ||||||||||||||||||
| grid-template-rows: auto auto | ||||||||||||||||||
| grid-template-areas: "info_line menu" "subject subject" | ||||||||||||||||||
| grid-template-areas: "info_line actions" "subject subject" | ||||||||||||||||||
| align-items: center | ||||||||||||||||||
| margin-top: calc(-1 * var(--base-size-4)) | ||||||||||||||||||
| margin-bottom: var(--base-size-4) | ||||||||||||||||||
| container-type: inline-size | ||||||||||||||||||
|
|
||||||||||||||||||
| .op-work-package-card_with-metric | ||||||||||||||||||
| grid-template-columns: 1fr minmax(2rem, max-content) auto | ||||||||||||||||||
| grid-template-areas: "info_line metric menu" "subject subject subject" | ||||||||||||||||||
| &_with-footer | ||||||||||||||||||
| grid-template-rows: auto auto auto | ||||||||||||||||||
| grid-template-areas: "info_line actions" "subject subject" "footer footer" | ||||||||||||||||||
|
|
||||||||||||||||||
| .op-work-package-card--metric | ||||||||||||||||||
| margin-left: var(--stack-gap-normal) | ||||||||||||||||||
| font-variant-numeric: tabular-nums | ||||||||||||||||||
| text-align: right | ||||||||||||||||||
| &_with-drag-handle | ||||||||||||||||||
| grid-template-columns: auto 1fr auto | ||||||||||||||||||
| grid-template-areas: "drag_handle info_line actions" ". subject subject" | ||||||||||||||||||
|
|
||||||||||||||||||
| .op-work-package-card--menu | ||||||||||||||||||
| margin-left: var(--stack-gap-normal) | ||||||||||||||||||
| &.op-work-package-card_with-footer | ||||||||||||||||||
| grid-template-rows: auto auto auto | ||||||||||||||||||
| grid-template-areas: "drag_handle info_line actions" ". subject subject" ". footer footer" | ||||||||||||||||||
|
|
||||||||||||||||||
| .op-work-package-card--subject | ||||||||||||||||||
| align-self: start // Align to top of second row | ||||||||||||||||||
| word-wrap: break-word | ||||||||||||||||||
| overflow-wrap: break-word | ||||||||||||||||||
| &--drag_handle | ||||||||||||||||||
| align-self: center | ||||||||||||||||||
| padding-right: var(--stack-gap-condensed) | ||||||||||||||||||
| cursor: grab | ||||||||||||||||||
| color: var(--fgColor-muted) | ||||||||||||||||||
|
|
||||||||||||||||||
| &--actions | ||||||||||||||||||
| display: flex | ||||||||||||||||||
| align-items: center | ||||||||||||||||||
| gap: var(--stack-gap-normal) | ||||||||||||||||||
| color: var(--fgColor-muted) | ||||||||||||||||||
|
|
||||||||||||||||||
| &--metric | ||||||||||||||||||
| font-variant-numeric: tabular-nums | ||||||||||||||||||
| text-align: right | ||||||||||||||||||
|
|
||||||||||||||||||
| // parent_link and bottom_line auto-place into implicit rows, spanning full width | ||||||||||||||||||
| &--parent_link, | ||||||||||||||||||
| &--bottom_line | ||||||||||||||||||
| grid-column: 1 / -1 | ||||||||||||||||||
| padding-top: var(--base-size-4) | ||||||||||||||||||
|
|
||||||||||||||||||
| &_with-drag-handle | ||||||||||||||||||
| .op-work-package-card--parent_link, | ||||||||||||||||||
| .op-work-package-card--bottom_line | ||||||||||||||||||
| grid-column: 2 / -1 | ||||||||||||||||||
|
|
||||||||||||||||||
| &--subject | ||||||||||||||||||
| align-self: start | ||||||||||||||||||
| word-wrap: break-word | ||||||||||||||||||
| overflow-wrap: break-word | ||||||||||||||||||
|
|
||||||||||||||||||
|
|
||||||||||||||||||
| // < 768px: hide text labels, show only icons | ||||||||||||||||||
| @container (width < 768px) | ||||||||||||||||||
| .op-work-package-card--assignee-name | ||||||||||||||||||
| display: none | ||||||||||||||||||
|
|
||||||||||||||||||
| .op-work-package-card--actions .__hl_inline__small_dot | ||||||||||||||||||
| font-size: 0 | ||||||||||||||||||
|
Comment on lines
+89
to
+90
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, I don't like this. I'm not 100% sure of what implications are for accessibility, etc. but it feels "hacky". Without a supporting comment it's unclear to other devs what tis code is meant to be do.. Can we not just apply an explicit class for
Suggested change
or - for better accessibility support (/cc @bsatarnejad):
Suggested change
(if you do do the latter though, you'll need to make the parent container |
||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -32,6 +32,7 @@ See COPYRIGHT and LICENSE files for more details. | |
| scheme: :invisible, | ||
| icon: :"kebab-horizontal", | ||
| "aria-label": button_aria_label || t(".label_actions"), | ||
| tooltip_direction: :se | ||
| tooltip_direction: :se, | ||
| size: :small | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not against doing this in principle, but this will break alignment with
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| ) %> | ||
| <% end %> | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -35,6 +35,7 @@ def initialize(work_package:, | |
| show_project: false, | ||
| show_subject: false, | ||
| show_status: true, | ||
| status_scheme: :default, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Won't this break existing callers? We should document this option on |
||
| font_size: :small, | ||
| **system_arguments) | ||
| super | ||
|
|
@@ -44,6 +45,7 @@ def initialize(work_package:, | |
| @show_project = show_project | ||
| @show_subject = show_subject | ||
| @show_status = show_status | ||
| @status_scheme = status_scheme | ||
|
|
||
| @system_arguments = system_arguments | ||
| end | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| The `WorkPackageCard` is a compact representation of a work package, designed for use in list and board views such as sprint and backlog views. | ||
|
|
||
| ## Overview | ||
|
|
||
| <%= embed OpenProject::Common::WorkPackageCardComponentPreview, :default %> | ||
|
|
||
| ## Anatomy | ||
|
|
||
| The card is structured in up to three rows: | ||
|
|
||
| **Row 1 — Metadata** | ||
| Contains the info line (type, ID, status), followed by optional elements: assignee, metric (e.g. story points), priority, and the actions menu. When `show_drag_handle: true`, a drag handle icon appears on the far left, spanning all rows, aligned to the top (row 1). | ||
|
|
||
| **Row 2 — Subject** | ||
| The work package title, always visible. | ||
|
|
||
| **Row 3 — Footer** *(only rendered when content is present)* | ||
| Shows a link to the parent work package (when `show_parent_link: true` and a parent exists) and/or the `with_bottom_line` slot. | ||
|
|
||
| ## Parameters | ||
|
|
||
| | Parameter | Type | Default | Description | | ||
| |---|---|---|---| | ||
| | `work_package` | `WorkPackage` | required | The work package to display | | ||
| | `menu_src` | `String` | `nil` | Optional lazy-load URL for the actions menu | | ||
| | `show_assignee` | `Boolean` | `false` | Show the assignee icon and name | | ||
| | `show_priority` | `Boolean` | `false` | Show a priority badge | | ||
| | `show_drag_handle` | `Boolean` | `false` | Show a drag handle icon | | ||
| | `show_parent_link` | `Boolean` | `false` | Show a link to the parent work package in row 3. Only rendered when `work_package.parent` is present. | | ||
| | `status_scheme` | `Symbol` | `:default` | Status label style: `:default` (highlighted) or `:secondary` (neutral label) | | ||
|
|
||
| ## Slots | ||
|
|
||
| | Slot | Description | | ||
| |---|---| | ||
| | `with_metric` | Numeric value shown in row 1 (e.g. story points) | | ||
| | `with_menu` | Custom actions menu, replaces the default lazy menu | | ||
| | `with_bottom_line` | Additional content appended to row 3 | | ||
|
|
||
| ## Variants | ||
|
|
||
| <%= embed OpenProject::Common::WorkPackageCardComponentPreview, :playground, panels: %i[params source] %> | ||
|
|
||
| ## Code structure | ||
|
|
||
| ```ruby | ||
| render OpenProject::Common::WorkPackageCardComponent.new( | ||
| work_package: @work_package, | ||
| show_assignee: true, | ||
| show_priority: true, | ||
| show_parent_link: true, | ||
| status_scheme: :secondary | ||
| ) do |card| | ||
| card.with_metric { @work_package.story_points } | ||
| card.with_menu do |menu| | ||
| menu.with_item(label: "Open", href: work_package_path(@work_package)) | ||
| end | ||
| card.with_bottom_line do | ||
| # Whatever else you want to show | ||
| end | ||
| end | ||
| ``` |


There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nitpick: you can alias these methods as predicate in
WorkPackageCardComponent:Example here