@flamingo-stack/openframe-frontend-core 0.0.550-1809.4788.1 → 0.0.550-1809.4789.1

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flamingo-stack/openframe-frontend-core",
3
- "version": "0.0.550-1809.4788.1",
3
+ "version": "0.0.550-1809.4789.1",
4
4
  "description": "Shared design system and components for all Flamingo platforms",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -0,0 +1,95 @@
1
+ import type { Meta, StoryObj } from '@storybook/nextjs-vite'
2
+ import { EmployeeEntryCard, EmployeeEntryBadge } from '../components/chat/entity-cards/employee-entry-card'
3
+ import { ChatColumnDecorator, makeAnchorProps } from './__fixtures__/chat-card-decorator'
4
+ import { howIWorkEntry, whatIShippedEntry } from './__fixtures__/chat-cards'
5
+
6
+ const meta: Meta<typeof EmployeeEntryCard> = {
7
+ title: 'Chat/EntityCards/EmployeeEntryCard',
8
+ component: EmployeeEntryCard,
9
+ parameters: {
10
+ layout: 'fullscreen',
11
+ docs: {
12
+ description: {
13
+ component:
14
+ 'THE people-hub employee-entry engine. `WhatIShippedCard` and `HowIWorkCard` are thin bindings over it — they differ only in which date column they format and which extra badges they add — so the states exercised here are the states of every employee-entry card. Cover falls back featured_image → main_video_thumbnail → placeholderUrl.',
15
+ },
16
+ },
17
+ },
18
+ decorators: [(Story) => <ChatColumnDecorator><Story /></ChatColumnDecorator>],
19
+ }
20
+
21
+ export default meta
22
+ type Story = StoryObj<typeof meta>
23
+
24
+ export const Published: Story = {
25
+ args: {
26
+ entry: whatIShippedEntry,
27
+ dateLabel: 'Jul 2026',
28
+ },
29
+ }
30
+
31
+ export const Draft: Story = {
32
+ args: {
33
+ entry: { ...whatIShippedEntry, status: 'draft' },
34
+ dateLabel: 'Jul 2026',
35
+ },
36
+ }
37
+
38
+ export const Archived: Story = {
39
+ args: {
40
+ entry: { ...whatIShippedEntry, status: 'archived' },
41
+ dateLabel: 'Jul 2026',
42
+ },
43
+ }
44
+
45
+ export const WithExtraBadges: Story = {
46
+ args: {
47
+ entry: howIWorkEntry,
48
+ dateLabel: 'Aug 16, 2026',
49
+ extraBadges: (
50
+ <>
51
+ <EmployeeEntryBadge>Marketing</EmployeeEntryBadge>
52
+ <EmployeeEntryBadge>Intermediate</EmployeeEntryBadge>
53
+ </>
54
+ ),
55
+ },
56
+ }
57
+
58
+ /** Cover falls back to the video frame when there is no featured image. */
59
+ export const VideoThumbnailFallback: Story = {
60
+ args: {
61
+ entry: {
62
+ ...whatIShippedEntry,
63
+ featured_image: null,
64
+ main_video_thumbnail:
65
+ 'https://images.unsplash.com/photo-1461749280684-dccba630e2f6?w=1200&h=675&fit=crop',
66
+ },
67
+ dateLabel: 'Jul 2026',
68
+ },
69
+ }
70
+
71
+ /** The auto-created-draft state: no title yet, no summary, no cover — the card
72
+ * must still read as a specific entity, not an error. */
73
+ export const EmptyDraft: Story = {
74
+ args: {
75
+ entry: {
76
+ title: null,
77
+ summary: null,
78
+ status: 'draft',
79
+ featured_image: null,
80
+ main_video_thumbnail: null,
81
+ author: whatIShippedEntry.author,
82
+ },
83
+ dateLabel: null,
84
+ untitledLabel: 'Untitled session',
85
+ },
86
+ }
87
+
88
+ /** Related-rail mode: the whole card is a link (never combined with actions). */
89
+ export const AsAnchor: Story = {
90
+ args: {
91
+ entry: whatIShippedEntry,
92
+ dateLabel: 'Jul 2026',
93
+ anchorProps: makeAnchorProps('/what-i-shipped/billing-flow'),
94
+ },
95
+ }
@@ -0,0 +1,47 @@
1
+ import type { Meta, StoryObj } from '@storybook/nextjs-vite'
2
+ import { HowIWorkCard, HowIWorkCardSkeleton } from '../components/chat/entity-cards/how-i-work-card'
3
+ import { ChatColumnDecorator, makeAnchorProps } from './__fixtures__/chat-card-decorator'
4
+ import { howIWorkEntry } from './__fixtures__/chat-cards'
5
+
6
+ const meta: Meta<typeof HowIWorkCard> = {
7
+ title: 'Chat/EntityCards/HowIWorkCard',
8
+ component: HowIWorkCard,
9
+ parameters: {
10
+ layout: 'fullscreen',
11
+ docs: {
12
+ description: {
13
+ component:
14
+ 'Card for a `how_i_work` session — a thin binding over `EmployeeEntryCard` that maps `session_date` to the meta date and adds the discipline badge. Engine states (statuses, cover fallbacks, empty draft) are covered by the EmployeeEntryCard stories.',
15
+ },
16
+ },
17
+ },
18
+ decorators: [(Story) => <ChatColumnDecorator><Story /></ChatColumnDecorator>],
19
+ }
20
+
21
+ export default meta
22
+ type Story = StoryObj<typeof meta>
23
+
24
+ export const Default: Story = {
25
+ args: {
26
+ entry: howIWorkEntry,
27
+ },
28
+ }
29
+
30
+ /** Related-rail click-through — the whole card is one anchor. */
31
+ export const AsAnchor: Story = {
32
+ args: {
33
+ entry: howIWorkEntry,
34
+ anchorProps: makeAnchorProps('/how-i-work/' + 'competitive-research-claude'),
35
+ },
36
+ }
37
+
38
+ /** No discipline picked yet (fresh draft) — badge row shows status only. */
39
+ export const NoDiscipline: Story = {
40
+ args: {
41
+ entry: { ...howIWorkEntry, discipline: null, status: 'draft' },
42
+ },
43
+ }
44
+
45
+ export const Skeleton: StoryObj = {
46
+ render: () => <HowIWorkCardSkeleton />,
47
+ }
@@ -0,0 +1,74 @@
1
+ import type { Meta, StoryObj } from '@storybook/nextjs-vite'
2
+ import { useState } from 'react'
3
+ import { StatusFilterComponent } from '../components/features/status-filter-component'
4
+
5
+ const meta: Meta<typeof StatusFilterComponent> = {
6
+ title: 'Features/StatusFilterComponent',
7
+ component: StatusFilterComponent,
8
+ parameters: {
9
+ layout: 'padded',
10
+ docs: {
11
+ description: {
12
+ component:
13
+ 'THE single-select facet filter row. Renders its own "All" button and drops any option valued `all`, so callers pass real options only. The `label` prop lets one component serve every facet axis (Status, Discipline, Level) — hand-rolling a look-alike row produces markup identical to this one, and React then pairs the copy against this component during hydration and reports a text mismatch (the bug that motivated the prop).',
14
+ },
15
+ },
16
+ },
17
+ }
18
+
19
+ export default meta
20
+ type Story = StoryObj<typeof meta>
21
+
22
+ /** Default label. Interactive — pills toggle the selection. */
23
+ export const Status: Story = {
24
+ render: function StatusStory() {
25
+ const [status, setStatus] = useState('all')
26
+ return (
27
+ <StatusFilterComponent
28
+ selectedStatus={status}
29
+ onStatusChange={setStatus}
30
+ statusOptions={[
31
+ { value: 'draft', label: 'Draft (1)' },
32
+ { value: 'published', label: 'Published (3)' },
33
+ { value: 'archived', label: 'Archived (1)' },
34
+ ]}
35
+ />
36
+ )
37
+ },
38
+ }
39
+
40
+ /** Relabeled: the same row serving a data-derived Discipline facet, counts in
41
+ * the labels — how the people-hub How I Work dashboard uses it. */
42
+ export const RelabeledFacet: Story = {
43
+ render: function DisciplineStory() {
44
+ const [discipline, setDiscipline] = useState('all')
45
+ return (
46
+ <StatusFilterComponent
47
+ label="Discipline"
48
+ selectedStatus={discipline}
49
+ onStatusChange={setDiscipline}
50
+ statusOptions={[
51
+ { value: 'engineering', label: 'Engineering (4)' },
52
+ { value: 'marketing', label: 'Marketing (2)' },
53
+ { value: 'sales', label: 'Sales (0)' },
54
+ ]}
55
+ />
56
+ )
57
+ },
58
+ }
59
+
60
+ /** Viewer may see every option but select only one (people-hub "Everyone" view
61
+ * for non-management: pinned to Published, the rest visible but disabled). */
62
+ export const DisabledValues: Story = {
63
+ args: {
64
+ label: 'Status',
65
+ selectedStatus: 'published',
66
+ onStatusChange: () => {},
67
+ statusOptions: [
68
+ { value: 'draft', label: 'Draft' },
69
+ { value: 'published', label: 'Published' },
70
+ { value: 'archived', label: 'Archived' },
71
+ ],
72
+ disabledValues: ['all', 'draft', 'archived'],
73
+ },
74
+ }
@@ -0,0 +1,40 @@
1
+ import type { Meta, StoryObj } from '@storybook/nextjs-vite'
2
+ import { WhatIShippedCard, WhatIShippedCardSkeleton } from '../components/chat/entity-cards/what-i-shipped-card'
3
+ import { ChatColumnDecorator, makeAnchorProps } from './__fixtures__/chat-card-decorator'
4
+ import { whatIShippedEntry } from './__fixtures__/chat-cards'
5
+
6
+ const meta: Meta<typeof WhatIShippedCard> = {
7
+ title: 'Chat/EntityCards/WhatIShippedCard',
8
+ component: WhatIShippedCard,
9
+ parameters: {
10
+ layout: 'fullscreen',
11
+ docs: {
12
+ description: {
13
+ component:
14
+ 'Card for a monthly `what_i_shipped` entry — a thin binding over `EmployeeEntryCard` that maps `entry_month` to the meta date. Engine states (statuses, cover fallbacks, empty draft) are covered by the EmployeeEntryCard stories.',
15
+ },
16
+ },
17
+ },
18
+ decorators: [(Story) => <ChatColumnDecorator><Story /></ChatColumnDecorator>],
19
+ }
20
+
21
+ export default meta
22
+ type Story = StoryObj<typeof meta>
23
+
24
+ export const Default: Story = {
25
+ args: {
26
+ entry: whatIShippedEntry,
27
+ },
28
+ }
29
+
30
+ /** Related-rail click-through — the whole card is one anchor. */
31
+ export const AsAnchor: Story = {
32
+ args: {
33
+ entry: whatIShippedEntry,
34
+ anchorProps: makeAnchorProps('/what-i-shipped/billing-flow'),
35
+ },
36
+ }
37
+
38
+ export const Skeleton: StoryObj = {
39
+ render: () => <WhatIShippedCardSkeleton />,
40
+ }
@@ -30,6 +30,8 @@ import type {
30
30
  CampaignCardItem,
31
31
  GenericEntityCardItem,
32
32
  } from '../../components/chat/entity-cards'
33
+ import type { WhatIShippedCardData } from '../../components/chat/entity-cards/what-i-shipped-card'
34
+ import type { HowIWorkCardData } from '../../components/chat/entity-cards/how-i-work-card'
33
35
 
34
36
  // =============================================================================
35
37
  // Common
@@ -599,3 +601,41 @@ export const campaignItem: CampaignCardItem = {
599
601
  { id: 'g2', label: '12 podcast plays' },
600
602
  ],
601
603
  }
604
+
605
+ // =============================================================================
606
+ // Employee entries (people-hub) — What I Shipped / How I Work
607
+ // =============================================================================
608
+
609
+
610
+ export const whatIShippedEntry: WhatIShippedCardData = {
611
+ title: 'Shipped the new billing flow + cut sync latency 40%',
612
+ summary:
613
+ 'Rebuilt the checkout path around the unified billing service, moved webhook fan-out to the outbox drainer, and deleted the legacy cron.',
614
+ status: 'published',
615
+ entry_month: '2026-07-01',
616
+ featured_image:
617
+ 'https://images.unsplash.com/photo-1551288049-bebda4e38f71?w=1200&h=675&fit=crop',
618
+ main_video_thumbnail: null,
619
+ author: {
620
+ full_name: 'Yevhenii Petrenko',
621
+ avatar_url:
622
+ 'https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?w=120&h=120&fit=crop',
623
+ },
624
+ }
625
+
626
+ export const howIWorkEntry: HowIWorkCardData = {
627
+ title: 'Competitive research in 40 minutes with a standing Claude prompt',
628
+ summary:
629
+ 'Three separate single-purpose conversations (pricing, docs gaps, changelog velocity), exported as JSON into Notion through a Zapier automation.',
630
+ status: 'published',
631
+ session_date: '2026-08-16',
632
+ discipline: 'marketing',
633
+ featured_image:
634
+ 'https://images.unsplash.com/photo-1552664730-d307ca884978?w=1200&h=675&fit=crop',
635
+ main_video_thumbnail: null,
636
+ author: {
637
+ full_name: 'Semen Kovalenko',
638
+ avatar_url:
639
+ 'https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?w=120&h=120&fit=crop',
640
+ },
641
+ }