@teambit/component.ui.version-block 0.0.939 → 0.0.941

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.
@@ -6,9 +6,9 @@ export type VersionBlockProps = {
6
6
  isLatest: boolean;
7
7
  snap: LegacyComponentLog;
8
8
  isCurrent: boolean;
9
+ collapsed?: boolean;
10
+ onToggleCollapse?: () => void;
11
+ /** When true, all entries get card-style borders (used in "expand all" mode) */
12
+ allExpanded?: boolean;
9
13
  } & HTMLAttributes<HTMLDivElement>;
10
- /**
11
- * change log section
12
- * @name VersionBlock
13
- */
14
- export declare function VersionBlock({ isLatest, className, snap, componentId, isCurrent, ...rest }: VersionBlockProps): React.JSX.Element;
14
+ export declare function VersionBlock({ isLatest, className, snap, componentId, isCurrent, collapsed, onToggleCollapse, allExpanded, ...rest }: VersionBlockProps): React.JSX.Element;
@@ -48,65 +48,122 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
48
48
  };
49
49
  Object.defineProperty(exports, "__esModule", { value: true });
50
50
  exports.VersionBlock = VersionBlock;
51
- const documenter_ui_heading_1 = require("@teambit/documenter.ui.heading");
52
- const design_ui_contributors_1 = require("@teambit/design.ui.contributors");
53
- const base_react_navigation_link_1 = require("@teambit/base-react.navigation.link");
51
+ /* eslint-disable complexity */
52
+ const design_ui_avatar_1 = require("@teambit/design.ui.avatar");
53
+ const design_ui_time_ago_1 = require("@teambit/design.ui.time-ago");
54
54
  const component_ui_version_label_1 = require("@teambit/component.ui.version-label");
55
+ const base_react_navigation_link_1 = require("@teambit/base-react.navigation.link");
55
56
  const classnames_1 = __importDefault(require("classnames"));
56
57
  const react_1 = __importStar(require("react"));
57
58
  const design_ui_tooltip_1 = require("@teambit/design.ui.tooltip");
58
59
  const lanes_hooks_use_lanes_1 = require("@teambit/lanes.hooks.use-lanes");
59
60
  const lanes_ui_models_lanes_model_1 = require("@teambit/lanes.ui.models.lanes-model");
60
61
  const version_block_module_scss_1 = __importDefault(require("./version-block.module.scss"));
62
+ function ChevronIcon({ collapsed, className }) {
63
+ return (react_1.default.createElement("svg", { className: (0, classnames_1.default)(version_block_module_scss_1.default.chevronIcon, collapsed && version_block_module_scss_1.default.chevronCollapsed, className), width: "12", height: "8", viewBox: "0 0 12 8", fill: "none", xmlns: "http://www.w3.org/2000/svg" },
64
+ react_1.default.createElement("path", { d: "M10.9999 1.17C10.8126 0.983753 10.5591 0.879211 10.2949 0.879211C10.0308 0.879211 9.77731 0.983753 9.58995 1.17L5.99995 4.71L2.45995 1.17C2.27259 0.983753 2.01913 0.879211 1.75495 0.879211C1.49076 0.879211 1.23731 0.983753 1.04995 1.17C0.95622 1.26297 0.881826 1.37357 0.831057 1.49543C0.780288 1.61729 0.75415 1.74799 0.75415 1.88C0.75415 2.01202 0.780288 2.14272 0.831057 2.26458C0.881826 2.38644 0.95622 2.49704 1.04995 2.59L5.28995 6.83C5.38291 6.92373 5.49351 6.99813 5.61537 7.04889C5.73723 7.09966 5.86794 7.1258 5.99995 7.1258C6.13196 7.1258 6.26267 7.09966 6.38453 7.04889C6.50638 6.99813 6.61699 6.92373 6.70995 6.83L10.9999 2.59C11.0937 2.49704 11.1681 2.38644 11.2188 2.26458C11.2696 2.14272 11.2957 2.01202 11.2957 1.88C11.2957 1.74799 11.2696 1.61729 11.2188 1.49543C11.1681 1.37357 11.0937 1.26297 10.9999 1.17Z", fill: "var(--bit-text-color-light, #6c707c)" })));
65
+ }
61
66
  // @todo - this will be fixed as part of the @teambit/base-react.navigation.link upgrade to latest
62
67
  const Link = base_react_navigation_link_1.Link;
63
- /**
64
- * change log section
65
- * @name VersionBlock
66
- */
67
68
  function VersionBlock(_a) {
68
- var { isLatest, className, snap, componentId, isCurrent } = _a, rest = __rest(_a, ["isLatest", "className", "snap", "componentId", "isCurrent"]);
69
- const { username, email, message, tag, hash, date } = snap;
69
+ var { isLatest, className, snap, componentId, isCurrent, collapsed = false, onToggleCollapse, allExpanded = false } = _a, rest = __rest(_a, ["isLatest", "className", "snap", "componentId", "isCurrent", "collapsed", "onToggleCollapse", "allExpanded"]);
70
+ const { username, displayName, email, profileImage, message, tag, hash, date } = snap;
70
71
  const { lanesModel } = (0, lanes_hooks_use_lanes_1.useLanes)();
71
72
  const currentLaneUrl = (lanesModel === null || lanesModel === void 0 ? void 0 : lanesModel.isViewingNonDefaultLane())
72
- ? // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
73
- `${lanes_ui_models_lanes_model_1.LanesModel.getLaneUrl(lanesModel.viewedLane.id)}${lanes_ui_models_lanes_model_1.LanesModel.baseLaneComponentRoute}`
73
+ ? `${lanes_ui_models_lanes_model_1.LanesModel.getLaneUrl(lanesModel.viewedLane.id)}${lanes_ui_models_lanes_model_1.LanesModel.baseLaneComponentRoute}`
74
74
  : '';
75
75
  const version = tag || hash;
76
- const author = (0, react_1.useMemo)(() => {
77
- return {
78
- displayName: username,
79
- email,
80
- };
81
- }, [snap]);
82
- const timestamp = (0, react_1.useMemo)(() => (date ? new Date(parseInt(date)).toString() : new Date().toString()), [date]);
76
+ const isTag = Boolean(tag);
77
+ const displayVersion = tag ? `v${tag}` : hash.slice(0, 7);
78
+ const author = (0, react_1.useMemo)(() => ({
79
+ displayName: displayName || username || '',
80
+ email: email || '',
81
+ name: username || '',
82
+ profileImage: profileImage || '',
83
+ }), [displayName, username, email, profileImage]);
83
84
  const location = (0, base_react_navigation_link_1.useLocation)();
84
85
  const { pathname } = location || {};
85
- const testsUrl = currentLaneUrl
86
- ? `${currentLaneUrl}/${componentId}/~tests?version=${version}`
87
- : `${pathname === null || pathname === void 0 ? void 0 : pathname.replace('~changelog', '~tests')}?version=${version}`;
88
- const compositionsUrl = currentLaneUrl
89
- ? `${currentLaneUrl}/${componentId}/~compositions?version=${version}`
90
- : `${pathname === null || pathname === void 0 ? void 0 : pathname.replace('~changelog', '~compositions')}?version=${version}`;
86
+ // Navigate to component page at that version (not the changelog)
87
+ const componentBasePath = (pathname === null || pathname === void 0 ? void 0 : pathname.replace(/\/~changelog.*$/, '')) || '';
91
88
  const versionUrl = currentLaneUrl
92
89
  ? `${currentLaneUrl}/${componentId}?version=${version}`
93
- : `${pathname}?version=${version}`;
94
- return (react_1.default.createElement("div", { className: (0, classnames_1.default)(version_block_module_scss_1.default.versionWrapper, className) },
95
- react_1.default.createElement("div", { className: version_block_module_scss_1.default.left },
96
- react_1.default.createElement(component_ui_version_label_1.Labels, { isLatest: isLatest, isCurrent: isCurrent }),
97
- react_1.default.createElement(Link, { className: version_block_module_scss_1.default.link, href: testsUrl }, "Tests"),
98
- react_1.default.createElement(Link, { className: version_block_module_scss_1.default.link, href: compositionsUrl }, "Compositions"),
99
- react_1.default.createElement("div", { className: version_block_module_scss_1.default.placeholder })),
100
- react_1.default.createElement("div", Object.assign({ className: (0, classnames_1.default)(version_block_module_scss_1.default.right, className) }, rest),
101
- react_1.default.createElement(design_ui_tooltip_1.Tooltip, { placement: "right", content: hash },
102
- react_1.default.createElement(Link, { className: version_block_module_scss_1.default.titleLink, href: versionUrl },
103
- react_1.default.createElement(documenter_ui_heading_1.H3, { size: "xs", className: version_block_module_scss_1.default.versionTitle }, tag ? `v${tag}` : hash))),
104
- react_1.default.createElement(design_ui_contributors_1.Contributors, { contributors: [author || {}], timestamp: timestamp }),
105
- commitMessage(message))));
90
+ : `${componentBasePath}?version=${version}`;
91
+ const { firstLine, rest: restOfMessage } = useCommitMessage(message);
92
+ const [messageExpanded, setMessageExpanded] = (0, react_1.useState)(false);
93
+ const hasMore = restOfMessage.length > 0;
94
+ const authorDisplay = displayName || username || 'Unknown';
95
+ if (!isTag && collapsed) {
96
+ return (react_1.default.createElement("div", Object.assign({ className: (0, classnames_1.default)(version_block_module_scss_1.default.row, version_block_module_scss_1.default.snapRow, version_block_module_scss_1.default.collapsedRow, className), onClick: onToggleCollapse, role: "button", tabIndex: 0, onKeyDown: (e) => e.key === 'Enter' && (onToggleCollapse === null || onToggleCollapse === void 0 ? void 0 : onToggleCollapse()) }, rest),
97
+ react_1.default.createElement("div", { className: version_block_module_scss_1.default.dateCol },
98
+ react_1.default.createElement(design_ui_time_ago_1.TimeAgo, { className: version_block_module_scss_1.default.dateText, date: date ? parseInt(date) : Date.now() })),
99
+ react_1.default.createElement("div", { className: version_block_module_scss_1.default.dotCol },
100
+ react_1.default.createElement("div", { className: version_block_module_scss_1.default.snapDotOuter },
101
+ react_1.default.createElement("div", { className: version_block_module_scss_1.default.snapDotInner }))),
102
+ react_1.default.createElement("div", { className: version_block_module_scss_1.default.contentCol },
103
+ react_1.default.createElement("div", { className: version_block_module_scss_1.default.collapsedContent },
104
+ react_1.default.createElement(design_ui_tooltip_1.Tooltip, { placement: "bottom", content: hash },
105
+ react_1.default.createElement("span", { className: version_block_module_scss_1.default.snapHash }, displayVersion)),
106
+ react_1.default.createElement("span", { className: version_block_module_scss_1.default.collapsedAuthor }, authorDisplay),
107
+ react_1.default.createElement(ChevronIcon, { collapsed: true, className: version_block_module_scss_1.default.expandHint })))));
108
+ }
109
+ if (!isTag) {
110
+ return (react_1.default.createElement("div", Object.assign({ className: (0, classnames_1.default)(version_block_module_scss_1.default.row, version_block_module_scss_1.default.snapRow, className) }, rest),
111
+ react_1.default.createElement("div", { className: version_block_module_scss_1.default.dateCol },
112
+ react_1.default.createElement(design_ui_time_ago_1.TimeAgo, { className: version_block_module_scss_1.default.dateText, date: date ? parseInt(date) : Date.now() })),
113
+ react_1.default.createElement("div", { className: version_block_module_scss_1.default.dotCol },
114
+ react_1.default.createElement("div", { className: version_block_module_scss_1.default.snapDotOuter },
115
+ react_1.default.createElement("div", { className: version_block_module_scss_1.default.snapDotInner }))),
116
+ react_1.default.createElement("div", { className: version_block_module_scss_1.default.contentCol },
117
+ react_1.default.createElement("div", { className: version_block_module_scss_1.default.snapCard, onClick: onToggleCollapse, role: "button", tabIndex: 0, onKeyDown: (e) => e.key === 'Enter' && (onToggleCollapse === null || onToggleCollapse === void 0 ? void 0 : onToggleCollapse()) },
118
+ react_1.default.createElement("div", { className: version_block_module_scss_1.default.headerRow },
119
+ react_1.default.createElement(design_ui_tooltip_1.Tooltip, { placement: "bottom", content: hash },
120
+ react_1.default.createElement(Link, { className: version_block_module_scss_1.default.snapVersionLink, href: versionUrl, onClick: (e) => e.stopPropagation() }, displayVersion)),
121
+ react_1.default.createElement(ChevronIcon, { className: version_block_module_scss_1.default.collapseHint })),
122
+ react_1.default.createElement("div", { className: version_block_module_scss_1.default.metaRow },
123
+ react_1.default.createElement(design_ui_avatar_1.UserAvatar, { account: author, size: 20, fontSize: 8 }),
124
+ react_1.default.createElement("span", { className: version_block_module_scss_1.default.authorName }, authorDisplay)),
125
+ react_1.default.createElement("div", { className: version_block_module_scss_1.default.messageSection }, firstLine ? (react_1.default.createElement(react_1.default.Fragment, null,
126
+ react_1.default.createElement("div", { className: version_block_module_scss_1.default.firstLine }, firstLine),
127
+ hasMore && (react_1.default.createElement(react_1.default.Fragment, null,
128
+ messageExpanded && react_1.default.createElement("div", { className: version_block_module_scss_1.default.restOfMessage }, restOfMessage),
129
+ react_1.default.createElement("button", { className: version_block_module_scss_1.default.expandButton, onClick: (e) => {
130
+ e.stopPropagation();
131
+ setMessageExpanded(!messageExpanded);
132
+ }, type: "button" }, messageExpanded ? 'Show less' : 'Show more'))))) : (react_1.default.createElement("div", { className: version_block_module_scss_1.default.emptyMessage }, "No commit message")))))));
133
+ }
134
+ // ----- Tag entry -----
135
+ return (react_1.default.createElement("div", Object.assign({ className: (0, classnames_1.default)(version_block_module_scss_1.default.row, version_block_module_scss_1.default.tagRow, className) }, rest),
136
+ react_1.default.createElement("div", { className: version_block_module_scss_1.default.dateCol },
137
+ react_1.default.createElement(design_ui_time_ago_1.TimeAgo, { className: version_block_module_scss_1.default.dateText, date: date ? parseInt(date) : Date.now() })),
138
+ react_1.default.createElement("div", { className: version_block_module_scss_1.default.dotCol },
139
+ react_1.default.createElement("div", { className: version_block_module_scss_1.default.tagDotOuter },
140
+ react_1.default.createElement("div", { className: version_block_module_scss_1.default.tagDotRing },
141
+ react_1.default.createElement("div", { className: version_block_module_scss_1.default.tagDotInner })))),
142
+ react_1.default.createElement("div", { className: version_block_module_scss_1.default.contentCol },
143
+ react_1.default.createElement("div", { className: (0, classnames_1.default)(version_block_module_scss_1.default.tagCard, allExpanded && version_block_module_scss_1.default.tagCardBordered) },
144
+ react_1.default.createElement("div", { className: version_block_module_scss_1.default.headerRow },
145
+ react_1.default.createElement(design_ui_tooltip_1.Tooltip, { placement: "bottom", content: hash },
146
+ react_1.default.createElement(Link, { className: version_block_module_scss_1.default.versionLink, href: versionUrl }, displayVersion)),
147
+ isLatest && react_1.default.createElement(component_ui_version_label_1.VersionLabel, { status: "latest" }),
148
+ isCurrent && react_1.default.createElement(component_ui_version_label_1.VersionLabel, { status: "current" })),
149
+ react_1.default.createElement("div", { className: version_block_module_scss_1.default.metaRow },
150
+ react_1.default.createElement(design_ui_avatar_1.UserAvatar, { account: author, size: 20, fontSize: 8 }),
151
+ react_1.default.createElement("span", { className: version_block_module_scss_1.default.authorName }, authorDisplay)),
152
+ react_1.default.createElement("div", { className: version_block_module_scss_1.default.messageSection }, firstLine ? (react_1.default.createElement(react_1.default.Fragment, null,
153
+ react_1.default.createElement("div", { className: version_block_module_scss_1.default.firstLine }, firstLine),
154
+ hasMore && (react_1.default.createElement(react_1.default.Fragment, null,
155
+ messageExpanded && react_1.default.createElement("div", { className: version_block_module_scss_1.default.restOfMessage }, restOfMessage),
156
+ react_1.default.createElement("button", { className: version_block_module_scss_1.default.expandButton, onClick: () => setMessageExpanded(!messageExpanded), type: "button" }, messageExpanded ? 'Show less' : 'Show more'))))) : (react_1.default.createElement("div", { className: version_block_module_scss_1.default.emptyMessage }, "No commit message")))))));
106
157
  }
107
- function commitMessage(message) {
108
- if (!message || message === '')
109
- return react_1.default.createElement("div", { className: version_block_module_scss_1.default.emptyMessage }, "No commit message");
110
- return react_1.default.createElement("div", { className: version_block_module_scss_1.default.commitMessage }, message);
158
+ function useCommitMessage(message) {
159
+ return (0, react_1.useMemo)(() => {
160
+ if (!message)
161
+ return { firstLine: '', rest: '' };
162
+ const lines = message.split('\n');
163
+ return {
164
+ firstLine: lines[0],
165
+ rest: lines.slice(1).join('\n').trim(),
166
+ };
167
+ }, [message]);
111
168
  }
112
169
  //# sourceMappingURL=version-block.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"version-block.js","sourceRoot":"","sources":["../version-block.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"version-block.js","sourceRoot":"","sources":["../version-block.tsx"],"names":[],"mappings":"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"}
@@ -1,63 +1,327 @@
1
- .versionWrapper {
1
+ .row {
2
+ display: grid;
3
+ grid-template-columns: 100px 36px 1fr;
4
+ align-items: start;
5
+ min-height: 0;
6
+ }
7
+
8
+ .dateCol {
9
+ text-align: right;
10
+ padding-right: 12px;
11
+ }
12
+
13
+ .dateText {
14
+ font-size: 12px;
15
+ color: var(--bit-text-color-light, #6c707c);
16
+ white-space: nowrap;
17
+ }
18
+
19
+ .dotCol {
2
20
  display: flex;
21
+ justify-content: center;
22
+ align-items: center;
23
+ position: relative;
24
+ z-index: 1;
25
+ }
3
26
 
4
- .left {
5
- display: flex;
6
- flex-direction: column;
7
- align-items: flex-end;
8
- border-right: 1px solid var(--bit-border-color-lightest, #ededed);
9
- padding-right: 22px;
10
- padding-left: 10px;
11
- padding-top: 24px;
12
- text-align: right;
13
- @media screen and (max-width: 768px) {
14
- display: none;
27
+ .contentCol {
28
+ padding-left: 16px;
29
+ min-width: 0;
30
+ }
31
+
32
+ .tagDotOuter {
33
+ width: 24px;
34
+ height: 24px;
35
+ border-radius: 50%;
36
+ background-color: var(--bit-accent-bg, rgba(108, 92, 231, 0.12));
37
+ display: flex;
38
+ align-items: center;
39
+ justify-content: center;
40
+ flex-shrink: 0;
41
+ }
42
+
43
+ .tagDotRing {
44
+ width: 16px;
45
+ height: 16px;
46
+ border-radius: 50%;
47
+ border: 2px solid var(--bit-accent-color, #6c5ce7);
48
+ background: transparent;
49
+ display: flex;
50
+ align-items: center;
51
+ justify-content: center;
52
+ }
53
+
54
+ .tagDotInner {
55
+ width: 7px;
56
+ height: 7px;
57
+ border-radius: 50%;
58
+ background-color: var(--bit-accent-color, #6c5ce7);
59
+ }
60
+
61
+ .snapDotOuter {
62
+ width: 16px;
63
+ height: 16px;
64
+ border-radius: 50%;
65
+ background-color: var(--bit-bg-color, #ffffff);
66
+ border: 2px solid var(--bit-border-color-lightest, #d0d0d0);
67
+ display: flex;
68
+ align-items: center;
69
+ justify-content: center;
70
+ flex-shrink: 0;
71
+ }
72
+
73
+ .snapDotInner {
74
+ width: 6px;
75
+ height: 6px;
76
+ border-radius: 50%;
77
+ background-color: var(--bit-border-color-lightest, #d0d0d0);
78
+ }
79
+
80
+ .tagRow {
81
+ padding-top: 6px;
82
+ padding-bottom: 6px;
83
+
84
+ .dateCol {
85
+ padding-top: 14px;
86
+ }
87
+
88
+ .dotCol {
89
+ padding-top: 12px;
90
+ }
91
+
92
+ .contentCol {
93
+ padding-bottom: 4px;
94
+ }
95
+ }
96
+
97
+ .tagCard {
98
+ padding: 10px 16px;
99
+ border-radius: 8px;
100
+ border: 1px solid transparent;
101
+ transition:
102
+ background-color 0.15s ease,
103
+ border-color 0.15s ease;
104
+
105
+ &:hover {
106
+ background-color: var(--bit-bg-dent, #f6f6f6);
107
+ border-color: var(--bit-border-color-lightest, #ededed);
108
+ }
109
+ }
110
+
111
+ .tagCardBordered {
112
+ border-color: var(--bit-border-color-lightest, #ededed);
113
+ }
114
+
115
+ .snapRow {
116
+ padding-top: 2px;
117
+ padding-bottom: 2px;
118
+
119
+ .dateCol {
120
+ padding-top: 10px;
121
+ }
122
+
123
+ .dotCol {
124
+ padding-top: 10px;
125
+ }
126
+ }
127
+
128
+ .snapCard {
129
+ padding: 10px 14px;
130
+ border-radius: 8px;
131
+ background-color: transparent;
132
+ border: 1px solid var(--bit-border-color-lightest, #ededed);
133
+ cursor: pointer;
134
+ transition:
135
+ background-color 0.15s ease,
136
+ opacity 0.15s ease;
137
+ opacity: 0.7;
138
+
139
+ &:hover {
140
+ opacity: 1;
141
+ background-color: var(--bit-bg-dent, #f6f6f6);
142
+ }
143
+ }
144
+
145
+ .collapsedRow {
146
+ cursor: pointer;
147
+ align-items: center;
148
+ padding-top: 1px;
149
+ padding-bottom: 1px;
150
+
151
+ .dateCol {
152
+ padding-top: 0;
153
+ }
154
+
155
+ .dotCol {
156
+ padding-top: 0;
157
+ }
158
+
159
+ .contentCol {
160
+ padding-bottom: 0;
161
+ }
162
+
163
+ &:hover {
164
+ .collapsedContent {
165
+ background-color: var(--bit-bg-dent, #f6f6f6);
166
+ }
167
+ .expandHint {
168
+ opacity: 1;
15
169
  }
16
170
  }
17
171
  }
18
- .emptyLabel {
19
- height: 25px;
20
- margin-bottom: 21px;
172
+
173
+ .collapsedContent {
174
+ display: flex;
175
+ align-items: center;
176
+ gap: 10px;
177
+ padding: 8px 14px;
178
+ border-radius: 8px;
179
+ border: 1px solid transparent;
180
+ transition:
181
+ background-color 0.15s ease,
182
+ border-color 0.15s ease;
183
+ min-width: 0;
184
+ }
185
+
186
+ .collapsedRow:hover .collapsedContent {
187
+ border-color: var(--bit-border-color-lightest, #ededed);
21
188
  }
22
- .link {
189
+
190
+ .snapHash {
191
+ font-size: 12px;
192
+ font-family: 'SF Mono', 'Menlo', 'Monaco', 'Consolas', monospace;
193
+ color: var(--bit-text-color-light, #6c707c);
194
+ flex-shrink: 0;
195
+ }
196
+
197
+ .collapsedAuthor {
198
+ font-size: 12px;
199
+ font-weight: 600;
200
+ color: var(--bit-text-color-light, #6c707c);
201
+ white-space: nowrap;
202
+ flex-shrink: 0;
203
+ }
204
+
205
+ // Chevron icon
206
+ .chevronIcon {
207
+ flex-shrink: 0;
208
+ transition:
209
+ transform 0.2s ease,
210
+ opacity 0.15s ease;
211
+ }
212
+
213
+ .chevronCollapsed {
214
+ transform: rotate(0deg); // points down when collapsed
215
+ }
216
+
217
+ // Expand hint (collapsed snap row)
218
+ .expandHint {
219
+ opacity: 0.3;
220
+ margin-left: auto;
221
+ transition: opacity 0.15s ease;
222
+ }
223
+
224
+ // Collapse hint (expanded snap card header)
225
+ .collapseHint {
226
+ opacity: 0.4;
227
+ margin-left: auto;
228
+ transform: rotate(180deg); // points up when expanded
229
+ }
230
+
231
+ .headerRow {
232
+ display: flex;
233
+ align-items: center;
234
+ gap: 8px;
235
+ margin-bottom: 4px;
236
+ }
237
+
238
+ .metaRow {
239
+ display: flex;
240
+ align-items: center;
241
+ gap: 6px;
242
+ margin-bottom: 6px;
243
+ }
244
+
245
+ .versionLink {
23
246
  text-decoration: none;
24
- color: var(--bit-text-color-heavyed);
25
- font-size: 14px;
26
- margin-bottom: 8px;
247
+ color: var(--bit-accent-color, #6c5ce7);
248
+ font-size: 16px;
249
+ font-weight: 600;
250
+
27
251
  &:hover {
28
252
  text-decoration: underline;
29
253
  }
30
254
  }
31
- .right {
32
- padding-top: 20px;
33
- padding-left: 50px;
34
- margin-bottom: 45px;
35
- max-width: 700px;
36
- .versionTitle {
37
- color: var(--bit-accent-color, #6c5ce7);
38
- font-weight: unset;
39
- margin-bottom: 10px;
40
- }
41
- .titleLink {
42
- text-decoration: none;
43
- display: inline-block;
44
- max-width: fit-content;
45
- }
46
- @media screen and (max-width: 768px) {
47
- padding-left: 0;
255
+
256
+ .snapVersionLink {
257
+ text-decoration: none;
258
+ font-family: 'SF Mono', 'Menlo', 'Monaco', 'Consolas', monospace;
259
+ color: var(--bit-text-color-light, #6c707c);
260
+ font-size: 13px;
261
+ font-weight: 500;
262
+
263
+ &:hover {
264
+ text-decoration: underline;
48
265
  }
49
266
  }
50
267
 
51
- .commitMessage {
52
- font-size: 15px;
53
- max-width: 200px;
268
+ .authorName {
269
+ font-size: 14px;
270
+ font-weight: 600;
271
+ color: var(--bit-text-color-heavy, #2b2b2b);
54
272
  }
273
+
274
+ .firstLine {
275
+ font-size: 14px;
276
+ color: var(--bit-text-color-heavy, #2b2b2b);
277
+ line-height: 1.5;
278
+ word-break: break-word;
279
+ }
280
+
281
+ .restOfMessage {
282
+ font-size: 13px;
283
+ color: var(--bit-text-color-light, #6c707c);
284
+ line-height: 1.5;
285
+ white-space: pre-wrap;
286
+ word-break: break-word;
287
+ margin-top: 4px;
288
+ }
289
+
290
+ .expandButton {
291
+ background: none;
292
+ border: none;
293
+ padding: 0;
294
+ margin-top: 4px;
295
+ font-size: 12px;
296
+ color: var(--bit-accent-color, #6c5ce7);
297
+ cursor: pointer;
298
+
299
+ &:hover {
300
+ text-decoration: underline;
301
+ }
302
+ }
303
+
55
304
  .emptyMessage {
56
- font-size: 15px;
305
+ font-size: 14px;
57
306
  font-style: italic;
58
307
  color: var(--bit-text-color-light, #6c707c);
59
308
  }
60
309
 
61
- .placeholder {
62
- width: 57px;
310
+ @media screen and (max-width: 768px) {
311
+ .row {
312
+ grid-template-columns: 76px 28px 1fr;
313
+ }
314
+
315
+ .dateCol {
316
+ padding-right: 8px;
317
+ }
318
+
319
+ .contentCol {
320
+ padding-left: 10px;
321
+ }
322
+
323
+ .tagCard,
324
+ .snapCard {
325
+ padding: 8px 10px;
326
+ }
63
327
  }
package/package.json CHANGED
@@ -1,24 +1,23 @@
1
1
  {
2
2
  "name": "@teambit/component.ui.version-block",
3
- "version": "0.0.939",
3
+ "version": "0.0.941",
4
4
  "homepage": "https://bit.cloud/teambit/component/ui/version-block",
5
5
  "main": "dist/index.js",
6
6
  "componentId": {
7
7
  "scope": "teambit.component",
8
8
  "name": "ui/version-block",
9
- "version": "0.0.939"
9
+ "version": "0.0.941"
10
10
  },
11
11
  "dependencies": {
12
12
  "classnames": "^2.5.1",
13
13
  "core-js": "^3.0.0",
14
+ "@teambit/design.ui.avatar": "1.1.31",
14
15
  "@teambit/design.ui.tooltip": "0.0.382",
15
- "@teambit/documenter.ui.heading": "4.1.8",
16
16
  "@teambit/lanes.hooks.use-lanes": "0.0.292",
17
17
  "@teambit/lanes.ui.models.lanes-model": "0.0.232",
18
+ "@teambit/legacy-component-log": "0.0.417",
18
19
  "@teambit/component.ui.version-label": "0.0.509",
19
- "@teambit/design.ui.contributors": "0.0.514",
20
- "@teambit/design.ui.avatar": "1.1.30",
21
- "@teambit/legacy-component-log": "0.0.417"
20
+ "@teambit/design.ui.time-ago": "0.0.383"
22
21
  },
23
22
  "devDependencies": {
24
23
  "@types/classnames": "^2.3.4",
@@ -1,63 +1,327 @@
1
- .versionWrapper {
1
+ .row {
2
+ display: grid;
3
+ grid-template-columns: 100px 36px 1fr;
4
+ align-items: start;
5
+ min-height: 0;
6
+ }
7
+
8
+ .dateCol {
9
+ text-align: right;
10
+ padding-right: 12px;
11
+ }
12
+
13
+ .dateText {
14
+ font-size: 12px;
15
+ color: var(--bit-text-color-light, #6c707c);
16
+ white-space: nowrap;
17
+ }
18
+
19
+ .dotCol {
2
20
  display: flex;
21
+ justify-content: center;
22
+ align-items: center;
23
+ position: relative;
24
+ z-index: 1;
25
+ }
3
26
 
4
- .left {
5
- display: flex;
6
- flex-direction: column;
7
- align-items: flex-end;
8
- border-right: 1px solid var(--bit-border-color-lightest, #ededed);
9
- padding-right: 22px;
10
- padding-left: 10px;
11
- padding-top: 24px;
12
- text-align: right;
13
- @media screen and (max-width: 768px) {
14
- display: none;
27
+ .contentCol {
28
+ padding-left: 16px;
29
+ min-width: 0;
30
+ }
31
+
32
+ .tagDotOuter {
33
+ width: 24px;
34
+ height: 24px;
35
+ border-radius: 50%;
36
+ background-color: var(--bit-accent-bg, rgba(108, 92, 231, 0.12));
37
+ display: flex;
38
+ align-items: center;
39
+ justify-content: center;
40
+ flex-shrink: 0;
41
+ }
42
+
43
+ .tagDotRing {
44
+ width: 16px;
45
+ height: 16px;
46
+ border-radius: 50%;
47
+ border: 2px solid var(--bit-accent-color, #6c5ce7);
48
+ background: transparent;
49
+ display: flex;
50
+ align-items: center;
51
+ justify-content: center;
52
+ }
53
+
54
+ .tagDotInner {
55
+ width: 7px;
56
+ height: 7px;
57
+ border-radius: 50%;
58
+ background-color: var(--bit-accent-color, #6c5ce7);
59
+ }
60
+
61
+ .snapDotOuter {
62
+ width: 16px;
63
+ height: 16px;
64
+ border-radius: 50%;
65
+ background-color: var(--bit-bg-color, #ffffff);
66
+ border: 2px solid var(--bit-border-color-lightest, #d0d0d0);
67
+ display: flex;
68
+ align-items: center;
69
+ justify-content: center;
70
+ flex-shrink: 0;
71
+ }
72
+
73
+ .snapDotInner {
74
+ width: 6px;
75
+ height: 6px;
76
+ border-radius: 50%;
77
+ background-color: var(--bit-border-color-lightest, #d0d0d0);
78
+ }
79
+
80
+ .tagRow {
81
+ padding-top: 6px;
82
+ padding-bottom: 6px;
83
+
84
+ .dateCol {
85
+ padding-top: 14px;
86
+ }
87
+
88
+ .dotCol {
89
+ padding-top: 12px;
90
+ }
91
+
92
+ .contentCol {
93
+ padding-bottom: 4px;
94
+ }
95
+ }
96
+
97
+ .tagCard {
98
+ padding: 10px 16px;
99
+ border-radius: 8px;
100
+ border: 1px solid transparent;
101
+ transition:
102
+ background-color 0.15s ease,
103
+ border-color 0.15s ease;
104
+
105
+ &:hover {
106
+ background-color: var(--bit-bg-dent, #f6f6f6);
107
+ border-color: var(--bit-border-color-lightest, #ededed);
108
+ }
109
+ }
110
+
111
+ .tagCardBordered {
112
+ border-color: var(--bit-border-color-lightest, #ededed);
113
+ }
114
+
115
+ .snapRow {
116
+ padding-top: 2px;
117
+ padding-bottom: 2px;
118
+
119
+ .dateCol {
120
+ padding-top: 10px;
121
+ }
122
+
123
+ .dotCol {
124
+ padding-top: 10px;
125
+ }
126
+ }
127
+
128
+ .snapCard {
129
+ padding: 10px 14px;
130
+ border-radius: 8px;
131
+ background-color: transparent;
132
+ border: 1px solid var(--bit-border-color-lightest, #ededed);
133
+ cursor: pointer;
134
+ transition:
135
+ background-color 0.15s ease,
136
+ opacity 0.15s ease;
137
+ opacity: 0.7;
138
+
139
+ &:hover {
140
+ opacity: 1;
141
+ background-color: var(--bit-bg-dent, #f6f6f6);
142
+ }
143
+ }
144
+
145
+ .collapsedRow {
146
+ cursor: pointer;
147
+ align-items: center;
148
+ padding-top: 1px;
149
+ padding-bottom: 1px;
150
+
151
+ .dateCol {
152
+ padding-top: 0;
153
+ }
154
+
155
+ .dotCol {
156
+ padding-top: 0;
157
+ }
158
+
159
+ .contentCol {
160
+ padding-bottom: 0;
161
+ }
162
+
163
+ &:hover {
164
+ .collapsedContent {
165
+ background-color: var(--bit-bg-dent, #f6f6f6);
166
+ }
167
+ .expandHint {
168
+ opacity: 1;
15
169
  }
16
170
  }
17
171
  }
18
- .emptyLabel {
19
- height: 25px;
20
- margin-bottom: 21px;
172
+
173
+ .collapsedContent {
174
+ display: flex;
175
+ align-items: center;
176
+ gap: 10px;
177
+ padding: 8px 14px;
178
+ border-radius: 8px;
179
+ border: 1px solid transparent;
180
+ transition:
181
+ background-color 0.15s ease,
182
+ border-color 0.15s ease;
183
+ min-width: 0;
184
+ }
185
+
186
+ .collapsedRow:hover .collapsedContent {
187
+ border-color: var(--bit-border-color-lightest, #ededed);
21
188
  }
22
- .link {
189
+
190
+ .snapHash {
191
+ font-size: 12px;
192
+ font-family: 'SF Mono', 'Menlo', 'Monaco', 'Consolas', monospace;
193
+ color: var(--bit-text-color-light, #6c707c);
194
+ flex-shrink: 0;
195
+ }
196
+
197
+ .collapsedAuthor {
198
+ font-size: 12px;
199
+ font-weight: 600;
200
+ color: var(--bit-text-color-light, #6c707c);
201
+ white-space: nowrap;
202
+ flex-shrink: 0;
203
+ }
204
+
205
+ // Chevron icon
206
+ .chevronIcon {
207
+ flex-shrink: 0;
208
+ transition:
209
+ transform 0.2s ease,
210
+ opacity 0.15s ease;
211
+ }
212
+
213
+ .chevronCollapsed {
214
+ transform: rotate(0deg); // points down when collapsed
215
+ }
216
+
217
+ // Expand hint (collapsed snap row)
218
+ .expandHint {
219
+ opacity: 0.3;
220
+ margin-left: auto;
221
+ transition: opacity 0.15s ease;
222
+ }
223
+
224
+ // Collapse hint (expanded snap card header)
225
+ .collapseHint {
226
+ opacity: 0.4;
227
+ margin-left: auto;
228
+ transform: rotate(180deg); // points up when expanded
229
+ }
230
+
231
+ .headerRow {
232
+ display: flex;
233
+ align-items: center;
234
+ gap: 8px;
235
+ margin-bottom: 4px;
236
+ }
237
+
238
+ .metaRow {
239
+ display: flex;
240
+ align-items: center;
241
+ gap: 6px;
242
+ margin-bottom: 6px;
243
+ }
244
+
245
+ .versionLink {
23
246
  text-decoration: none;
24
- color: var(--bit-text-color-heavyed);
25
- font-size: 14px;
26
- margin-bottom: 8px;
247
+ color: var(--bit-accent-color, #6c5ce7);
248
+ font-size: 16px;
249
+ font-weight: 600;
250
+
27
251
  &:hover {
28
252
  text-decoration: underline;
29
253
  }
30
254
  }
31
- .right {
32
- padding-top: 20px;
33
- padding-left: 50px;
34
- margin-bottom: 45px;
35
- max-width: 700px;
36
- .versionTitle {
37
- color: var(--bit-accent-color, #6c5ce7);
38
- font-weight: unset;
39
- margin-bottom: 10px;
40
- }
41
- .titleLink {
42
- text-decoration: none;
43
- display: inline-block;
44
- max-width: fit-content;
45
- }
46
- @media screen and (max-width: 768px) {
47
- padding-left: 0;
255
+
256
+ .snapVersionLink {
257
+ text-decoration: none;
258
+ font-family: 'SF Mono', 'Menlo', 'Monaco', 'Consolas', monospace;
259
+ color: var(--bit-text-color-light, #6c707c);
260
+ font-size: 13px;
261
+ font-weight: 500;
262
+
263
+ &:hover {
264
+ text-decoration: underline;
48
265
  }
49
266
  }
50
267
 
51
- .commitMessage {
52
- font-size: 15px;
53
- max-width: 200px;
268
+ .authorName {
269
+ font-size: 14px;
270
+ font-weight: 600;
271
+ color: var(--bit-text-color-heavy, #2b2b2b);
54
272
  }
273
+
274
+ .firstLine {
275
+ font-size: 14px;
276
+ color: var(--bit-text-color-heavy, #2b2b2b);
277
+ line-height: 1.5;
278
+ word-break: break-word;
279
+ }
280
+
281
+ .restOfMessage {
282
+ font-size: 13px;
283
+ color: var(--bit-text-color-light, #6c707c);
284
+ line-height: 1.5;
285
+ white-space: pre-wrap;
286
+ word-break: break-word;
287
+ margin-top: 4px;
288
+ }
289
+
290
+ .expandButton {
291
+ background: none;
292
+ border: none;
293
+ padding: 0;
294
+ margin-top: 4px;
295
+ font-size: 12px;
296
+ color: var(--bit-accent-color, #6c5ce7);
297
+ cursor: pointer;
298
+
299
+ &:hover {
300
+ text-decoration: underline;
301
+ }
302
+ }
303
+
55
304
  .emptyMessage {
56
- font-size: 15px;
305
+ font-size: 14px;
57
306
  font-style: italic;
58
307
  color: var(--bit-text-color-light, #6c707c);
59
308
  }
60
309
 
61
- .placeholder {
62
- width: 57px;
310
+ @media screen and (max-width: 768px) {
311
+ .row {
312
+ grid-template-columns: 76px 28px 1fr;
313
+ }
314
+
315
+ .dateCol {
316
+ padding-right: 8px;
317
+ }
318
+
319
+ .contentCol {
320
+ padding-left: 10px;
321
+ }
322
+
323
+ .tagCard,
324
+ .snapCard {
325
+ padding: 8px 10px;
326
+ }
63
327
  }
package/version-block.tsx CHANGED
@@ -1,10 +1,11 @@
1
- import { H3 } from '@teambit/documenter.ui.heading';
2
- import { Contributors } from '@teambit/design.ui.contributors';
1
+ /* eslint-disable complexity */
2
+ import { UserAvatar } from '@teambit/design.ui.avatar';
3
+ import { TimeAgo } from '@teambit/design.ui.time-ago';
4
+ import { VersionLabel } from '@teambit/component.ui.version-label';
3
5
  import { Link as BaseLink, useLocation } from '@teambit/base-react.navigation.link';
4
- import { Labels } from '@teambit/component.ui.version-label';
5
6
  import classNames from 'classnames';
6
7
  import type { HTMLAttributes } from 'react';
7
- import React, { useMemo } from 'react';
8
+ import React, { useMemo, useState } from 'react';
8
9
  import type { LegacyComponentLog } from '@teambit/legacy-component-log';
9
10
  import { Tooltip } from '@teambit/design.ui.tooltip';
10
11
  import { useLanes } from '@teambit/lanes.hooks.use-lanes';
@@ -12,6 +13,24 @@ import { LanesModel } from '@teambit/lanes.ui.models.lanes-model';
12
13
 
13
14
  import styles from './version-block.module.scss';
14
15
 
16
+ function ChevronIcon({ collapsed, className }: { collapsed?: boolean; className?: string }) {
17
+ return (
18
+ <svg
19
+ className={classNames(styles.chevronIcon, collapsed && styles.chevronCollapsed, className)}
20
+ width="12"
21
+ height="8"
22
+ viewBox="0 0 12 8"
23
+ fill="none"
24
+ xmlns="http://www.w3.org/2000/svg"
25
+ >
26
+ <path
27
+ d="M10.9999 1.17C10.8126 0.983753 10.5591 0.879211 10.2949 0.879211C10.0308 0.879211 9.77731 0.983753 9.58995 1.17L5.99995 4.71L2.45995 1.17C2.27259 0.983753 2.01913 0.879211 1.75495 0.879211C1.49076 0.879211 1.23731 0.983753 1.04995 1.17C0.95622 1.26297 0.881826 1.37357 0.831057 1.49543C0.780288 1.61729 0.75415 1.74799 0.75415 1.88C0.75415 2.01202 0.780288 2.14272 0.831057 2.26458C0.881826 2.38644 0.95622 2.49704 1.04995 2.59L5.28995 6.83C5.38291 6.92373 5.49351 6.99813 5.61537 7.04889C5.73723 7.09966 5.86794 7.1258 5.99995 7.1258C6.13196 7.1258 6.26267 7.09966 6.38453 7.04889C6.50638 6.99813 6.61699 6.92373 6.70995 6.83L10.9999 2.59C11.0937 2.49704 11.1681 2.38644 11.2188 2.26458C11.2696 2.14272 11.2957 2.01202 11.2957 1.88C11.2957 1.74799 11.2696 1.61729 11.2188 1.49543C11.1681 1.37357 11.0937 1.26297 10.9999 1.17Z"
28
+ fill="var(--bit-text-color-light, #6c707c)"
29
+ />
30
+ </svg>
31
+ );
32
+ }
33
+
15
34
  // @todo - this will be fixed as part of the @teambit/base-react.navigation.link upgrade to latest
16
35
  const Link = BaseLink as any;
17
36
 
@@ -20,71 +39,216 @@ export type VersionBlockProps = {
20
39
  isLatest: boolean;
21
40
  snap: LegacyComponentLog;
22
41
  isCurrent: boolean;
42
+ collapsed?: boolean;
43
+ onToggleCollapse?: () => void;
44
+ /** When true, all entries get card-style borders (used in "expand all" mode) */
45
+ allExpanded?: boolean;
23
46
  } & HTMLAttributes<HTMLDivElement>;
24
- /**
25
- * change log section
26
- * @name VersionBlock
27
- */
28
- export function VersionBlock({ isLatest, className, snap, componentId, isCurrent, ...rest }: VersionBlockProps) {
29
- const { username, email, message, tag, hash, date } = snap;
47
+
48
+ export function VersionBlock({
49
+ isLatest,
50
+ className,
51
+ snap,
52
+ componentId,
53
+ isCurrent,
54
+ collapsed = false,
55
+ onToggleCollapse,
56
+ allExpanded = false,
57
+ ...rest
58
+ }: VersionBlockProps) {
59
+ const { username, displayName, email, profileImage, message, tag, hash, date } = snap;
30
60
  const { lanesModel } = useLanes();
31
61
  const currentLaneUrl = lanesModel?.isViewingNonDefaultLane()
32
- ? // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
33
- `${LanesModel.getLaneUrl(lanesModel.viewedLane!.id)}${LanesModel.baseLaneComponentRoute}`
62
+ ? `${LanesModel.getLaneUrl(lanesModel.viewedLane!.id)}${LanesModel.baseLaneComponentRoute}`
34
63
  : '';
35
64
 
36
65
  const version = tag || hash;
37
- const author = useMemo(() => {
38
- return {
39
- displayName: username,
40
- email,
41
- };
42
- }, [snap]);
66
+ const isTag = Boolean(tag);
67
+ const displayVersion = tag ? `v${tag}` : hash.slice(0, 7);
68
+
69
+ const author = useMemo(
70
+ () => ({
71
+ displayName: displayName || username || '',
72
+ email: email || '',
73
+ name: username || '',
74
+ profileImage: profileImage || '',
75
+ }),
76
+ [displayName, username, email, profileImage]
77
+ );
43
78
 
44
- const timestamp = useMemo(() => (date ? new Date(parseInt(date)).toString() : new Date().toString()), [date]);
45
79
  const location = useLocation();
46
80
  const { pathname } = location || {};
47
81
 
48
- const testsUrl = currentLaneUrl
49
- ? `${currentLaneUrl}/${componentId}/~tests?version=${version}`
50
- : `${pathname?.replace('~changelog', '~tests')}?version=${version}`;
51
-
52
- const compositionsUrl = currentLaneUrl
53
- ? `${currentLaneUrl}/${componentId}/~compositions?version=${version}`
54
- : `${pathname?.replace('~changelog', '~compositions')}?version=${version}`;
55
-
82
+ // Navigate to component page at that version (not the changelog)
83
+ const componentBasePath = pathname?.replace(/\/~changelog.*$/, '') || '';
56
84
  const versionUrl = currentLaneUrl
57
85
  ? `${currentLaneUrl}/${componentId}?version=${version}`
58
- : `${pathname}?version=${version}`;
86
+ : `${componentBasePath}?version=${version}`;
87
+
88
+ const { firstLine, rest: restOfMessage } = useCommitMessage(message);
89
+ const [messageExpanded, setMessageExpanded] = useState(false);
90
+ const hasMore = restOfMessage.length > 0;
91
+
92
+ const authorDisplay = displayName || username || 'Unknown';
93
+
94
+ if (!isTag && collapsed) {
95
+ return (
96
+ <div
97
+ className={classNames(styles.row, styles.snapRow, styles.collapsedRow, className)}
98
+ onClick={onToggleCollapse}
99
+ role="button"
100
+ tabIndex={0}
101
+ onKeyDown={(e) => e.key === 'Enter' && onToggleCollapse?.()}
102
+ {...rest}
103
+ >
104
+ <div className={styles.dateCol}>
105
+ <TimeAgo className={styles.dateText} date={date ? parseInt(date) : Date.now()} />
106
+ </div>
107
+ <div className={styles.dotCol}>
108
+ <div className={styles.snapDotOuter}>
109
+ <div className={styles.snapDotInner} />
110
+ </div>
111
+ </div>
112
+ <div className={styles.contentCol}>
113
+ <div className={styles.collapsedContent}>
114
+ <Tooltip placement="bottom" content={hash}>
115
+ <span className={styles.snapHash}>{displayVersion}</span>
116
+ </Tooltip>
117
+ <span className={styles.collapsedAuthor}>{authorDisplay}</span>
118
+ <ChevronIcon collapsed className={styles.expandHint} />
119
+ </div>
120
+ </div>
121
+ </div>
122
+ );
123
+ }
59
124
 
125
+ if (!isTag) {
126
+ return (
127
+ <div className={classNames(styles.row, styles.snapRow, className)} {...rest}>
128
+ <div className={styles.dateCol}>
129
+ <TimeAgo className={styles.dateText} date={date ? parseInt(date) : Date.now()} />
130
+ </div>
131
+ <div className={styles.dotCol}>
132
+ <div className={styles.snapDotOuter}>
133
+ <div className={styles.snapDotInner} />
134
+ </div>
135
+ </div>
136
+ <div className={styles.contentCol}>
137
+ <div
138
+ className={styles.snapCard}
139
+ onClick={onToggleCollapse}
140
+ role="button"
141
+ tabIndex={0}
142
+ onKeyDown={(e) => e.key === 'Enter' && onToggleCollapse?.()}
143
+ >
144
+ <div className={styles.headerRow}>
145
+ <Tooltip placement="bottom" content={hash}>
146
+ <Link
147
+ className={styles.snapVersionLink}
148
+ href={versionUrl}
149
+ onClick={(e: React.MouseEvent) => e.stopPropagation()}
150
+ >
151
+ {displayVersion}
152
+ </Link>
153
+ </Tooltip>
154
+ <ChevronIcon className={styles.collapseHint} />
155
+ </div>
156
+ <div className={styles.metaRow}>
157
+ <UserAvatar account={author} size={20} fontSize={8} />
158
+ <span className={styles.authorName}>{authorDisplay}</span>
159
+ </div>
160
+ <div className={styles.messageSection}>
161
+ {firstLine ? (
162
+ <>
163
+ <div className={styles.firstLine}>{firstLine}</div>
164
+ {hasMore && (
165
+ <>
166
+ {messageExpanded && <div className={styles.restOfMessage}>{restOfMessage}</div>}
167
+ <button
168
+ className={styles.expandButton}
169
+ onClick={(e) => {
170
+ e.stopPropagation();
171
+ setMessageExpanded(!messageExpanded);
172
+ }}
173
+ type="button"
174
+ >
175
+ {messageExpanded ? 'Show less' : 'Show more'}
176
+ </button>
177
+ </>
178
+ )}
179
+ </>
180
+ ) : (
181
+ <div className={styles.emptyMessage}>No commit message</div>
182
+ )}
183
+ </div>
184
+ </div>
185
+ </div>
186
+ </div>
187
+ );
188
+ }
189
+
190
+ // ----- Tag entry -----
60
191
  return (
61
- <div className={classNames(styles.versionWrapper, className)}>
62
- <div className={styles.left}>
63
- <Labels isLatest={isLatest} isCurrent={isCurrent} />
64
- <Link className={styles.link} href={testsUrl}>
65
- Tests
66
- </Link>
67
- <Link className={styles.link} href={compositionsUrl}>
68
- Compositions
69
- </Link>
70
- <div className={styles.placeholder} />
192
+ <div className={classNames(styles.row, styles.tagRow, className)} {...rest}>
193
+ <div className={styles.dateCol}>
194
+ <TimeAgo className={styles.dateText} date={date ? parseInt(date) : Date.now()} />
195
+ </div>
196
+ <div className={styles.dotCol}>
197
+ <div className={styles.tagDotOuter}>
198
+ <div className={styles.tagDotRing}>
199
+ <div className={styles.tagDotInner} />
200
+ </div>
201
+ </div>
71
202
  </div>
72
- <div className={classNames(styles.right, className)} {...rest}>
73
- <Tooltip placement="right" content={hash}>
74
- <Link className={styles.titleLink} href={versionUrl}>
75
- <H3 size="xs" className={styles.versionTitle}>
76
- {tag ? `v${tag}` : hash}
77
- </H3>
78
- </Link>
79
- </Tooltip>
80
- <Contributors contributors={[author || {}]} timestamp={timestamp} />
81
- {commitMessage(message)}
203
+ <div className={styles.contentCol}>
204
+ <div className={classNames(styles.tagCard, allExpanded && styles.tagCardBordered)}>
205
+ <div className={styles.headerRow}>
206
+ <Tooltip placement="bottom" content={hash}>
207
+ <Link className={styles.versionLink} href={versionUrl}>
208
+ {displayVersion}
209
+ </Link>
210
+ </Tooltip>
211
+ {isLatest && <VersionLabel status="latest" />}
212
+ {isCurrent && <VersionLabel status="current" />}
213
+ </div>
214
+ <div className={styles.metaRow}>
215
+ <UserAvatar account={author} size={20} fontSize={8} />
216
+ <span className={styles.authorName}>{authorDisplay}</span>
217
+ </div>
218
+ <div className={styles.messageSection}>
219
+ {firstLine ? (
220
+ <>
221
+ <div className={styles.firstLine}>{firstLine}</div>
222
+ {hasMore && (
223
+ <>
224
+ {messageExpanded && <div className={styles.restOfMessage}>{restOfMessage}</div>}
225
+ <button
226
+ className={styles.expandButton}
227
+ onClick={() => setMessageExpanded(!messageExpanded)}
228
+ type="button"
229
+ >
230
+ {messageExpanded ? 'Show less' : 'Show more'}
231
+ </button>
232
+ </>
233
+ )}
234
+ </>
235
+ ) : (
236
+ <div className={styles.emptyMessage}>No commit message</div>
237
+ )}
238
+ </div>
239
+ </div>
82
240
  </div>
83
241
  </div>
84
242
  );
85
243
  }
86
244
 
87
- function commitMessage(message: string) {
88
- if (!message || message === '') return <div className={styles.emptyMessage}>No commit message</div>;
89
- return <div className={styles.commitMessage}>{message}</div>;
245
+ function useCommitMessage(message: string) {
246
+ return useMemo(() => {
247
+ if (!message) return { firstLine: '', rest: '' };
248
+ const lines = message.split('\n');
249
+ return {
250
+ firstLine: lines[0],
251
+ rest: lines.slice(1).join('\n').trim(),
252
+ };
253
+ }, [message]);
90
254
  }