diff --git a/BucketList/63957fff-4848-46fe-ae46-86e966ead769.json b/BucketList/63957fff-4848-46fe-ae46-86e966ead769.json new file mode 100644 index 0000000..ab029c0 --- /dev/null +++ b/BucketList/63957fff-4848-46fe-ae46-86e966ead769.json @@ -0,0 +1,32 @@ +{ + "data": { + "meta": { + "adoptsFrom": { + "name": "BucketList", + "module": "../bucket-list" + } + }, + "type": "card", + "attributes": { + "name": "Bucket List 2025", + "items": [ + "New York Travel", + "Buy Macbook", + "Movie Marathon" + ], + "cardInfo": { + "notes": null, + "name": null, + "summary": null, + "cardThumbnailURL": null + } + }, + "relationships": { + "cardInfo.theme": { + "links": { + "self": null + } + } + } + } +} diff --git a/CardListing/0a43b1b4-da21-4981-9b30-f6429516c4c9.json b/CardListing/0a43b1b4-da21-4981-9b30-f6429516c4c9.json new file mode 100644 index 0000000..dc333ac --- /dev/null +++ b/CardListing/0a43b1b4-da21-4981-9b30-f6429516c4c9.json @@ -0,0 +1,69 @@ +{ + "data": { + "meta": { + "adoptsFrom": { + "name": "CardListing", + "module": "https://realms-staging.stack.cards/catalog/catalog-app/listing/listing" + } + }, + "type": "card", + "attributes": { + "name": "Bucket List Card Definition", + "images": [], + "summary": "The BucketList component provides an interface for managing a customizable list of items, typically used as a personal or shared bucket list. It displays the list’s name and item count, and allows users to add, edit, or remove items dynamically through an embedded chips-style editor interface. The primary purpose of this component is to facilitate easy creation and modification of a list of goals or milestones in a clear, interactive format.", + "cardInfo": { + "name": null, + "notes": null, + "summary": null, + "cardThumbnailURL": null + } + }, + "relationships": { + "specs.0": { + "links": { + "self": "../Spec/ac9fc947-61c2-4e6c-9d5e-025d34cd27b6" + } + }, + "specs.1": { + "links": { + "self": "../Spec/9fc94761-c2fe-4cdd-9e02-5d34cd27b67c" + } + }, + "skills": { + "links": { + "self": null + } + }, + "tags.0": { + "links": { + "self": "https://realms-staging.stack.cards/catalog/Tag/51de249c-516a-4c4d-bd88-76e88274c483" + } + }, + "license": { + "links": { + "self": "https://realms-staging.stack.cards/catalog/License/4c5a023b-a72c-4f90-930b-da60a1de5b2d" + } + }, + "publisher": { + "links": { + "self": null + } + }, + "examples.0": { + "links": { + "self": "../BucketList/63957fff-4848-46fe-ae46-86e966ead769" + } + }, + "categories.0": { + "links": { + "self": "https://realms-staging.stack.cards/catalog/Category/goals-habits" + } + }, + "cardInfo.theme": { + "links": { + "self": null + } + } + } + } +} \ No newline at end of file diff --git a/Spec/9fc94761-c2fe-4cdd-9e02-5d34cd27b67c.json b/Spec/9fc94761-c2fe-4cdd-9e02-5d34cd27b67c.json new file mode 100644 index 0000000..e5d7e66 --- /dev/null +++ b/Spec/9fc94761-c2fe-4cdd-9e02-5d34cd27b67c.json @@ -0,0 +1,40 @@ +{ + "data": { + "type": "card", + "attributes": { + "readMe": null, + "ref": { + "module": "../components/chips-editor", + "name": "ChipsEditor" + }, + "specType": "component", + "containedExamples": [], + "cardTitle": "ChipsEditor", + "cardDescription": null, + "cardInfo": { + "name": null, + "summary": null, + "cardThumbnailURL": null, + "notes": null + } + }, + "relationships": { + "linkedExamples": { + "links": { + "self": null + } + }, + "cardInfo.theme": { + "links": { + "self": null + } + } + }, + "meta": { + "adoptsFrom": { + "module": "https://cardstack.com/base/spec", + "name": "Spec" + } + } + } +} \ No newline at end of file diff --git a/Spec/ac9fc947-61c2-4e6c-9d5e-025d34cd27b6.json b/Spec/ac9fc947-61c2-4e6c-9d5e-025d34cd27b6.json new file mode 100644 index 0000000..b8e37f9 --- /dev/null +++ b/Spec/ac9fc947-61c2-4e6c-9d5e-025d34cd27b6.json @@ -0,0 +1,40 @@ +{ + "data": { + "meta": { + "adoptsFrom": { + "name": "Spec", + "module": "https://cardstack.com/base/spec" + } + }, + "type": "card", + "attributes": { + "ref": { + "name": "BucketList", + "module": "../bucket-list" + }, + "readMe": "## BucketList Card Spec\n\n### Summary\nThe BucketList spec defines a card that allows users to manage a list of items, represented as a set of strings. It includes a header with the list name and the number of items, as well as a ChipsEditor component that lets users add, remove, and rearrange the list items.\n\n### Import\n```javascript\nimport { BucketList } from 'https://realms-staging.stack.cards/experiments/bucket-list';\n```\n\n### Usage as a Field\nTo use the BucketList card as a field within a consuming card or field, you can include it like this:\n\n```javascript\nimport { CardDef, field, contains } from 'https://cardstack.com/base/card-api';\nimport { BucketList } from 'https://realms-staging.stack.cards/experiments/bucket-list';\n\nclass MyCard extends CardDef {\n @field bucketList = contains(BucketList);\n}\n```\n\n### Template Usage\nTo display the BucketList card within a consuming card or field, you can use the following template syntax:\n\n```handlebars\n\n```\n\nThis will render the BucketList card using the data from the `bucketList` field.", + "cardInfo": { + "name": null, + "notes": null, + "summary": null, + "cardThumbnailURL": null + }, + "specType": "card", + "cardTitle": "Card", + "cardDescription": null, + "containedExamples": [] + }, + "relationships": { + "cardInfo.theme": { + "links": { + "self": null + } + }, + "linkedExamples": { + "links": { + "self": null + } + } + } + } +} \ No newline at end of file diff --git a/bucket-list.gts b/bucket-list.gts new file mode 100644 index 0000000..6f2f6e6 --- /dev/null +++ b/bucket-list.gts @@ -0,0 +1,48 @@ +import { + CardDef, + field, + contains, + containsMany, + StringField, + Component, +} from 'https://cardstack.com/base/card-api'; +import { ChipsEditor } from './components/chips-editor'; + +// BucketList Isolated View - now uses the reusable ChipsComponent +class BucketListIsolated extends Component { + updateItems = (items: string[]) => { + this.args.model.items = items; + }; + + get listingName() { + const hasName = !!this.args.model.name?.trim(); + return hasName ? this.args.model.name : 'Untitled List'; + } + + +} + +export class BucketList extends CardDef { + @field name = contains(StringField); + @field items = containsMany(StringField); + + static isolated = BucketListIsolated; +} diff --git a/components/chips-editor.gts b/components/chips-editor.gts new file mode 100644 index 0000000..738a38f --- /dev/null +++ b/components/chips-editor.gts @@ -0,0 +1,156 @@ +import GlimmerComponent from '@glimmer/component'; +import { tracked } from '@glimmer/tracking'; +import { fn } from '@ember/helper'; +import { on } from '@ember/modifier'; +import { action } from '@ember/object'; + +import { IconX } from '@cardstack/boxel-ui/icons'; +import { Pill, BoxelInput } from '@cardstack/boxel-ui/components'; + +// Chip Component +interface ChipSignature { + Args: { + label: string; + onDelete?: (event: Event) => void; + }; + Element: HTMLElement; +} + +// Chip Component +class Chip extends GlimmerComponent { + @action + handleDelete(event: Event) { + event.preventDefault(); + event.stopPropagation(); + + if (this.args.onDelete) { + this.args.onDelete(event); + } + } + + +} + +// Reusable Chips Editor Component +interface ChipsEditorSignature { + Args: { + name?: string; + items: string[] | undefined; + onItemsUpdate: (items: string[]) => void; + placeholder?: string; + }; + Element: HTMLElement; +} + +export class ChipsEditor extends GlimmerComponent { + @tracked newItemValue = ''; + + updateNewItemValue = (value: string) => { + this.newItemValue = value; + }; + + handleKeyPress = (event: KeyboardEvent) => { + if (event.key === 'Enter' && this.newItemValue.trim()) { + event.preventDefault(); + + // Add new item to the array + const newItem = this.newItemValue.trim(); + const updatedItems = [...(this.args.items || []), newItem]; + this.args.onItemsUpdate(updatedItems); + + // Clear the input + this.newItemValue = ''; + } + }; + + deleteItem = (index: number) => { + if (this.args.items) { + let updatedItems = [...this.args.items]; + updatedItems.splice(index, 1); + this.args.onItemsUpdate(updatedItems); + } + }; + + +}