@flamingo-stack/openframe-frontend-core 0.0.253 → 0.0.254

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.
Files changed (41) hide show
  1. package/dist/{chunk-E7LM7TPK.cjs → chunk-65JN25VM.cjs} +25 -25
  2. package/dist/{chunk-E7LM7TPK.cjs.map → chunk-65JN25VM.cjs.map} +1 -1
  3. package/dist/{chunk-N37WSTB7.js → chunk-BE472J6X.js} +2 -2
  4. package/dist/{chunk-4WFV4OTE.js → chunk-CAPVFI5A.js} +2 -2
  5. package/dist/{chunk-TNC2OENX.cjs → chunk-HYJ5IV4Z.cjs} +11 -11
  6. package/dist/{chunk-TNC2OENX.cjs.map → chunk-HYJ5IV4Z.cjs.map} +1 -1
  7. package/dist/{chunk-IA6B3HHQ.js → chunk-NP5U35FA.js} +53 -29
  8. package/dist/chunk-NP5U35FA.js.map +1 -0
  9. package/dist/{chunk-AXCLUHMQ.js → chunk-QCRQNKRO.js} +2 -2
  10. package/dist/{chunk-YEWZVXKX.cjs → chunk-TSY72CQ6.cjs} +28 -28
  11. package/dist/{chunk-YEWZVXKX.cjs.map → chunk-TSY72CQ6.cjs.map} +1 -1
  12. package/dist/{chunk-44UP5DJB.cjs → chunk-ZITMX2IQ.cjs} +53 -29
  13. package/dist/chunk-ZITMX2IQ.cjs.map +1 -0
  14. package/dist/components/chat/index.cjs +2 -2
  15. package/dist/components/chat/index.js +1 -1
  16. package/dist/components/contact/index.cjs +3 -3
  17. package/dist/components/contact/index.js +2 -2
  18. package/dist/components/features/index.cjs +2 -2
  19. package/dist/components/features/index.js +1 -1
  20. package/dist/components/index.cjs +49 -49
  21. package/dist/components/index.js +3 -3
  22. package/dist/components/navigation/index.cjs +2 -2
  23. package/dist/components/navigation/index.js +1 -1
  24. package/dist/components/related-content/index.cjs +3 -3
  25. package/dist/components/related-content/index.js +2 -2
  26. package/dist/components/tickets/index.cjs +55 -55
  27. package/dist/components/tickets/index.js +3 -3
  28. package/dist/components/ui/index.cjs +2 -2
  29. package/dist/components/ui/index.js +1 -1
  30. package/dist/components/ui/info-card.d.ts +16 -1
  31. package/dist/components/ui/info-card.d.ts.map +1 -1
  32. package/dist/index.cjs +2 -2
  33. package/dist/index.js +1 -1
  34. package/package.json +1 -1
  35. package/src/components/ui/info-card.tsx +87 -42
  36. package/src/stories/InfoCard.stories.tsx +60 -5
  37. package/dist/chunk-44UP5DJB.cjs.map +0 -1
  38. package/dist/chunk-IA6B3HHQ.js.map +0 -1
  39. /package/dist/{chunk-N37WSTB7.js.map → chunk-BE472J6X.js.map} +0 -0
  40. /package/dist/{chunk-4WFV4OTE.js.map → chunk-CAPVFI5A.js.map} +0 -0
  41. /package/dist/{chunk-AXCLUHMQ.js.map → chunk-QCRQNKRO.js.map} +0 -0
@@ -7,7 +7,21 @@ import { CheckIcon } from '../icons-v2-generated'
7
7
  import { useCopyToClipboard } from '../../hooks'
8
8
  import { ProgressBar } from './progress-bar'
9
9
 
10
- interface InfoCardData {
10
+ export interface InfoCardFooterData {
11
+ /** Leading icon next to the text, expected at 24x24 (e.g. <ShieldCheckIcon size={24} />) */
12
+ icon?: React.ReactNode
13
+ text: string
14
+ /** Trailing icon/logo aligned to the right edge, expected at 24x24 */
15
+ logo?: React.ReactNode
16
+ /** External resource link rendered below the text row */
17
+ link?: {
18
+ href: string
19
+ /** Defaults to href without the protocol */
20
+ label?: string
21
+ }
22
+ }
23
+
24
+ export interface InfoCardData {
11
25
  title?: string
12
26
  subtitle?: string
13
27
  icon?: React.ReactNode
@@ -22,6 +36,8 @@ interface InfoCardData {
22
36
  criticalThreshold?: number
23
37
  inverted?: boolean // if true, high values are good (green), low values are bad (red)
24
38
  }
39
+ /** Optional footer rendered below the content, separated by a divider line */
40
+ footer?: InfoCardFooterData
25
41
  }
26
42
 
27
43
  interface InfoCardProps {
@@ -33,54 +49,83 @@ export function InfoCard({ data, className = '' }: InfoCardProps) {
33
49
  return (
34
50
  <div
35
51
  className={cn(
36
- 'bg-ods-card border border-ods-border rounded-md p-[var(--spacing-system-m)] flex flex-col gap-[var(--spacing-system-s)] items-start w-full',
52
+ 'bg-ods-card border border-ods-border rounded-md overflow-hidden flex flex-col w-full',
37
53
  className,
38
54
  )}
39
55
  >
40
- {data.title && (
41
- <div className="flex items-center gap-[var(--spacing-system-xsf)] self-stretch h-6">
42
- <span className="text-h4 text-ods-text-primary truncate" title={data.title}>
43
- {data.title}
44
- </span>
45
- {data.icon}
46
- </div>
47
- )}
56
+ <div className="p-[var(--spacing-system-m)] flex flex-col gap-[var(--spacing-system-s)] items-start w-full">
57
+ {data.title && (
58
+ <div className="flex items-center gap-[var(--spacing-system-xsf)] self-stretch h-6">
59
+ <span className="text-h4 text-ods-text-primary truncate" title={data.title}>
60
+ {data.title}
61
+ </span>
62
+ {data.icon}
63
+ </div>
64
+ )}
48
65
 
49
- {/* Subtitle */}
50
- {data.subtitle && (
51
- <div className="text-h4 text-ods-text-secondary truncate self-stretch" title={data.subtitle}>
52
- {data.subtitle}
53
- </div>
54
- )}
66
+ {/* Subtitle */}
67
+ {data.subtitle && (
68
+ <div className="text-h4 text-ods-text-secondary truncate self-stretch" title={data.subtitle}>
69
+ {data.subtitle}
70
+ </div>
71
+ )}
55
72
 
56
- {/* Info items */}
57
- {data.items.map((item, index) => {
58
- const values = Array.isArray(item.value) ? item.value : [item.value]
73
+ {/* Info items */}
74
+ {data.items.map((item, index) => {
75
+ const values = Array.isArray(item.value) ? item.value : [item.value]
59
76
 
60
- return (
61
- <React.Fragment key={index}>
62
- {values.map((val, valIndex) => (
63
- <InfoCardValueRow
64
- key={`${index}-${valIndex}`}
65
- label={item.label}
66
- value={val}
67
- showLabel={valIndex === 0}
68
- copyable={item.copyable}
69
- copyAriaLabel={`Copy ${item.label} ${valIndex + 1}`}
70
- />
71
- ))}
72
- </React.Fragment>
73
- )
74
- })}
77
+ return (
78
+ <React.Fragment key={index}>
79
+ {values.map((val, valIndex) => (
80
+ <InfoCardValueRow
81
+ key={`${index}-${valIndex}`}
82
+ label={item.label}
83
+ value={val}
84
+ showLabel={valIndex === 0}
85
+ copyable={item.copyable}
86
+ copyAriaLabel={`Copy ${item.label ?? 'value'} ${valIndex + 1}`}
87
+ />
88
+ ))}
89
+ </React.Fragment>
90
+ )
91
+ })}
75
92
 
76
- {/* Progress bar */}
77
- {data.progress && (
78
- <ProgressBar
79
- progress={data.progress.value}
80
- warningThreshold={data.progress.warningThreshold}
81
- criticalThreshold={data.progress.criticalThreshold}
82
- inverted={data.progress.inverted}
83
- />
93
+ {/* Progress bar */}
94
+ {data.progress && (
95
+ <ProgressBar
96
+ progress={data.progress.value}
97
+ warningThreshold={data.progress.warningThreshold}
98
+ criticalThreshold={data.progress.criticalThreshold}
99
+ inverted={data.progress.inverted}
100
+ />
101
+ )}
102
+ </div>
103
+
104
+ {/* Footer */}
105
+ {data.footer && <InfoCardFooter footer={data.footer} />}
106
+ </div>
107
+ )
108
+ }
109
+
110
+ function InfoCardFooter({ footer }: { footer: InfoCardFooterData }) {
111
+ return (
112
+ <div className="mt-auto border-t border-ods-border p-[var(--spacing-system-m)] flex flex-col w-full">
113
+ <div className="flex items-center justify-between gap-1 w-full">
114
+ <div className="flex items-center gap-1">
115
+ {footer.icon}
116
+ <span className="text-h4 text-ods-text-primary">{footer.text}</span>
117
+ </div>
118
+ {footer.logo}
119
+ </div>
120
+ {footer.link && (
121
+ <a
122
+ href={footer.link.href}
123
+ target="_blank"
124
+ rel="noopener noreferrer"
125
+ className="text-h6 text-ods-text-secondary underline truncate hover:text-ods-text-primary"
126
+ >
127
+ {footer.link.label ?? footer.link.href.replace(/^https?:\/\//, '')}
128
+ </a>
84
129
  )}
85
130
  </div>
86
131
  )
@@ -1,5 +1,7 @@
1
1
  import type { Meta, StoryObj } from '@storybook/nextjs-vite'
2
2
  import { InfoCard } from '../components'
3
+ import { ShieldCheckIcon } from '../components/icons-v2-generated/security'
4
+ import { FlamingoLogo } from '../components/icons'
3
5
 
4
6
  const meta = {
5
7
  title: 'UI/InfoCard',
@@ -9,7 +11,7 @@ const meta = {
9
11
  docs: {
10
12
  description: {
11
13
  component:
12
- 'A dark-themed info card showing a title, optional subtitle, label/value rows with dotted leaders, optional copy buttons, and an optional progress bar.',
14
+ 'A dark-themed info card showing a title, optional subtitle, label/value rows with dotted leaders, optional copy buttons, an optional progress bar, and an optional footer with an icon, text, trailing logo, and external link, separated by a divider line.',
13
15
  },
14
16
  },
15
17
  },
@@ -32,7 +34,7 @@ export const DiskC: Story = {
32
34
  },
33
35
  },
34
36
  render: (args) => (
35
- <div style={{ width: 320 }}>
37
+ <div className="w-80">
36
38
  <InfoCard {...args} />
37
39
  </div>
38
40
  ),
@@ -51,7 +53,7 @@ export const DiskD: Story = {
51
53
  },
52
54
  },
53
55
  render: (args) => (
54
- <div style={{ width: 320 }}>
56
+ <div className="w-80">
55
57
  <InfoCard {...args} />
56
58
  </div>
57
59
  ),
@@ -70,7 +72,7 @@ export const PhysicalRAM: Story = {
70
72
  },
71
73
  },
72
74
  render: (args) => (
73
- <div style={{ width: 320 }}>
75
+ <div className="w-80">
74
76
  <InfoCard {...args} />
75
77
  </div>
76
78
  ),
@@ -86,10 +88,63 @@ export const WithCopyableValues: Story = {
86
88
  { label: 'IPv4', value: '192.168.1.100', copyable: true },
87
89
  { label: 'MAC', value: '00:1A:2B:3C:4D:5E', copyable: true },
88
90
  ],
91
+ footer: {
92
+ icon: <ShieldCheckIcon size={24} className="text-ods-success" />,
93
+ text: 'Signed by Flamingo',
94
+ logo: <FlamingoLogo width={24} height={24} />,
95
+ link: { href: 'https://github.com/flamingo-ai/fleetdm' },
96
+ },
97
+ },
98
+ },
99
+ render: (args) => (
100
+ <div className="w-96">
101
+ <InfoCard {...args} />
102
+ </div>
103
+ ),
104
+ }
105
+
106
+ export const WithFooter: Story = {
107
+ args: {
108
+ data: {
109
+ title: 'Fleet',
110
+ items: [
111
+ { label: 'Status', value: 'ONLINE' },
112
+ { label: 'Last seen', value: '06/11/26 9:35 AM' },
113
+ { label: 'ID', value: '7', copyable: true },
114
+ { label: 'Version', value: '0.1.8' },
115
+ ],
116
+ footer: {
117
+ icon: <ShieldCheckIcon size={24} className="text-ods-success" />,
118
+ text: 'Signed by Flamingo',
119
+ logo: <FlamingoLogo width={24} height={24} />,
120
+ link: { href: 'https://github.com/flamingo-ai/fleetdm' },
121
+ },
122
+ },
123
+ },
124
+ render: (args) => (
125
+ <div className="w-96">
126
+ <InfoCard {...args} />
127
+ </div>
128
+ ),
129
+ }
130
+
131
+ export const WithFooterWithoutLink: Story = {
132
+ args: {
133
+ data: {
134
+ title: 'Fleet',
135
+ items: [
136
+ { label: 'Status', value: 'ONLINE' },
137
+ { label: 'Version', value: '0.1.8' },
138
+ ],
139
+ footer: {
140
+ icon: <ShieldCheckIcon size={24} className="text-ods-success" />,
141
+ text: 'Signed by Flamingo',
142
+ logo: <FlamingoLogo width={24} height={24} />,
143
+ },
89
144
  },
90
145
  },
91
146
  render: (args) => (
92
- <div style={{ width: 360 }}>
147
+ <div className="w-96">
93
148
  <InfoCard {...args} />
94
149
  </div>
95
150
  ),