Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions packages/boxel-ui/addon/src/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,21 @@ import DndKanbanBoard, {
type DndItem,
DndColumn,
} from './components/drag-and-drop/index.gts';
import {
KanbanPlane,
KanbanDragManager,
KanbanColumnHeader,
KanbanCard,
KanbanGhost,
type KanbanPlacement,
type KanbanColumnConfig,
type InsertionPoint,
autoPlaceKanban,
cardsInColumn,
columnCount as kanbanColumnCount,
resolveInsertion,
findInsertionFromPointer,
} from './components/kanban/index.gts';
import BoxelDropdown, {
type DropdownAPI as BoxelDropdownAPI,
} from './components/dropdown/index.gts';
Expand Down Expand Up @@ -75,6 +90,9 @@ import ViewSelector, {

export {
type Filter,
type InsertionPoint,
type KanbanColumnConfig,
type KanbanPlacement,
type SortOption,
type ViewItem,
Accordion,
Expand Down Expand Up @@ -112,6 +130,16 @@ export {
DndItem,
DndKanbanBoard,
EmailInput,
KanbanCard,
KanbanColumnHeader,
KanbanDragManager,
KanbanGhost,
KanbanPlane,
autoPlaceKanban,
cardsInColumn,
findInsertionFromPointer,
kanbanColumnCount,
resolveInsertion,
EntityDisplayWithIcon,
EntityDisplayWithThumbnail,
FieldContainer,
Expand Down
74 changes: 74 additions & 0 deletions packages/boxel-ui/addon/src/components/kanban/card.gts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import type { TemplateOnlyComponent } from '@ember/component/template-only';
import type { SafeString } from '@ember/template';
import { cn } from '@cardstack/boxel-ui/helpers';
import type { KanbanPlacement } from './engine.ts';

interface Signature {
Args: {
placement: KanbanPlacement;
isSelected: boolean;
isSource: boolean;
shiftStyle: SafeString;
isDragging: boolean;
};
Blocks: {
default: [];
};
Element: HTMLDivElement;
}

const KanbanCard: TemplateOnlyComponent<Signature> = <template>
<div
class={{cn
'card'
selected=@isSelected
dragging=@isSource
board-dragging=@isDragging
}}
style={{@shiftStyle}}
data-card-index={{@placement.index}}
...attributes
>
{{yield}}
</div>

<style scoped>
.card {
flex-shrink: 0;
height: 170px;
border-radius: 8px;
overflow: hidden;
color: var(--card-foreground, var(--boxel-dark));
background: var(--card, var(--boxel-light));
box-shadow:
0 1px 2px rgba(0, 0, 0, 0.06),
0 0 0 1px rgba(0, 0, 0, 0.04);
cursor: grab;
transition: box-shadow 120ms ease-out;
}
.card.board-dragging {
transition:
transform 200ms cubic-bezier(0.34, 1.56, 0.64, 1),
box-shadow 120ms ease-out;
}
.card:hover {
box-shadow:
0 2px 8px rgba(0, 0, 0, 0.08),
0 0 0 1px rgba(0, 0, 0, 0.06);
}
.card.selected {
box-shadow:
0 0 0 2px var(--ring, var(--boxel-highlight)),
0 1px 2px rgba(0, 0, 0, 0.06);
}
.card.dragging {
opacity: 0;
height: 0;
min-height: 0;
overflow: hidden;
margin: -3px 0;
}
</style>
</template>;

export { KanbanCard };
117 changes: 117 additions & 0 deletions packages/boxel-ui/addon/src/components/kanban/column-header.gts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import type { TemplateOnlyComponent } from '@ember/component/template-only';
import { concat } from '@ember/helper';
import { on } from '@ember/modifier';
import ContextButton from '../context-button/index.gts';
import { cn, cssVar } from '@cardstack/boxel-ui/helpers';
import type { KanbanColumnConfig } from './engine.ts';

interface Signature {
Args: {
column: KanbanColumnConfig;
cardCount: number;
isOverWip: boolean;
isTarget: boolean;
onAddCard?: () => void;
};
Element: HTMLDivElement;
}

const KanbanColumnHeader: TemplateOnlyComponent<Signature> = <template>
<div class={{cn 'col-header' is-target=@isTarget}} ...attributes>
<div class='col-header-left'>
<span class='col-dot' style={{cssVar col-dot-bg=@column.color}}></span>
<span class='col-name'>{{if
@column.label
@column.label
'Untitled'
}}</span>
<span class='col-count'>{{@cardCount}}</span>
</div>
<div class='col-header-right'>
{{#if @column.wipLimit}}
<span class={{cn 'col-wip' over=@isOverWip}}>
max
{{@column.wipLimit}}
</span>
{{/if}}
{{#if @onAddCard}}
<ContextButton
class='col-add-btn'
@label={{if
@column.label
(concat 'Add card to ' @column.label)
'Add card to column'
}}
@icon='add'
@variant='ghost'
{{on 'click' @onAddCard}}
/>
{{/if}}
</div>
</div>

<style scoped>
.col-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 14px 8px;
flex-shrink: 0;
}
.col-header.is-target {
background: color-mix(
in oklch,
var(--accent, var(--boxel-highlight)) 8%,
transparent
);
color: var(--accent-foreground, var(--boxel-dark));
border-radius: 8px 8px 0 0;
}
.col-header-left {
display: flex;
align-items: center;
gap: 8px;
}
.col-dot {
width: 0.625rem;
height: 0.625rem;
border-radius: 50%;
flex-shrink: 0;
background: var(--col-dot-bg, var(--muted-foreground, var(--boxel-450)));
box-shadow: 0 0 0 1.5px
color-mix(in oklch, var(--col-dot-bg) 30%, transparent);
}
.col-name {
font-size: 13px;
font-weight: 600;
letter-spacing: -0.01em;
}
.col-count {
font-size: 12px;
font-weight: 500;
color: var(--muted-foreground, var(--boxel-450));
}
.col-header-right {
display: flex;
align-items: center;
gap: 4px;
}
.col-wip {
font-size: 10px;
color: var(--muted-foreground);
font-family: var(--font-mono, var(--boxel-monospace-font-family));
}
.col-wip.over {
color: var(--destructive, var(--boxel-red));
font-weight: 600;
}
.col-add-btn {
opacity: 0.6;
}
.col-add-btn:hover {
opacity: 1;
}
</style>
</template>;

export { KanbanColumnHeader };
Loading
Loading