mirror of
https://github.com/nexu-io/open-design.git
synced 2026-10-27 09:51:12 +01:00
fix(share): use green for update-link action (OPEND-3790) (#8717)
This commit is contained in:
@@ -63,7 +63,7 @@ button.primary.transparentBusy[aria-busy="true"]:disabled {
|
||||
mid-poll would drop focus from the button. */
|
||||
button.primary:global(.is-signing) { cursor: default; background: var(--share-ink-busy); }
|
||||
|
||||
/* ---- soft — 88px/32px blue (updateButton / signInSecondaryAction / plain retry) ---- */
|
||||
/* ---- soft — shared 88px/32px geometry; blue sign-in / plain retry ---- */
|
||||
button.soft {
|
||||
box-sizing: border-box;
|
||||
display: inline-flex;
|
||||
@@ -98,6 +98,24 @@ button.soft:global(.is-signing) { cursor: default; }
|
||||
declared this and keep the shared Button's own disabled treatment. */
|
||||
button.soft.dimDisabled:disabled { opacity: 0.6; cursor: default; }
|
||||
|
||||
/* Update inherits soft geometry, but none of its blue interaction colors.
|
||||
Disabled/loading retain the same readable green pair; :not(:disabled)
|
||||
prevents hover/active from painting over those states. */
|
||||
button.update,
|
||||
button.update:disabled {
|
||||
background: var(--share-update-bg);
|
||||
color: var(--share-update-fg);
|
||||
}
|
||||
button.update:hover:not(:disabled) {
|
||||
background: color-mix(in srgb, var(--share-update-fg) 18%, var(--share-white));
|
||||
color: var(--share-update-fg);
|
||||
}
|
||||
button.update:active:not(:disabled) {
|
||||
background: color-mix(in srgb, var(--share-update-fg) 24%, var(--share-white));
|
||||
color: var(--share-update-fg);
|
||||
}
|
||||
button.update:focus-visible { outline-color: var(--share-update-fg); }
|
||||
|
||||
/* ---- soft-error — CommentSyncBanner's reopened-K5 retry (red) ---- */
|
||||
button.softError {
|
||||
background: var(--share-red-bg);
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { ButtonHTMLAttributes } from 'react';
|
||||
import { Button } from '@open-design/components';
|
||||
import styles from './ShareButton.module.css';
|
||||
|
||||
export type ShareButtonVariant = 'primary' | 'soft' | 'soft-error' | 'dark-sm';
|
||||
export type ShareButtonVariant = 'primary' | 'soft' | 'update' | 'soft-error' | 'dark-sm';
|
||||
|
||||
export interface ShareButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
variant: ShareButtonVariant;
|
||||
@@ -36,6 +36,7 @@ export interface ShareButtonProps extends ButtonHTMLAttributes<HTMLButtonElement
|
||||
const VARIANT_CLASS: Record<ShareButtonVariant, string | undefined> = {
|
||||
primary: styles.primary,
|
||||
soft: styles.soft,
|
||||
update: `${styles.soft} ${styles.update}`,
|
||||
'soft-error': `${styles.soft} ${styles.softError}`,
|
||||
'dark-sm': styles.darkSm,
|
||||
};
|
||||
|
||||
@@ -378,7 +378,7 @@ export function ShareTab({
|
||||
message={t('fileViewer.shareUpdateHint')}
|
||||
action={
|
||||
<ShareButton
|
||||
variant="soft"
|
||||
variant="update"
|
||||
disabled={!canMutatePublicShare || viewerOnly || streaming || publishing || preflightFailed || planTooLarge || planBlocked}
|
||||
aria-busy={publishing || undefined}
|
||||
onClick={() => { if (canMutatePublicShare && !viewerOnly) void planState.run(() => updateCurrentFilePublic?.() ?? Promise.resolve()); }}
|
||||
|
||||
@@ -28,7 +28,12 @@
|
||||
--share-ink-dark-sm: var(--share-surface-dark);
|
||||
--share-ink-dark-sm-hover: #353535;
|
||||
|
||||
/* Blues — the 88px soft button (update link / sign-in-to-update / plain retry). */
|
||||
/* Update link only: ShareEntry's existing published green pair.
|
||||
Reuse the product palette, not sampled screenshot colors. */
|
||||
--share-update-fg: #2F6B3F;
|
||||
--share-update-bg: #E3F1E6;
|
||||
|
||||
/* Blues — sign-in-to-update / plain retry (not the update action). */
|
||||
--share-blue-bg: #EDF3FF;
|
||||
--share-blue-fg: #245FCE;
|
||||
--share-blue-bg-hover: #E0EAFF;
|
||||
|
||||
@@ -7,6 +7,8 @@ import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
import { buildWorkspacePermissions, buildWorkspaceSeatSummary, type WorkspaceCollabContext } from '@open-design/contracts';
|
||||
import { parse } from 'postcss';
|
||||
import { ShareTab } from '../../../src/components/share/ShareTab';
|
||||
import { ShareButton } from '../../../src/components/share/ShareButton';
|
||||
import buttonStyles from '../../../src/components/share/ShareButton.module.css';
|
||||
import { I18nProvider } from '../../../src/i18n';
|
||||
import { readShareCss } from '../../helpers/read-share-css';
|
||||
|
||||
@@ -36,6 +38,57 @@ function props(overrides: Partial<Props> = {}): Props {
|
||||
};
|
||||
}
|
||||
|
||||
describe('OPEND-3790 update-only green action', () => {
|
||||
it('renders green update colors without recoloring soft actions or changing the update callback and URL', async () => {
|
||||
// Apply the production stylesheet to the rendered CSS Module classes. This
|
||||
// checks the actual cascade on the action, not merely a selector's presence.
|
||||
const sheet = parse(readShareCss(resolve(__dirname, '../../../src/components/share/ShareButton.module.css')));
|
||||
sheet.walkRules(rule => {
|
||||
rule.selector = rule.selector.replace(/:global\(([^)]+)\)/g, '$1')
|
||||
.replace(/\.([a-zA-Z][\w-]*)/g, (match, name: string) => buttonStyles[name] ? `.${buttonStyles[name]}` : match);
|
||||
});
|
||||
const style = document.createElement('style');
|
||||
style.textContent = sheet.toString() + readFileSync(resolve(__dirname, '../../../src/styles/primitives.css'), 'utf8');
|
||||
document.head.append(style);
|
||||
try {
|
||||
vi.stubGlobal('fetch', vi.fn(async () => Response.json({
|
||||
fileCount: 1, totalBytes: 12, exceedsSizeLimit: false, exclusions: [],
|
||||
})));
|
||||
const update = vi.fn().mockResolvedValue(undefined);
|
||||
const input = props({ projectId: 'color-fixture', filePath: 'index.html', publicationStatus: 'active', publicationFreshness: 'outdated', updateCurrentFilePublic: update });
|
||||
const view = render(<><ShareTab {...input} /><ShareButton variant="soft">Sign in / Retry control</ShareButton></>);
|
||||
const action = screen.getByRole('button', { name: 'fileViewer.shareUpdateLink' });
|
||||
const colors = () => {
|
||||
const computed = getComputedStyle(action);
|
||||
expect(computed.color).toBe('rgb(47, 107, 63)');
|
||||
expect(computed.backgroundColor).toBe('rgb(227, 241, 230)');
|
||||
expect(computed.height).toBe('32px');
|
||||
expect(computed.width).toBe('88px');
|
||||
expect(computed.borderRadius).toBe('6px');
|
||||
};
|
||||
colors();
|
||||
const soft = getComputedStyle(screen.getByRole('button', { name: 'Sign in / Retry control' }));
|
||||
expect(soft.color).toBe('rgb(36, 95, 206)');
|
||||
expect(soft.backgroundColor).toBe('rgb(237, 243, 255)');
|
||||
expect(update).not.toHaveBeenCalled();
|
||||
await act(async () => fireEvent.click(action));
|
||||
await waitFor(() => expect(update).toHaveBeenCalledTimes(1));
|
||||
expect(screen.getByText(input.publishedFileUrl)).toBeVisible();
|
||||
expect(input.publishCurrentFilePublic).not.toHaveBeenCalled();
|
||||
expect(input.copyPublishedFileLink).not.toHaveBeenCalled();
|
||||
for (const state of [{ publishingPublicFile: true }, { viewerOnly: true }, { streaming: true }]) {
|
||||
view.rerender(<ShareTab {...input} {...state} />);
|
||||
expect(action).toBeDisabled();
|
||||
colors();
|
||||
fireEvent.click(action);
|
||||
expect(update).toHaveBeenCalledTimes(1);
|
||||
}
|
||||
} finally {
|
||||
style.remove();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const checkPath = 'm3 8 3 3 7-7';
|
||||
const linkPath = 'M10 13.5a5 5 0 0 0 7 .2l3-3a5 5 0 0 0-7-7l-1.7 1.7M14 10.5a5 5 0 0 0-7-.2l-3 3a5 5 0 0 0 7 7l1.7-1.7';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user