feat: Phase 2 - Extract layout components from GiftedDialog
- Create EntityGrid.vue for unified entity grid layout * Responsive grid layout for people and projects * Integrates special entities (You, Unnamed) seamlessly * Conflict detection integration with prop-based checker * Empty state messaging based on entity type * Show All navigation with query parameter support * Event delegation for all entity selection types * Configurable display limits and grid columns - Create SpecialEntityCard.vue for special entity handling * Handles 'You' and 'Unnamed' entity types * FontAwesome icon integration with configurable styling * Conflict state handling with visual feedback * Entity-type-specific color schemes (blue for You, gray for Unnamed) * Emits structured events with entity data - Create ShowAllCard.vue for navigation functionality * Router-link integration with query parameter passing * Consistent visual styling with other entity cards * Hover effects and animations * Maintains context through configurable route params - Update GiftedDialog-Decomposition-Plan.md * Mark Phase 2 as completed * Add comprehensive integration examples * Update component specifications with actual props/emits * Add EntityGrid, SpecialEntityCard, and ShowAllCard usage patterns * Update project status and next steps Phase 2 creates reusable layout components that can replace the complex grid logic in GiftedDialog. The EntityGrid component provides a clean API for entity selection while maintaining all existing functionality. Components: 7 total (4 Phase 1 + 3 Phase 2) Next: Integration phase - Update GiftedDialog to use new components
This commit is contained in:
@@ -67,29 +67,42 @@ These components handle pure presentation with minimal business logic:
|
|||||||
- **Props**: `value`, `min`, `max`, `step`, `inputId`
|
- **Props**: `value`, `min`, `max`, `step`, `inputId`
|
||||||
- **Emits**: `update:value`
|
- **Emits**: `update:value`
|
||||||
|
|
||||||
### Phase 2: Extract Layout Components (NEXT)
|
### Phase 2: Extract Layout Components (✅ COMPLETED)
|
||||||
|
|
||||||
These components handle layout and entity organization:
|
These components handle layout and entity organization:
|
||||||
|
|
||||||
#### 5. EntityGrid.vue (PLANNED)
|
#### 5. EntityGrid.vue ✅
|
||||||
- **Purpose**: Reusable grid for displaying people or projects
|
- **Purpose**: Unified grid layout for displaying people or projects
|
||||||
- **Features**:
|
- **Features**:
|
||||||
- Responsive grid layout
|
- Responsive grid layout for people/projects
|
||||||
- Entity type switching (people/projects)
|
- Special entity integration (You, Unnamed)
|
||||||
- "Show All" navigation
|
- Conflict detection integration
|
||||||
- Empty state handling
|
- Empty state messaging
|
||||||
- **Props**: `entities`, `entityType`, `gridCols`, `maxItems`
|
- Show All navigation
|
||||||
- **Emits**: `entity-selected`, `show-all-clicked`
|
- Event delegation for entity selection
|
||||||
|
- **Props**: `entityType`, `entities`, `maxItems`, `activeDid`, `allMyDids`, `allContacts`, `conflictChecker`, `showYouEntity`, `youSelectable`, `showAllRoute`, `showAllQueryParams`
|
||||||
|
- **Emits**: `entity-selected`
|
||||||
|
|
||||||
#### 6. SpecialEntityCard.vue (PLANNED)
|
#### 6. SpecialEntityCard.vue ✅
|
||||||
- **Purpose**: Handle special entities like "You" and "Unnamed"
|
- **Purpose**: Handle special entities like "You" and "Unnamed"
|
||||||
- **Features**:
|
- **Features**:
|
||||||
- Special icon display (hand, question mark)
|
- Special icon display (hand, question mark)
|
||||||
- Conflict state handling
|
- Conflict state handling
|
||||||
|
- Configurable styling based on entity type
|
||||||
- Click event handling
|
- Click event handling
|
||||||
- **Props**: `entityType`, `label`, `icon`, `conflicted`
|
- **Props**: `entityType`, `label`, `icon`, `selectable`, `conflicted`, `entityData`
|
||||||
- **Emits**: `entity-selected`
|
- **Emits**: `entity-selected`
|
||||||
|
|
||||||
|
#### 7. ShowAllCard.vue ✅
|
||||||
|
- **Purpose**: Handle "Show All" navigation functionality
|
||||||
|
- **Features**:
|
||||||
|
- Router-link integration
|
||||||
|
- Query parameter passing
|
||||||
|
- Consistent visual styling
|
||||||
|
- Hover effects
|
||||||
|
- **Props**: `entityType`, `routeName`, `queryParams`
|
||||||
|
- **Emits**: None (uses router-link)
|
||||||
|
|
||||||
### Phase 3: Extract Step Components (FUTURE)
|
### Phase 3: Extract Step Components (FUTURE)
|
||||||
|
|
||||||
These components handle major UI sections:
|
These components handle major UI sections:
|
||||||
@@ -130,17 +143,23 @@ These components handle major UI sections:
|
|||||||
|
|
||||||
### ✅ Completed Components
|
### ✅ Completed Components
|
||||||
|
|
||||||
|
**Phase 1: Display Components**
|
||||||
1. **PersonCard.vue** - Individual person display with selection
|
1. **PersonCard.vue** - Individual person display with selection
|
||||||
2. **ProjectCard.vue** - Individual project display with selection
|
2. **ProjectCard.vue** - Individual project display with selection
|
||||||
3. **EntitySummaryButton.vue** - Selected entity display with edit capability
|
3. **EntitySummaryButton.vue** - Selected entity display with edit capability
|
||||||
4. **AmountInput.vue** - Numeric input with increment/decrement controls
|
4. **AmountInput.vue** - Numeric input with increment/decrement controls
|
||||||
|
|
||||||
|
**Phase 2: Layout Components**
|
||||||
|
5. **EntityGrid.vue** - Unified grid layout for entity selection
|
||||||
|
6. **SpecialEntityCard.vue** - Special entities (You, Unnamed) with conflict handling
|
||||||
|
7. **ShowAllCard.vue** - Show All navigation with router integration
|
||||||
|
|
||||||
### 🔄 Next Steps
|
### 🔄 Next Steps
|
||||||
|
|
||||||
1. **Create EntityGrid.vue** - Unified grid layout for entities
|
1. **Update GiftedDialog.vue** - Integrate Phase 1 & 2 components incrementally
|
||||||
2. **Create SpecialEntityCard.vue** - Handle "You" and "Unnamed" entities
|
2. **Test integration** - Ensure functionality remains intact
|
||||||
3. **Update GiftedDialog.vue** - Integrate new components incrementally
|
3. **Create unit tests** - For all new components
|
||||||
4. **Test integration** - Ensure functionality remains intact
|
4. **Performance validation** - Ensure no regression
|
||||||
|
|
||||||
### 📋 Future Phases
|
### 📋 Future Phases
|
||||||
|
|
||||||
@@ -232,6 +251,58 @@ These components handle major UI sections:
|
|||||||
</template>
|
</template>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Using EntityGrid in EntitySelectionStep
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<label class="block font-bold mb-4">
|
||||||
|
{{ stepLabel }}
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<EntityGrid
|
||||||
|
:entity-type="shouldShowProjects ? 'projects' : 'people'"
|
||||||
|
:entities="shouldShowProjects ? projects : allContacts"
|
||||||
|
:max-items="10"
|
||||||
|
:active-did="activeDid"
|
||||||
|
:all-my-dids="allMyDids"
|
||||||
|
:all-contacts="allContacts"
|
||||||
|
:conflict-checker="wouldCreateConflict"
|
||||||
|
:show-you-entity="showYouEntity"
|
||||||
|
:you-selectable="youSelectable"
|
||||||
|
:show-all-route="showAllRoute"
|
||||||
|
:show-all-query-params="showAllQueryParams"
|
||||||
|
@entity-selected="handleEntitySelected"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using SpecialEntityCard Standalone
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<ul class="grid grid-cols-4 gap-2">
|
||||||
|
<SpecialEntityCard
|
||||||
|
entity-type="you"
|
||||||
|
label="You"
|
||||||
|
icon="hand"
|
||||||
|
:conflicted="wouldCreateConflict(activeDid)"
|
||||||
|
:entity-data="{ did: activeDid, name: 'You' }"
|
||||||
|
@entity-selected="handleYouSelected"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<SpecialEntityCard
|
||||||
|
entity-type="unnamed"
|
||||||
|
label="Unnamed"
|
||||||
|
icon="circle-question"
|
||||||
|
:entity-data="{ did: '', name: 'Unnamed' }"
|
||||||
|
@entity-selected="handleUnnamedSelected"
|
||||||
|
/>
|
||||||
|
</ul>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
## Migration Strategy
|
## Migration Strategy
|
||||||
|
|
||||||
### Backward Compatibility
|
### Backward Compatibility
|
||||||
@@ -271,4 +342,4 @@ The completed Phase 1 components (PersonCard, ProjectCard, EntitySummaryButton,
|
|||||||
|
|
||||||
**Author**: Matthew Raymer
|
**Author**: Matthew Raymer
|
||||||
**Last Updated**: 2025-01-28
|
**Last Updated**: 2025-01-28
|
||||||
**Status**: Phase 1 Complete, Phase 2 In Progress
|
**Status**: Phase 1 & 2 Complete, Integration Phase Next
|
||||||
259
src/components/EntityGrid.vue
Normal file
259
src/components/EntityGrid.vue
Normal file
@@ -0,0 +1,259 @@
|
|||||||
|
/**
|
||||||
|
* EntityGrid.vue - Unified entity grid layout component
|
||||||
|
*
|
||||||
|
* Extracted from GiftedDialog.vue to provide a reusable grid layout
|
||||||
|
* for displaying people, projects, and special entities with selection.
|
||||||
|
*
|
||||||
|
* @author Matthew Raymer
|
||||||
|
*/
|
||||||
|
<template>
|
||||||
|
<ul :class="gridClasses">
|
||||||
|
<!-- Special entities (You, Unnamed) for people grids -->
|
||||||
|
<template v-if="entityType === 'people'">
|
||||||
|
<!-- "You" entity -->
|
||||||
|
<SpecialEntityCard
|
||||||
|
v-if="showYouEntity"
|
||||||
|
entity-type="you"
|
||||||
|
label="You"
|
||||||
|
icon="hand"
|
||||||
|
:selectable="youSelectable"
|
||||||
|
:conflicted="youConflicted"
|
||||||
|
:entity-data="youEntityData"
|
||||||
|
@entity-selected="handleEntitySelected"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- "Unnamed" entity -->
|
||||||
|
<SpecialEntityCard
|
||||||
|
entity-type="unnamed"
|
||||||
|
label="Unnamed"
|
||||||
|
icon="circle-question"
|
||||||
|
:entity-data="unnamedEntityData"
|
||||||
|
@entity-selected="handleEntitySelected"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- Empty state message -->
|
||||||
|
<li
|
||||||
|
v-if="entities.length === 0"
|
||||||
|
class="text-xs text-slate-500 italic col-span-full"
|
||||||
|
>
|
||||||
|
{{ emptyStateMessage }}
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<!-- Entity cards (people or projects) -->
|
||||||
|
<template v-if="entityType === 'people'">
|
||||||
|
<PersonCard
|
||||||
|
v-for="person in displayedEntities"
|
||||||
|
:key="person.did"
|
||||||
|
:person="person"
|
||||||
|
:conflicted="isPersonConflicted(person.did)"
|
||||||
|
:show-time-icon="true"
|
||||||
|
@person-selected="handlePersonSelected"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-else-if="entityType === 'projects'">
|
||||||
|
<ProjectCard
|
||||||
|
v-for="project in displayedEntities"
|
||||||
|
:key="project.handleId"
|
||||||
|
:project="project"
|
||||||
|
:active-did="activeDid"
|
||||||
|
:all-my-dids="allMyDids"
|
||||||
|
:all-contacts="allContacts"
|
||||||
|
@project-selected="handleProjectSelected"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- Show All navigation -->
|
||||||
|
<ShowAllCard
|
||||||
|
v-if="shouldShowAll"
|
||||||
|
:entity-type="entityType"
|
||||||
|
:route-name="showAllRoute"
|
||||||
|
:query-params="showAllQueryParams"
|
||||||
|
/>
|
||||||
|
</ul>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { Component, Prop, Vue } from "vue-facing-decorator";
|
||||||
|
import PersonCard from "./PersonCard.vue";
|
||||||
|
import ProjectCard from "./ProjectCard.vue";
|
||||||
|
import SpecialEntityCard from "./SpecialEntityCard.vue";
|
||||||
|
import ShowAllCard from "./ShowAllCard.vue";
|
||||||
|
import { Contact } from "@/interfaces/contact";
|
||||||
|
import { PlanData } from "@/interfaces/plan-data";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* EntityGrid - Unified grid layout for entity selection
|
||||||
|
*
|
||||||
|
* Features:
|
||||||
|
* - Responsive grid layout for people or projects
|
||||||
|
* - Special entity handling (You, Unnamed)
|
||||||
|
* - Conflict detection integration
|
||||||
|
* - Empty state messaging
|
||||||
|
* - Show All navigation
|
||||||
|
* - Configurable display limits
|
||||||
|
* - Event delegation for entity selection
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
components: {
|
||||||
|
PersonCard,
|
||||||
|
ProjectCard,
|
||||||
|
SpecialEntityCard,
|
||||||
|
ShowAllCard,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
export default class EntityGrid extends Vue {
|
||||||
|
/** Type of entities to display */
|
||||||
|
@Prop({ required: true })
|
||||||
|
entityType!: "people" | "projects";
|
||||||
|
|
||||||
|
/** Array of entities to display */
|
||||||
|
@Prop({ required: true })
|
||||||
|
entities!: Contact[] | PlanData[];
|
||||||
|
|
||||||
|
/** Maximum number of entities to display */
|
||||||
|
@Prop({ default: 10 })
|
||||||
|
maxItems!: number;
|
||||||
|
|
||||||
|
/** Active user's DID */
|
||||||
|
@Prop({ required: true })
|
||||||
|
activeDid!: string;
|
||||||
|
|
||||||
|
/** All user's DIDs */
|
||||||
|
@Prop({ required: true })
|
||||||
|
allMyDids!: string[];
|
||||||
|
|
||||||
|
/** All contacts */
|
||||||
|
@Prop({ required: true })
|
||||||
|
allContacts!: Contact[];
|
||||||
|
|
||||||
|
/** Function to check if a person DID would create a conflict */
|
||||||
|
@Prop({ required: true })
|
||||||
|
conflictChecker!: (did: string) => boolean;
|
||||||
|
|
||||||
|
/** Whether to show the "You" entity for people grids */
|
||||||
|
@Prop({ default: true })
|
||||||
|
showYouEntity!: boolean;
|
||||||
|
|
||||||
|
/** Whether the "You" entity is selectable */
|
||||||
|
@Prop({ default: true })
|
||||||
|
youSelectable!: boolean;
|
||||||
|
|
||||||
|
/** Route name for "Show All" navigation */
|
||||||
|
@Prop({ default: "" })
|
||||||
|
showAllRoute!: string;
|
||||||
|
|
||||||
|
/** Query parameters for "Show All" navigation */
|
||||||
|
@Prop({ default: () => ({}) })
|
||||||
|
showAllQueryParams!: Record<string, any>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computed CSS classes for the grid layout
|
||||||
|
*/
|
||||||
|
get gridClasses(): string {
|
||||||
|
const baseClasses = "grid gap-x-2 gap-y-4 text-center mb-4";
|
||||||
|
|
||||||
|
if (this.entityType === "projects") {
|
||||||
|
return `${baseClasses} grid-cols-3 md:grid-cols-4`;
|
||||||
|
} else {
|
||||||
|
return `${baseClasses} grid-cols-4 sm:grid-cols-5 md:grid-cols-6`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computed entities to display (limited by maxItems)
|
||||||
|
*/
|
||||||
|
get displayedEntities(): Contact[] | PlanData[] {
|
||||||
|
const maxDisplay = this.entityType === "projects" ? 7 : this.maxItems;
|
||||||
|
return this.entities.slice(0, maxDisplay);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computed empty state message based on entity type
|
||||||
|
*/
|
||||||
|
get emptyStateMessage(): string {
|
||||||
|
if (this.entityType === "projects") {
|
||||||
|
return "(No projects found.)";
|
||||||
|
} else {
|
||||||
|
return "(Add friends to see more people worthy of recognition.)";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to show the "Show All" navigation
|
||||||
|
*/
|
||||||
|
get shouldShowAll(): boolean {
|
||||||
|
return this.entities.length > 0 && this.showAllRoute !== "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the "You" entity is conflicted
|
||||||
|
*/
|
||||||
|
get youConflicted(): boolean {
|
||||||
|
return this.conflictChecker(this.activeDid);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Entity data for the "You" special entity
|
||||||
|
*/
|
||||||
|
get youEntityData(): { did: string; name: string } {
|
||||||
|
return {
|
||||||
|
did: this.activeDid,
|
||||||
|
name: "You",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Entity data for the "Unnamed" special entity
|
||||||
|
*/
|
||||||
|
get unnamedEntityData(): { did: string; name: string } {
|
||||||
|
return {
|
||||||
|
did: "",
|
||||||
|
name: "Unnamed",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a person DID is conflicted
|
||||||
|
*/
|
||||||
|
isPersonConflicted(did: string): boolean {
|
||||||
|
return this.conflictChecker(did);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle person selection from PersonCard
|
||||||
|
*/
|
||||||
|
handlePersonSelected(person: Contact): void {
|
||||||
|
this.$emit("entity-selected", {
|
||||||
|
type: "person",
|
||||||
|
data: person,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle project selection from ProjectCard
|
||||||
|
*/
|
||||||
|
handleProjectSelected(project: PlanData): void {
|
||||||
|
this.$emit("entity-selected", {
|
||||||
|
type: "project",
|
||||||
|
data: project,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle special entity selection from SpecialEntityCard
|
||||||
|
*/
|
||||||
|
handleEntitySelected(event: { type: string; entityType: string; data: any }): void {
|
||||||
|
this.$emit("entity-selected", {
|
||||||
|
type: "special",
|
||||||
|
entityType: event.entityType,
|
||||||
|
data: event.data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* Grid-specific styles if needed */
|
||||||
|
</style>
|
||||||
75
src/components/ShowAllCard.vue
Normal file
75
src/components/ShowAllCard.vue
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
/**
|
||||||
|
* ShowAllCard.vue - Show All navigation card component
|
||||||
|
*
|
||||||
|
* Extracted from GiftedDialog.vue to handle "Show All" navigation
|
||||||
|
* for both people and projects entity types.
|
||||||
|
*
|
||||||
|
* @author Matthew Raymer
|
||||||
|
*/
|
||||||
|
<template>
|
||||||
|
<li class="cursor-pointer">
|
||||||
|
<router-link
|
||||||
|
:to="navigationRoute"
|
||||||
|
class="block text-center"
|
||||||
|
>
|
||||||
|
<font-awesome
|
||||||
|
icon="circle-right"
|
||||||
|
class="text-blue-500 text-5xl mb-1"
|
||||||
|
/>
|
||||||
|
<h3 class="text-xs text-slate-500 font-medium italic text-ellipsis whitespace-nowrap overflow-hidden">
|
||||||
|
Show All
|
||||||
|
</h3>
|
||||||
|
</router-link>
|
||||||
|
</li>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { Component, Prop, Vue } from "vue-facing-decorator";
|
||||||
|
import { RouteLocationRaw } from "vue-router";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ShowAllCard - Displays "Show All" navigation for entity grids
|
||||||
|
*
|
||||||
|
* Features:
|
||||||
|
* - Provides navigation to full entity listings
|
||||||
|
* - Supports different routes based on entity type
|
||||||
|
* - Maintains context through query parameters
|
||||||
|
* - Consistent visual styling with other cards
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
export default class ShowAllCard extends Vue {
|
||||||
|
/** Type of entities being shown */
|
||||||
|
@Prop({ required: true })
|
||||||
|
entityType!: "people" | "projects";
|
||||||
|
|
||||||
|
/** Route name to navigate to */
|
||||||
|
@Prop({ required: true })
|
||||||
|
routeName!: string;
|
||||||
|
|
||||||
|
/** Query parameters to pass to the route */
|
||||||
|
@Prop({ default: () => ({}) })
|
||||||
|
queryParams!: Record<string, any>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computed navigation route with query parameters
|
||||||
|
*/
|
||||||
|
get navigationRoute(): RouteLocationRaw {
|
||||||
|
return {
|
||||||
|
name: this.routeName,
|
||||||
|
query: this.queryParams,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* Ensure router-link styling is consistent */
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover .fa-circle-right {
|
||||||
|
transform: scale(1.1);
|
||||||
|
transition: transform 0.2s ease;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
135
src/components/SpecialEntityCard.vue
Normal file
135
src/components/SpecialEntityCard.vue
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
/**
|
||||||
|
* SpecialEntityCard.vue - Special entity display component
|
||||||
|
*
|
||||||
|
* Extracted from GiftedDialog.vue to handle special entities like "You"
|
||||||
|
* and "Unnamed" with conflict detection and selection capability.
|
||||||
|
*
|
||||||
|
* @author Matthew Raymer
|
||||||
|
*/
|
||||||
|
<template>
|
||||||
|
<li
|
||||||
|
:class="cardClasses"
|
||||||
|
@click="handleClick"
|
||||||
|
>
|
||||||
|
<font-awesome
|
||||||
|
:icon="icon"
|
||||||
|
:class="iconClasses"
|
||||||
|
/>
|
||||||
|
<h3 :class="nameClasses">
|
||||||
|
{{ label }}
|
||||||
|
</h3>
|
||||||
|
</li>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { Component, Prop, Vue } from "vue-facing-decorator";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SpecialEntityCard - Displays special entities with selection capability
|
||||||
|
*
|
||||||
|
* Features:
|
||||||
|
* - Displays special entities like "You" and "Unnamed"
|
||||||
|
* - Shows appropriate FontAwesome icons
|
||||||
|
* - Handles conflict states and selection
|
||||||
|
* - Emits selection events with entity data
|
||||||
|
* - Configurable styling based on entity type
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
export default class SpecialEntityCard extends Vue {
|
||||||
|
/** Type of special entity */
|
||||||
|
@Prop({ required: true })
|
||||||
|
entityType!: "you" | "unnamed";
|
||||||
|
|
||||||
|
/** Display label for the entity */
|
||||||
|
@Prop({ required: true })
|
||||||
|
label!: string;
|
||||||
|
|
||||||
|
/** FontAwesome icon name */
|
||||||
|
@Prop({ required: true })
|
||||||
|
icon!: string;
|
||||||
|
|
||||||
|
/** Whether this entity can be selected */
|
||||||
|
@Prop({ default: true })
|
||||||
|
selectable!: boolean;
|
||||||
|
|
||||||
|
/** Whether selecting this entity would create a conflict */
|
||||||
|
@Prop({ default: false })
|
||||||
|
conflicted!: boolean;
|
||||||
|
|
||||||
|
/** Entity data to emit when selected */
|
||||||
|
@Prop({ required: true })
|
||||||
|
entityData!: { did?: string; name: string };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computed CSS classes for the card container
|
||||||
|
*/
|
||||||
|
get cardClasses(): string {
|
||||||
|
const baseClasses = "block";
|
||||||
|
|
||||||
|
if (!this.selectable || this.conflicted) {
|
||||||
|
return `${baseClasses} cursor-not-allowed opacity-50`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${baseClasses} cursor-pointer`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computed CSS classes for the icon
|
||||||
|
*/
|
||||||
|
get iconClasses(): string {
|
||||||
|
const baseClasses = "text-5xl mb-1";
|
||||||
|
|
||||||
|
if (this.conflicted) {
|
||||||
|
return `${baseClasses} text-slate-400`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Different colors for different entity types
|
||||||
|
switch (this.entityType) {
|
||||||
|
case "you":
|
||||||
|
return `${baseClasses} text-blue-500`;
|
||||||
|
case "unnamed":
|
||||||
|
return `${baseClasses} text-slate-400`;
|
||||||
|
default:
|
||||||
|
return `${baseClasses} text-slate-400`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computed CSS classes for the entity name/label
|
||||||
|
*/
|
||||||
|
get nameClasses(): string {
|
||||||
|
const baseClasses = "text-xs font-medium text-ellipsis whitespace-nowrap overflow-hidden";
|
||||||
|
|
||||||
|
if (this.conflicted) {
|
||||||
|
return `${baseClasses} text-slate-400`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Different colors for different entity types
|
||||||
|
switch (this.entityType) {
|
||||||
|
case "you":
|
||||||
|
return `${baseClasses} text-blue-500`;
|
||||||
|
case "unnamed":
|
||||||
|
return `${baseClasses} text-slate-500 italic`;
|
||||||
|
default:
|
||||||
|
return `${baseClasses} text-slate-500`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle card click - only emit if selectable and not conflicted
|
||||||
|
*/
|
||||||
|
handleClick(): void {
|
||||||
|
if (this.selectable && !this.conflicted) {
|
||||||
|
this.$emit("entity-selected", {
|
||||||
|
type: "special",
|
||||||
|
entityType: this.entityType,
|
||||||
|
data: this.entityData,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* Component-specific styles if needed */
|
||||||
|
</style>
|
||||||
Reference in New Issue
Block a user