Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .changeset/30070-customization-mark-words.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
'@object-ui/app-shell': patch
'@object-ui/i18n': patch
---

fix(app-shell): Studio's rails mark a customized row with the word 定制 / Custom and an added row with 新增 / Added, no fill, instead of a filled square with a glyph that read as an add button (#30070)

Clause-②: yes (widening) — new studio i18n key `engine.customizationMark.customizedShort` (en, zh)
Metadata-protocol: none

The compact customization mark the pillar rails draw (and the Interfaces rail's navigation-override mark, which wraps it) now prints one short coloured word with no background. Its tooltip and accessible name are unchanged: the full text ("已定制(HotCRM)") followed by the sentence naming the customization package. It is not a control: a click on it is a click on its row.
28 changes: 14 additions & 14 deletions ui/packages/app-shell/src/views/studio-design/CustomizationMark.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
*/

import * as React from 'react';
import { Layers3, PencilRuler, Plus } from 'lucide-react';
import { cn } from '@object-ui/components';
import { customizationPackageId } from '@objectstack/spec/kernel';
import { t, tFormat } from '../metadata-admin/i18n.js';
Expand All @@ -36,18 +35,20 @@ const KIND_CLASS: Record<CustomizationMarkChipKind, string> = {
customizationOf: 'bg-muted text-muted-foreground',
};

/** objectstack#29693 — the glyph the compact mark draws in place of its text. */
const KIND_ICON: Record<CustomizationMarkChipKind, React.ElementType> = {
customized: PencilRuler,
added: Plus,
customizationOf: Layers3,
/** The compact mark's colour: its word alone, no fill, so it never reads as a button. */
const KIND_TEXT_CLASS: Record<CustomizationMarkChipKind, string> = {
customized: 'text-violet-700 dark:text-violet-300',
added: 'text-emerald-700 dark:text-emerald-300',
customizationOf: 'text-muted-foreground',
};

/**
* objectstack#29693 — `compact` (the pillar rails): the mark is its glyph
* alone, so it never takes a row's name ("已定制(ObjectStack Showcase)" left a
* selected flow row no room for its label). Its text is its accessible name
* and leads its tooltip, before the sentence naming the pair.
* `compact` (the pillar rails): the mark prints one short word with no
* background (objectstack#30070: 定制 for customized, 新增 for added), so it
* reads as a status, never as a button, and never takes a row's name
* (objectstack#29693). Its full text is its accessible name and leads its
* tooltip, before the sentence naming the pair. It is not a control: a click
* on it is a click on its row.
*/
export function CustomizationMark({
kind,
Expand All @@ -64,7 +65,7 @@ export function CustomizationMark({
pairId?: string;
locale: string;
className?: string;
/** Draw the glyph alone, the text in its accessible name and tooltip. */
/** Print one short word, no fill; the full text is its accessible name and leads its tooltip. */
compact?: boolean;
}): React.ReactElement {
const base = baseName ?? '';
Expand All @@ -76,7 +77,6 @@ export function CustomizationMark({
? tFormat(`engine.customizationMark.${kind}Title`, locale, { baseName: base, pair: pairId })
: undefined;
if (compact) {
const Icon = KIND_ICON[kind];
return (
<span
data-testid="customization-mark"
Expand All @@ -85,9 +85,9 @@ export function CustomizationMark({
role="img"
aria-label={label}
title={title ? `${label}\n${title}` : label}
className={cn('inline-flex h-[18px] w-[18px] shrink-0 items-center justify-center rounded-[5px]', KIND_CLASS[kind], className)}
className={cn('inline-flex shrink-0 items-center whitespace-nowrap text-[11px] font-medium leading-none', KIND_TEXT_CLASS[kind], className)}
>
<Icon className="h-3 w-3" aria-hidden="true" />
{kind === 'customized' ? t('engine.customizationMark.customizedShort', locale) : label}
</span>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -464,8 +464,8 @@ describe('the Interfaces rail lists a base and its pair as one list (objectstack
});
});

describe('the rail mark never takes the row’s name (objectstack#29693)', () => {
it('is its glyph alone: the row prints only its label, and the mark’s text is its name and leads its tooltip', async () => {
describe('the rail mark never takes the row’s name (objectstack#29693, objectstack#30070)', () => {
it('prints one short word with no fill and no glyph: its full text is its name and leads its tooltip', async () => {
renderAt(`/studio/${BASE}/interfaces`);
await interfacesReady();
const row = rowOf(ifRail(), 'dashboard', 'pipeline')!;
Expand All @@ -475,11 +475,16 @@ describe('the rail mark never takes the row’s name (objectstack#29693)', () =>
expect(mark).toHaveAttribute('data-compact');
expect(mark).toHaveAccessibleName(label);
expect(mark.getAttribute('title')?.split('\n')[0]).toBe(label);
expect(mark.textContent).toBe('');
expect(mark.textContent).toBe(t('engine.customizationMark.customizedShort', 'en'));
expect(within(row).queryByText(label)).toBeNull();
const added = within(rowOf(ifRail(), 'dashboard', 'wins')!).getByTestId('customization-mark');
expect(added).toHaveAccessibleName(t('engine.customizationMark.added', 'en'));
expect(added.textContent).toBe('');
expect(added.textContent).toBe(t('engine.customizationMark.added', 'en'));
// A status, not a control: no filled block, no glyph that reads as an add button.
for (const m of [mark, added]) {
expect(m.querySelector('svg')).toBeNull();
expect(m.className).not.toMatch(/(^|\s)bg-/);
}
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,9 +115,9 @@ export function railCustomizationsOf(
}

/**
* A row's mark in the rail's trailing slot, or nothing. objectstack#29693 —
* compact (its glyph, the text in its name and tooltip), so it never takes the
* row's label.
* A row's mark in the rail's trailing slot, or nothing. Compact (one short
* word, no fill; the full text in its name and tooltip), so it never takes the
* row's label and never reads as a button.
*/
export function RailCustomizationMark({
row,
Expand Down
1 change: 1 addition & 0 deletions ui/packages/i18n/src/namespaces/studio/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3677,6 +3677,7 @@ const STUDIO_EN: Record<string, string> = {
// objectstack ADR-0146 D3 (#28568) — the customization marks, counts and
// filter of the All metadata area, from a base's customization read.
'engine.customizationMark.customized': 'Customized ({baseName})',
'engine.customizationMark.customizedShort': 'Custom',
'engine.customizationMark.customizedTitle': 'The customization package {pair} overrides this item of {baseName}',
'engine.customizationMark.added': 'Added',
'engine.customizationMark.addedTitle': 'Added to {baseName} by its customization package {pair}',
Expand Down
1 change: 1 addition & 0 deletions ui/packages/i18n/src/namespaces/studio/zh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3254,6 +3254,7 @@ const STUDIO_ZH: Record<string, string> = {
// objectstack ADR-0146 D3 (#28568) — the customization marks, counts and
// filter of the All metadata area, from a base's customization read.
'engine.customizationMark.customized': '已定制({baseName})',
'engine.customizationMark.customizedShort': '定制',
'engine.customizationMark.customizedTitle': '定制包 {pair} 覆盖了 {baseName} 的此项',
'engine.customizationMark.added': '新增',
'engine.customizationMark.addedTitle': '由 {baseName} 的定制包 {pair} 新增',
Expand Down
Loading