@countermeasure-platform/web-components 1.3.5 → 1.3.6-dev.37.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.
Files changed (82) hide show
  1. package/dist/{component-D5sRm1fq.js → component-D_asjmrt.js} +5 -3
  2. package/dist/component-D_asjmrt.js.map +1 -0
  3. package/dist/components/brand/index.d.ts.map +1 -1
  4. package/dist/components/brand/index.js +11 -5
  5. package/dist/components/brand/index.js.map +1 -1
  6. package/dist/components/brand/types.d.ts +2 -0
  7. package/dist/components/brand/types.d.ts.map +1 -1
  8. package/dist/icons/index.d.ts +7 -2
  9. package/dist/icons/index.d.ts.map +1 -1
  10. package/dist/icons/index.js +7 -2
  11. package/dist/icons/index.js.map +1 -1
  12. package/dist/icons/lucide.d.ts +3 -0
  13. package/dist/icons/lucide.d.ts.map +1 -1
  14. package/dist/icons/lucide.js +3 -0
  15. package/dist/icons/lucide.js.map +1 -1
  16. package/dist/index.d.ts +1 -1
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +126 -125
  19. package/dist/layout/app-shell.d.ts +7 -0
  20. package/dist/layout/app-shell.d.ts.map +1 -1
  21. package/dist/layout/app-shell.js +24 -6
  22. package/dist/layout/app-shell.js.map +1 -1
  23. package/dist/layout/core-app-chrome.d.ts.map +1 -1
  24. package/dist/layout/core-app-chrome.js +8 -5
  25. package/dist/layout/core-app-chrome.js.map +1 -1
  26. package/dist/layout/core-app-library-dashboard.d.ts +72 -0
  27. package/dist/layout/core-app-library-dashboard.d.ts.map +1 -0
  28. package/dist/layout/core-app-library-dashboard.js +160 -0
  29. package/dist/layout/core-app-library-dashboard.js.map +1 -0
  30. package/dist/layout/index.d.ts +2 -0
  31. package/dist/layout/index.d.ts.map +1 -1
  32. package/dist/layout/index.js +38 -37
  33. package/dist/layout/index.js.map +1 -1
  34. package/dist/react/brand/index.d.ts +3 -1
  35. package/dist/react/brand/index.d.ts.map +1 -1
  36. package/dist/react/brand.js +19 -13
  37. package/dist/react/brand.js.map +1 -1
  38. package/dist/react/layout/core-app-library-dashboard.d.ts +13 -0
  39. package/dist/react/layout/core-app-library-dashboard.d.ts.map +1 -0
  40. package/dist/react/layout/core-app-library-dashboard.js +27 -0
  41. package/dist/react/layout/core-app-library-dashboard.js.map +1 -0
  42. package/dist/react/layout/index.d.ts +1 -0
  43. package/dist/react/layout/index.d.ts.map +1 -1
  44. package/dist/react/layout/index.js +3 -2
  45. package/dist/react/primitives/alert.d.ts +1 -1
  46. package/dist/react/primitives/toast.d.ts +1 -1
  47. package/dist/react/sidebar.js +1 -1
  48. package/dist/react.js +97 -96
  49. package/dist/sidebar/component.d.ts.map +1 -1
  50. package/dist/sidebar/index.js +1 -1
  51. package/dist/sidebar/types.d.ts +7 -0
  52. package/dist/sidebar/types.d.ts.map +1 -1
  53. package/dist/styles/components/brand.css +22 -0
  54. package/dist/styles/layout.css +577 -0
  55. package/dist/styles/sidebar.css +26 -0
  56. package/package.json +11 -1
  57. package/src/components/brand/index.ts +22 -4
  58. package/src/components/brand/types.ts +2 -0
  59. package/src/icons/icons.test.ts +1 -1
  60. package/src/icons/index.ts +7 -2
  61. package/src/icons/lucide.ts +4 -0
  62. package/src/index.ts +2 -0
  63. package/src/layout/app-shell.test.ts +76 -0
  64. package/src/layout/app-shell.ts +38 -7
  65. package/src/layout/core-app-chrome.test.ts +17 -5
  66. package/src/layout/core-app-chrome.ts +8 -3
  67. package/src/layout/core-app-library-dashboard.test.ts +397 -0
  68. package/src/layout/core-app-library-dashboard.ts +519 -0
  69. package/src/layout/index.ts +18 -0
  70. package/src/react/brand/index.test.tsx +10 -0
  71. package/src/react/brand/index.tsx +25 -4
  72. package/src/react/layout/core-app-chrome.test.tsx +2 -2
  73. package/src/react/layout/core-app-library-dashboard.test.tsx +42 -0
  74. package/src/react/layout/core-app-library-dashboard.tsx +56 -0
  75. package/src/react/layout/index.ts +6 -0
  76. package/src/sidebar/component.test.ts +21 -1
  77. package/src/sidebar/component.ts +14 -8
  78. package/src/sidebar/types.ts +7 -0
  79. package/src/styles/components/brand.css +22 -0
  80. package/src/styles/layout.css +577 -0
  81. package/src/styles/sidebar.css +26 -0
  82. package/dist/component-D5sRm1fq.js.map +0 -1
@@ -0,0 +1,56 @@
1
+ import * as React from 'react'
2
+
3
+ import {
4
+ mountCoreAppLibraryDashboard,
5
+ type CoreAppLibraryDashboardData,
6
+ type CoreAppLibraryDashboardMount,
7
+ type CoreAppLibraryDashboardMountOptions,
8
+ } from '../../layout/core-app-library-dashboard'
9
+ import { cn } from '../primitives/utils'
10
+
11
+ export interface CoreAppLibraryDashboardProps {
12
+ data?: CoreAppLibraryDashboardData
13
+ className?: string
14
+ dashboardClassName?: string
15
+ }
16
+
17
+ export type { CoreAppLibraryDashboardData, CoreAppLibraryDashboardMount }
18
+
19
+ function CoreAppLibraryDashboard({
20
+ data,
21
+ className,
22
+ dashboardClassName,
23
+ }: CoreAppLibraryDashboardProps) {
24
+ const containerRef = React.useRef<HTMLDivElement>(null)
25
+ const mountRef = React.useRef<CoreAppLibraryDashboardMount | null>(null)
26
+ const latestDataRef = React.useRef<CoreAppLibraryDashboardData | undefined>(data)
27
+ latestDataRef.current = data
28
+
29
+ React.useEffect(() => {
30
+ const container = containerRef.current
31
+ if (container === null) {
32
+ return
33
+ }
34
+
35
+ const mountOptions: CoreAppLibraryDashboardMountOptions = { container }
36
+ if (latestDataRef.current !== undefined) mountOptions.data = latestDataRef.current
37
+ if (dashboardClassName !== undefined) mountOptions.className = dashboardClassName
38
+
39
+ mountRef.current = mountCoreAppLibraryDashboard(mountOptions)
40
+
41
+ return () => {
42
+ mountRef.current?.destroy()
43
+ mountRef.current = null
44
+ }
45
+ }, [dashboardClassName])
46
+
47
+ React.useEffect(() => {
48
+ mountRef.current?.update(data ?? {})
49
+ }, [data])
50
+
51
+ return <div ref={containerRef} data-slot="core-app-library-dashboard" className={cn(className)} />
52
+ }
53
+
54
+ CoreAppLibraryDashboard.displayName = 'CoreAppLibraryDashboard'
55
+
56
+ export { CoreAppLibraryDashboard }
@@ -7,6 +7,12 @@ export {
7
7
  type CoreAppSidebarPresetOptions,
8
8
  type CoreAppTopbarPresetOptions,
9
9
  } from './core-app-chrome'
10
+ export {
11
+ CoreAppLibraryDashboard,
12
+ type CoreAppLibraryDashboardData,
13
+ type CoreAppLibraryDashboardMount,
14
+ type CoreAppLibraryDashboardProps,
15
+ } from './core-app-library-dashboard'
10
16
  export { DetailHeader, type DetailHeaderProps } from './detail-header'
11
17
  export { PageHeader, type PageHeaderProps } from './page-header'
12
18
  export { SubNav, type SubNavItem, type SubNavProps } from './sub-nav'
@@ -1,7 +1,10 @@
1
1
  import { beforeEach, describe, expect, it, vi } from 'vitest'
2
+ import { readFileSync } from 'node:fs'
2
3
 
3
4
  import { SidebarComponent } from './component'
4
5
 
6
+ const sidebarCss = readFileSync('src/styles/sidebar.css', 'utf8')
7
+
5
8
  function createContainer(): HTMLElement {
6
9
  const container = document.createElement('div')
7
10
  container.style.height = '320px'
@@ -20,7 +23,7 @@ describe('SidebarComponent', () => {
20
23
  const sidebar = new SidebarComponent({
21
24
  container,
22
25
  variant: 'product',
23
- brand: { label: 'CounterMeasure' },
26
+ brand: { label: 'CounterMeasure', subtitle: 'Platform', wordmarkCase: 'title' },
24
27
  collapsible: true,
25
28
  collapseButtonVisible: true,
26
29
  sections: [
@@ -44,6 +47,12 @@ describe('SidebarComponent', () => {
44
47
  expect(container.querySelector('.sidebar')?.getAttribute('data-sidebar')).toBe('product')
45
48
  expect(container.querySelector('.sidebar')?.getAttribute('data-open')).toBe('false')
46
49
  expect(container.querySelector('.sidebar__header .cmm-brand-lockup')).toBeTruthy()
50
+ expect(
51
+ container.querySelector('.sidebar__header .cmm-brand-lockup__counter')?.textContent
52
+ ).toBe('Counter')
53
+ expect(
54
+ container.querySelector('.sidebar__header .cmm-brand-lockup__subtitle')?.textContent
55
+ ).toBe('Platform')
47
56
  expect(container.querySelector('.sidebar__body .sidebar__section')).toBeTruthy()
48
57
  expect(container.querySelector('.sidebar__badge--amber')?.textContent).toBe('3')
49
58
  expect(container.querySelector('.sidebar__item')?.getAttribute('data-badge-visible')).toBe(
@@ -97,6 +106,17 @@ describe('SidebarComponent', () => {
97
106
  container.remove()
98
107
  })
99
108
 
109
+ it('keeps collapsed brand lockups mark-only while restoring full copy on mobile', () => {
110
+ expect(sidebarCss).toContain('.sidebar--collapsed .cmm-brand-lockup__copy')
111
+ expect(sidebarCss).toContain('.sidebar--collapsed .cmm-brand-lockup__wordmark')
112
+ expect(sidebarCss).toMatch(
113
+ /\.sidebar--collapsed \.cmm-brand-lockup__copy,\s*\.sidebar--collapsed \.cmm-brand-lockup__wordmark\s*{\s*display: none;/s
114
+ )
115
+ expect(sidebarCss).toMatch(
116
+ /\.sidebar--collapsed \.cmm-brand-lockup__copy,\s*\.sidebar--collapsed \.cmm-brand-lockup__wordmark\s*{\s*display: inline-flex;/s
117
+ )
118
+ })
119
+
100
120
  it('reflects controlled mobile open state without changing desktop collapse state', () => {
101
121
  const container = createContainer()
102
122
  const sidebar = new SidebarComponent({
@@ -389,14 +389,20 @@ export class SidebarComponent {
389
389
  brandEl.addEventListener('click', brand.onClick)
390
390
  }
391
391
 
392
- brandEl.appendChild(
393
- createBrandLockupElement({
394
- label: brand.label ?? 'CounterMeasure',
395
- markSize: brand.markSize ?? 22,
396
- showWordmark: brand.showWordmark ?? true,
397
- decorative: brand.decorative ?? false,
398
- })
399
- )
392
+ const lockupConfig = {
393
+ label: brand.label ?? 'CounterMeasure',
394
+ markSize: brand.markSize ?? 22,
395
+ showWordmark: brand.showWordmark ?? true,
396
+ decorative: brand.decorative ?? false,
397
+ }
398
+ if (brand.subtitle !== undefined) {
399
+ Object.assign(lockupConfig, { subtitle: brand.subtitle })
400
+ }
401
+ if (brand.wordmarkCase !== undefined) {
402
+ Object.assign(lockupConfig, { wordmarkCase: brand.wordmarkCase })
403
+ }
404
+
405
+ brandEl.appendChild(createBrandLockupElement(lockupConfig))
400
406
 
401
407
  row.appendChild(brandEl)
402
408
  header.appendChild(row)
@@ -145,6 +145,13 @@ export interface SidebarFooterAction {
145
145
  export interface SidebarBrandConfig {
146
146
  /** Accessible brand label. */
147
147
  label?: string
148
+ /** Optional short subtitle rendered beneath the wordmark. */
149
+ subtitle?: string
150
+ /**
151
+ * Wordmark text treatment.
152
+ * @default 'uppercase'
153
+ */
154
+ wordmarkCase?: 'uppercase' | 'title'
148
155
  /** Brand mark size. */
149
156
  markSize?: BrandSize | number
150
157
  /**
@@ -104,6 +104,13 @@
104
104
  vertical-align: middle;
105
105
  }
106
106
 
107
+ .cmm-brand-lockup__copy {
108
+ display: inline-flex;
109
+ min-width: 0;
110
+ flex-direction: column;
111
+ gap: 0.1875rem;
112
+ }
113
+
107
114
  .cmm-brand-lockup__wordmark {
108
115
  display: inline-flex;
109
116
  align-items: baseline;
@@ -117,6 +124,11 @@
117
124
  letter-spacing: 0.08em;
118
125
  }
119
126
 
127
+ .cmm-brand-lockup--title .cmm-brand-lockup__wordmark {
128
+ font-size: 0.875rem;
129
+ letter-spacing: 0;
130
+ }
131
+
120
132
  .cmm-brand-lockup__counter {
121
133
  color: currentColor;
122
134
  }
@@ -125,6 +137,16 @@
125
137
  color: var(--color-orange-500, hsl(var(--primary)));
126
138
  }
127
139
 
140
+ .cmm-brand-lockup__subtitle {
141
+ overflow: hidden;
142
+ color: var(--color-text-dim, hsl(var(--muted-foreground)));
143
+ font: var(--type-caption, 600 0.625rem/1 var(--font-sans, sans-serif));
144
+ letter-spacing: 0.16em;
145
+ text-overflow: ellipsis;
146
+ text-transform: uppercase;
147
+ white-space: nowrap;
148
+ }
149
+
128
150
  .cmm-connector-logo {
129
151
  --cmm-connector-logo-bg: #475569;
130
152