@teambit/api-reference 0.0.0-e156c538a2f5aeb096706400e76fd31ba0f0a77c → 0.0.0-e1b3278e4caed4ded05c8bff62cf83bfe598d425

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
+ }
@@ -1,35 +1,27 @@
1
1
  import React from 'react';
2
2
  import { flatten } from 'lodash';
3
- import { ComponentAspect, ComponentUI } from '@teambit/component';
3
+ import type { ComponentUI } from '@teambit/component';
4
+ import { ComponentAspect } from '@teambit/component';
4
5
  import { UIRuntime } from '@teambit/ui';
5
6
  import { APIRefPage } from '@teambit/api-reference.sections.api-reference-page';
6
7
  import { APIRefSection } from '@teambit/api-reference.sections.api-reference-section';
7
- import { Harmony, Slot, SlotRegistry } from '@teambit/harmony';
8
- import { APINodeRenderer } from '@teambit/api-reference.models.api-node-renderer';
9
- import { classRenderer } from '@teambit/api-reference.renderers.class';
10
- import { interfaceRenderer } from '@teambit/api-reference.renderers.interface';
11
- import { typeRenderer } from '@teambit/api-reference.renderers.type';
12
- import { functionRenderer } from '@teambit/api-reference.renderers.function';
13
- import { enumRenderer } from '@teambit/api-reference.renderers.enum';
14
- import { variableRenderer } from '@teambit/api-reference.renderers.variable';
15
- import { unresolvedRenderer } from '@teambit/api-reference.renderers.unresolved';
16
- import { typeRefRenderer } from '@teambit/api-reference.renderers.type-ref';
17
- import { typeUnionRenderer } from '@teambit/api-reference.renderers.type-union';
18
- import { typeIntersectionRenderer } from '@teambit/api-reference.renderers.type-intersection';
19
- import { typeLiteralRenderer } from '@teambit/api-reference.renderers.type-literal';
20
- import { parameterRenderer } from '@teambit/api-reference.renderers.parameter';
21
- import { inferenceTypeRenderer } from '@teambit/api-reference.renderers.inference-type';
22
- import { typeArrayRenderer } from '@teambit/api-reference.renderers.type-array';
23
- import { tupleTypeRenderer } from '@teambit/api-reference.renderers.tuple-type';
24
- import { thisRenderer } from '@teambit/api-reference.renderers.this';
8
+ import type { Harmony, SlotRegistry } from '@teambit/harmony';
9
+ import { Slot } from '@teambit/harmony';
10
+ import type { APINodeRenderer } from '@teambit/api-reference.models.api-node-renderer';
11
+ import { defaultNodeRenderers } from '@teambit/api-reference.renderers.default-node-renderers';
25
12
  import { APIRefRenderersProvider } from '@teambit/api-reference.hooks.use-api-renderers';
26
- import { decoratorRenderer } from '@teambit/api-reference.renderers.decorator';
27
- import { SchemaNodeConstructor, SchemaRegistry, Schemas } from '@teambit/semantics.entities.semantic-schema';
28
- import { CodeAspect, CodeUI } from '@teambit/code';
13
+ import type { SchemaNodeConstructor } from '@teambit/semantics.entities.semantic-schema';
14
+ import { SchemaRegistry, Schemas } from '@teambit/semantics.entities.semantic-schema';
15
+ import type { CodeUI } from '@teambit/code';
16
+ import { CodeAspect } from '@teambit/code';
29
17
  import { TaggedExports } from '@teambit/tagged-exports';
30
- import { WorkspaceAspect, WorkspaceUI } from '@teambit/workspace';
18
+ import type { WorkspaceUI } from '@teambit/workspace';
19
+ import { WorkspaceAspect } from '@teambit/workspace';
20
+ import type { ComponentCompareUI } from '@teambit/component-compare';
21
+ import { ComponentCompareAspect } from '@teambit/component-compare';
31
22
 
32
23
  import { APIReferenceAspect } from './api-reference.aspect';
24
+ import { APICompareSection } from './api-compare.section';
33
25
 
34
26
  export type APINodeRendererSlot = SlotRegistry<APINodeRenderer[]>;
35
27
  export class APIReferenceUI {
@@ -38,9 +30,9 @@ export class APIReferenceUI {
38
30
  private apiNodeRendererSlot: APINodeRendererSlot,
39
31
  private code: CodeUI,
40
32
  private workspace: WorkspaceUI
41
- ) { }
33
+ ) {}
42
34
 
43
- static dependencies = [ComponentAspect, CodeAspect, WorkspaceAspect];
35
+ static dependencies = [ComponentAspect, CodeAspect, WorkspaceAspect, ComponentCompareAspect];
44
36
  static runtime = UIRuntime;
45
37
  static slots = [Slot.withType<APINodeRenderer[]>()];
46
38
 
@@ -61,7 +53,7 @@ export class APIReferenceUI {
61
53
  );
62
54
  };
63
55
 
64
- /**
56
+ /**
65
57
  * @deprecated use registerSchemaClasses instead
66
58
  * registerSchemaClasses is better for performance as it lazy-loads the schemas.
67
59
  */
@@ -77,28 +69,10 @@ export class APIReferenceUI {
77
69
  this.apiNodeRendererSlot.register(apiNodeRenderers);
78
70
  }
79
71
 
80
- apiNodeRenderers: APINodeRenderer[] = [
81
- classRenderer,
82
- typeRenderer,
83
- interfaceRenderer,
84
- variableRenderer,
85
- functionRenderer,
86
- enumRenderer,
87
- unresolvedRenderer,
88
- typeRefRenderer,
89
- typeIntersectionRenderer,
90
- typeUnionRenderer,
91
- parameterRenderer,
92
- typeLiteralRenderer,
93
- inferenceTypeRenderer,
94
- typeArrayRenderer,
95
- thisRenderer,
96
- decoratorRenderer,
97
- tupleTypeRenderer
98
- ];
72
+ apiNodeRenderers: APINodeRenderer[] = defaultNodeRenderers;
99
73
 
100
74
  static async provider(
101
- [componentUI, codeUI, workspaceUI]: [ComponentUI, CodeUI, WorkspaceUI],
75
+ [componentUI, codeUI, workspaceUI, componentCompareUI]: [ComponentUI, CodeUI, WorkspaceUI, ComponentCompareUI],
102
76
  _,
103
77
  [apiNodeRendererSlot]: [APINodeRendererSlot],
104
78
  harmony: Harmony
@@ -113,6 +87,11 @@ export class APIReferenceUI {
113
87
  // register all default schema classes
114
88
  apiReferenceUI.registerSchemaClasses(() => Object.values(Schemas));
115
89
 
90
+ // register API compare tab in component compare
91
+ const apiCompareSection = new APICompareSection();
92
+ componentCompareUI.registerNavigation(apiCompareSection);
93
+ componentCompareUI.registerRoutes([apiCompareSection.route]);
94
+
116
95
  return apiReferenceUI;
117
96
  }
118
97
  }
@@ -0,0 +1,4 @@
1
+ import React from 'react';
2
+ import type { APIDiffResult, APIDiffChange, APIDiffDetail } from './api-compare.types';
3
+ export type { APIDiffResult, APIDiffChange, APIDiffDetail };
4
+ export declare function APICompare(): React.JSX.Element;