@teambit/api-reference 1.0.927 → 1.0.929

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.
@@ -0,0 +1,250 @@
1
+ .apiCompareContainer {
2
+ display: flex;
3
+ flex-direction: column;
4
+ height: 100%;
5
+ overflow: auto;
6
+ padding: 24px;
7
+ gap: 16px;
8
+ }
9
+
10
+ .summary {
11
+ display: flex;
12
+ gap: 12px;
13
+ align-items: center;
14
+ padding: 12px 16px;
15
+ background: var(--bit-bg-dent, #f6f6f6);
16
+ border-radius: 8px;
17
+ font-size: 13px;
18
+ }
19
+
20
+ .summaryDivider {
21
+ opacity: 0.3;
22
+ color: var(--bit-text-color-light, #6c707c);
23
+ }
24
+
25
+ .summaryBadge {
26
+ display: inline-flex;
27
+ align-items: center;
28
+ gap: 4px;
29
+ padding: 3px 10px;
30
+ border-radius: 12px;
31
+ font-weight: 500;
32
+ font-size: 12px;
33
+ white-space: nowrap;
34
+ }
35
+
36
+ .addedBadge {
37
+ composes: summaryBadge;
38
+ background: color-mix(in srgb, var(--bit-color-added, #2ea043) 15%, transparent);
39
+ color: var(--bit-color-added, #2ea043);
40
+ }
41
+
42
+ .removedBadge {
43
+ composes: summaryBadge;
44
+ background: color-mix(in srgb, var(--bit-color-removed, #f85149) 15%, transparent);
45
+ color: var(--bit-color-removed, #f85149);
46
+ }
47
+
48
+ .modifiedBadge {
49
+ composes: summaryBadge;
50
+ background: color-mix(in srgb, var(--bit-color-modified, #d29922) 15%, transparent);
51
+ color: var(--bit-color-modified, #d29922);
52
+ }
53
+
54
+ .changeList {
55
+ display: flex;
56
+ flex-direction: column;
57
+ gap: 8px;
58
+ }
59
+
60
+ .sectionTitle {
61
+ margin: 16px 0 8px;
62
+ font-size: 12px;
63
+ font-weight: 600;
64
+ text-transform: uppercase;
65
+ letter-spacing: 0.5px;
66
+ color: var(--bit-text-color-light, #6c707c);
67
+ }
68
+
69
+ .diffEntry {
70
+ border: 1px solid var(--bit-border-color-lightest, #ededed);
71
+ border-radius: 8px;
72
+ overflow: hidden;
73
+ }
74
+
75
+ .diffEntryHeader {
76
+ display: flex;
77
+ align-items: center;
78
+ gap: 10px;
79
+ padding: 10px 16px;
80
+ cursor: pointer;
81
+ user-select: none;
82
+
83
+ &:hover {
84
+ background: var(--bit-bg-dent, #f6f6f6);
85
+ }
86
+ }
87
+
88
+ .statusBadge {
89
+ display: inline-flex;
90
+ align-items: center;
91
+ padding: 2px 8px;
92
+ border-radius: 4px;
93
+ font-weight: 600;
94
+ font-size: 11px;
95
+ text-transform: uppercase;
96
+ letter-spacing: 0.3px;
97
+ flex-shrink: 0;
98
+ white-space: nowrap;
99
+ }
100
+
101
+ .statusAdded {
102
+ composes: statusBadge;
103
+ background: color-mix(in srgb, var(--bit-color-added, #2ea043) 15%, transparent);
104
+ color: var(--bit-color-added, #2ea043);
105
+ }
106
+
107
+ .statusRemoved {
108
+ composes: statusBadge;
109
+ background: color-mix(in srgb, var(--bit-color-removed, #f85149) 15%, transparent);
110
+ color: var(--bit-color-removed, #f85149);
111
+ }
112
+
113
+ .statusModified {
114
+ composes: statusBadge;
115
+ background: color-mix(in srgb, var(--bit-color-modified, #d29922) 15%, transparent);
116
+ color: var(--bit-color-modified, #d29922);
117
+ }
118
+
119
+ .exportName {
120
+ font-weight: 600;
121
+ font-size: 14px;
122
+ font-family: var(--bit-font-mono, monospace);
123
+ color: var(--bit-text-color-heavy, #2b2b2b);
124
+ }
125
+
126
+ .schemaType {
127
+ color: var(--bit-text-color-light, #6c707c);
128
+ font-size: 12px;
129
+ }
130
+
131
+ .impactBadge {
132
+ composes: summaryBadge;
133
+ margin-left: auto;
134
+ font-size: 11px;
135
+ }
136
+
137
+ .expandIcon {
138
+ color: var(--bit-text-color-light, #6c707c);
139
+ font-size: 10px;
140
+ transition: transform 0.2s;
141
+ flex-shrink: 0;
142
+ }
143
+
144
+ .expandIconOpen {
145
+ composes: expandIcon;
146
+ transform: rotate(90deg);
147
+ }
148
+
149
+ .diffEntryBody {
150
+ padding: 0 16px 16px;
151
+ border-top: 1px solid var(--bit-border-color-lightest, #ededed);
152
+ }
153
+
154
+ .detailsList {
155
+ list-style: none;
156
+ padding: 12px 0 0;
157
+ margin: 0;
158
+ display: flex;
159
+ flex-direction: column;
160
+ gap: 8px;
161
+ }
162
+
163
+ .detailItem {
164
+ display: flex;
165
+ align-items: flex-start;
166
+ gap: 8px;
167
+ font-size: 13px;
168
+ line-height: 1.5;
169
+ color: var(--bit-text-color-heavy, #2b2b2b);
170
+ }
171
+
172
+ .detailDot {
173
+ width: 7px;
174
+ height: 7px;
175
+ min-width: 7px;
176
+ border-radius: 50%;
177
+ margin-top: 6px;
178
+ flex-shrink: 0;
179
+ }
180
+
181
+ .dotBreaking {
182
+ composes: detailDot;
183
+ background: var(--bit-color-removed, #f85149);
184
+ }
185
+
186
+ .dotNonBreaking {
187
+ composes: detailDot;
188
+ background: var(--bit-color-added, #2ea043);
189
+ }
190
+
191
+ .dotPatch {
192
+ composes: detailDot;
193
+ background: var(--bit-color-modified, #d29922);
194
+ }
195
+
196
+ .detailDescription {
197
+ color: var(--bit-text-color-heavy, #2b2b2b);
198
+ }
199
+
200
+ .signatureBlock {
201
+ margin-top: 12px;
202
+ padding: 12px;
203
+ background: var(--bit-bg-dent, #f6f6f6);
204
+ border-radius: 6px;
205
+ font-family: var(--bit-font-mono, monospace);
206
+ font-size: 12px;
207
+ white-space: pre-wrap;
208
+ word-break: break-word;
209
+ line-height: 1.6;
210
+ color: var(--bit-text-color-heavy, #2b2b2b);
211
+ }
212
+
213
+ .signatureLabel {
214
+ font-weight: 600;
215
+ color: var(--bit-text-color-light, #6c707c);
216
+ margin-top: 12px;
217
+ margin-bottom: 4px;
218
+ font-size: 11px;
219
+ text-transform: uppercase;
220
+ letter-spacing: 0.5px;
221
+ }
222
+
223
+ .signatureRemoved {
224
+ composes: signatureBlock;
225
+ text-decoration: line-through;
226
+ opacity: 0.6;
227
+ }
228
+
229
+ .emptyState {
230
+ display: flex;
231
+ align-items: center;
232
+ justify-content: center;
233
+ height: 100%;
234
+ color: var(--bit-text-color-light, #6c707c);
235
+ font-size: 14px;
236
+ }
237
+
238
+ .toggleSignatures {
239
+ background: none;
240
+ border: none;
241
+ color: var(--bit-accent-color, #0366d6);
242
+ font-size: 12px;
243
+ cursor: pointer;
244
+ padding: 4px 0;
245
+ margin-top: 12px;
246
+
247
+ &:hover {
248
+ text-decoration: underline;
249
+ }
250
+ }
@@ -0,0 +1,26 @@
1
+ import React from 'react';
2
+ import type { Section } from '@teambit/component';
3
+ import type { TabItem } from '@teambit/component.ui.component-compare.models.component-compare-props';
4
+ import type { ChangeType } from '@teambit/component.ui.component-compare.models.component-compare-change-type';
5
+ import { APICompare } from './api-compare';
6
+
7
+ // Use string cast since the external ChangeType enum doesn't have API yet
8
+ const ChangeTypeAPI = 'API' as unknown as ChangeType;
9
+
10
+ export class APICompareSection implements TabItem, Section {
11
+ navigationLink = {
12
+ href: 'api',
13
+ children: 'API',
14
+ };
15
+
16
+ props = this.navigationLink;
17
+
18
+ route = {
19
+ path: 'api/*',
20
+ element: <APICompare />,
21
+ };
22
+
23
+ order = 15;
24
+ changeType = ChangeTypeAPI;
25
+ id = 'api';
26
+ }
@@ -0,0 +1,202 @@
1
+ import React from 'react';
2
+ import { useComponentCompare } from '@teambit/component.ui.component-compare.context';
3
+ import type { APIDiffResult, APIDiffChange, APIDiffDetail } from './api-compare.types';
4
+ import styles from './api-compare.module.scss';
5
+
6
+ export type { APIDiffResult, APIDiffChange, APIDiffDetail };
7
+
8
+ function impactClass(impact: string): string {
9
+ switch (impact) {
10
+ case 'BREAKING':
11
+ return styles.removedBadge;
12
+ case 'NON_BREAKING':
13
+ return styles.addedBadge;
14
+ case 'PATCH':
15
+ return styles.modifiedBadge;
16
+ default:
17
+ return styles.summaryBadge;
18
+ }
19
+ }
20
+
21
+ function impactLabel(impact: string): string {
22
+ switch (impact) {
23
+ case 'BREAKING':
24
+ return 'Breaking';
25
+ case 'NON_BREAKING':
26
+ return 'Non-breaking';
27
+ case 'PATCH':
28
+ return 'Patch';
29
+ default:
30
+ return impact;
31
+ }
32
+ }
33
+
34
+ function getStatusClass(status: string): string {
35
+ switch (status) {
36
+ case 'ADDED':
37
+ return styles.statusAdded;
38
+ case 'REMOVED':
39
+ return styles.statusRemoved;
40
+ case 'MODIFIED':
41
+ return styles.statusModified;
42
+ default:
43
+ return styles.statusBadge;
44
+ }
45
+ }
46
+
47
+ function getStatusLabel(status: string): string {
48
+ switch (status) {
49
+ case 'ADDED':
50
+ return '+ Added';
51
+ case 'REMOVED':
52
+ return '- Removed';
53
+ case 'MODIFIED':
54
+ return '~ Modified';
55
+ default:
56
+ return status;
57
+ }
58
+ }
59
+
60
+ function dotClass(impact: string): string {
61
+ switch (impact) {
62
+ case 'BREAKING':
63
+ return styles.dotBreaking;
64
+ case 'NON_BREAKING':
65
+ return styles.dotNonBreaking;
66
+ default:
67
+ return styles.dotPatch;
68
+ }
69
+ }
70
+
71
+ function DetailItem({ detail }: { detail: APIDiffDetail }) {
72
+ return (
73
+ <li className={styles.detailItem}>
74
+ <span className={dotClass(detail.impact)} />
75
+ <span className={styles.detailDescription}>{detail.description}</span>
76
+ </li>
77
+ );
78
+ }
79
+
80
+ function APIDiffEntry({ change }: { change: APIDiffChange }) {
81
+ const [expanded, setExpanded] = React.useState(change.status === 'MODIFIED');
82
+ const [showSignatures, setShowSignatures] = React.useState(false);
83
+
84
+ const hasBody =
85
+ change.status === 'MODIFIED'
86
+ ? (change.changes && change.changes.length > 0) || change.baseSignature || change.compareSignature
87
+ : change.baseSignature || change.compareSignature;
88
+
89
+ return (
90
+ <div className={styles.diffEntry}>
91
+ <div
92
+ className={styles.diffEntryHeader}
93
+ onClick={() => hasBody && setExpanded(!expanded)}
94
+ role="button"
95
+ tabIndex={0}
96
+ aria-expanded={hasBody ? expanded : undefined}
97
+ onKeyDown={(e) => {
98
+ if (e.key === 'Enter' || e.key === ' ') {
99
+ e.preventDefault();
100
+ hasBody && setExpanded(!expanded);
101
+ }
102
+ }}
103
+ >
104
+ <span className={getStatusClass(change.status)}>{getStatusLabel(change.status)}</span>
105
+ <span className={styles.exportName}>{change.exportName}</span>
106
+ <span className={styles.schemaType}>{change.schemaType}</span>
107
+ <span className={`${impactClass(change.impact)} ${styles.impactBadge}`}>{impactLabel(change.impact)}</span>
108
+ {hasBody && <span className={expanded ? styles.expandIconOpen : styles.expandIcon}>▶</span>}
109
+ </div>
110
+ {expanded && hasBody && (
111
+ <div className={styles.diffEntryBody}>
112
+ {change.status === 'MODIFIED' && change.changes && change.changes.length > 0 && (
113
+ <ul className={styles.detailsList}>
114
+ {change.changes.map((detail, i) => (
115
+ <DetailItem key={`${detail.changeKind}-${i}`} detail={detail} />
116
+ ))}
117
+ </ul>
118
+ )}
119
+ {change.status === 'MODIFIED' && (change.baseSignature || change.compareSignature) && (
120
+ <>
121
+ <button className={styles.toggleSignatures} onClick={() => setShowSignatures(!showSignatures)}>
122
+ {showSignatures ? '▼ Hide signatures' : '▶ Show signatures'}
123
+ </button>
124
+ {showSignatures && (
125
+ <>
126
+ {change.baseSignature && (
127
+ <div>
128
+ <div className={styles.signatureLabel}>Base</div>
129
+ <div className={styles.signatureRemoved}>{change.baseSignature}</div>
130
+ </div>
131
+ )}
132
+ {change.compareSignature && (
133
+ <div>
134
+ <div className={styles.signatureLabel}>Compare</div>
135
+ <div className={styles.signatureBlock}>{change.compareSignature}</div>
136
+ </div>
137
+ )}
138
+ </>
139
+ )}
140
+ </>
141
+ )}
142
+ {change.status === 'ADDED' && change.compareSignature && (
143
+ <div className={styles.signatureBlock}>{change.compareSignature}</div>
144
+ )}
145
+ {change.status === 'REMOVED' && change.baseSignature && (
146
+ <div className={styles.signatureRemoved}>{change.baseSignature}</div>
147
+ )}
148
+ </div>
149
+ )}
150
+ </div>
151
+ );
152
+ }
153
+
154
+ function ChangeSection({ title, changes }: { title: string; changes: APIDiffChange[] }) {
155
+ if (changes.length === 0) return null;
156
+
157
+ const sorted = [...changes].sort((a, b) => {
158
+ const order: Record<string, number> = { REMOVED: 0, MODIFIED: 1, ADDED: 2 };
159
+ return (order[a.status] ?? 3) - (order[b.status] ?? 3);
160
+ });
161
+
162
+ return (
163
+ <>
164
+ <h3 className={styles.sectionTitle}>{title}</h3>
165
+ <div className={styles.changeList}>
166
+ {sorted.map((change) => (
167
+ <APIDiffEntry key={`${change.visibility}-${change.status}-${change.exportName}`} change={change} />
168
+ ))}
169
+ </div>
170
+ </>
171
+ );
172
+ }
173
+
174
+ export function APICompare() {
175
+ const compareContext = useComponentCompare();
176
+ const apiDiffResult: APIDiffResult | null | undefined = (compareContext as any)?.apiDiffResult;
177
+
178
+ if (apiDiffResult === undefined) {
179
+ return <div className={styles.emptyState}>Loading API diff...</div>;
180
+ }
181
+
182
+ if (!apiDiffResult || !apiDiffResult.hasChanges) {
183
+ return <div className={styles.emptyState}>No API changes between these versions</div>;
184
+ }
185
+
186
+ const { publicChanges, internalChanges, added, removed, modified, breaking, impact } = apiDiffResult;
187
+
188
+ return (
189
+ <div className={styles.apiCompareContainer}>
190
+ <div className={styles.summary}>
191
+ <span className={impactClass(impact)}>{impactLabel(impact)}</span>
192
+ <span className={styles.summaryDivider}>|</span>
193
+ {added > 0 && <span className={styles.addedBadge}>+{added} added</span>}
194
+ {removed > 0 && <span className={styles.removedBadge}>-{removed} removed</span>}
195
+ {modified > 0 && <span className={styles.modifiedBadge}>~{modified} modified</span>}
196
+ {breaking > 0 && <span className={styles.removedBadge}>{breaking} breaking</span>}
197
+ </div>
198
+ <ChangeSection title="Public API" changes={publicChanges} />
199
+ <ChangeSection title="Internal (non-exported)" changes={internalChanges} />
200
+ </div>
201
+ );
202
+ }
@@ -17,8 +17,11 @@ import { CodeAspect } from '@teambit/code';
17
17
  import { TaggedExports } from '@teambit/tagged-exports';
18
18
  import type { WorkspaceUI } from '@teambit/workspace';
19
19
  import { WorkspaceAspect } from '@teambit/workspace';
20
+ import type { ComponentCompareUI } from '@teambit/component-compare';
21
+ import { ComponentCompareAspect } from '@teambit/component-compare';
20
22
 
21
23
  import { APIReferenceAspect } from './api-reference.aspect';
24
+ import { APICompareSection } from './api-compare.section';
22
25
 
23
26
  export type APINodeRendererSlot = SlotRegistry<APINodeRenderer[]>;
24
27
  export class APIReferenceUI {
@@ -29,7 +32,7 @@ export class APIReferenceUI {
29
32
  private workspace: WorkspaceUI
30
33
  ) {}
31
34
 
32
- static dependencies = [ComponentAspect, CodeAspect, WorkspaceAspect];
35
+ static dependencies = [ComponentAspect, CodeAspect, WorkspaceAspect, ComponentCompareAspect];
33
36
  static runtime = UIRuntime;
34
37
  static slots = [Slot.withType<APINodeRenderer[]>()];
35
38
 
@@ -69,7 +72,7 @@ export class APIReferenceUI {
69
72
  apiNodeRenderers: APINodeRenderer[] = defaultNodeRenderers;
70
73
 
71
74
  static async provider(
72
- [componentUI, codeUI, workspaceUI]: [ComponentUI, CodeUI, WorkspaceUI],
75
+ [componentUI, codeUI, workspaceUI, componentCompareUI]: [ComponentUI, CodeUI, WorkspaceUI, ComponentCompareUI],
73
76
  _,
74
77
  [apiNodeRendererSlot]: [APINodeRendererSlot],
75
78
  harmony: Harmony
@@ -84,6 +87,11 @@ export class APIReferenceUI {
84
87
  // register all default schema classes
85
88
  apiReferenceUI.registerSchemaClasses(() => Object.values(Schemas));
86
89
 
90
+ // register API compare tab in component compare
91
+ const apiCompareSection = new APICompareSection();
92
+ componentCompareUI.registerNavigation(apiCompareSection);
93
+ componentCompareUI.registerRoutes([apiCompareSection.route]);
94
+
87
95
  return apiReferenceUI;
88
96
  }
89
97
  }
@@ -0,0 +1,3 @@
1
+ import type { APIDiffResult, APIDiffChange, APIDiffDetail } from './api-compare.types';
2
+ export type { APIDiffResult, APIDiffChange, APIDiffDetail };
3
+ export declare function APICompare(): import("react/jsx-runtime").JSX.Element;