diff --git a/packages/@ourworldindata/grapher/src/modal/SourcesModal.scss b/packages/@ourworldindata/grapher/src/modal/SourcesModal.scss
index be482435403..86f3482fb10 100644
--- a/packages/@ourworldindata/grapher/src/modal/SourcesModal.scss
+++ b/packages/@ourworldindata/grapher/src/modal/SourcesModal.scss
@@ -4,6 +4,7 @@
$tab-padding: 16px;
$tab-font-size: 13px;
$tab-gap: 8px;
+ $tab-secondary-margin: 8px;
$border: $gray-20;
@@ -64,6 +65,16 @@
font-size: $tab-font-size;
padding: 8px $tab-padding;
margin-right: $tab-gap;
+
+ .attribution {
+ color: $gray-60;
+ display: inline-block;
+ margin-left: $tab-secondary-margin;
+ }
+
+ &[aria-selected="true"] .attribution {
+ color: $blue-50;
+ }
}
.source {
diff --git a/packages/@ourworldindata/grapher/src/modal/SourcesModal.tsx b/packages/@ourworldindata/grapher/src/modal/SourcesModal.tsx
index 4210be78314..93edffb8aa1 100644
--- a/packages/@ourworldindata/grapher/src/modal/SourcesModal.tsx
+++ b/packages/@ourworldindata/grapher/src/modal/SourcesModal.tsx
@@ -25,7 +25,7 @@ import {
CLOSE_BUTTON_WIDTH,
CloseButton,
} from "@ourworldindata/components"
-import React from "react"
+import React, { ReactElement } from "react"
import cx from "classnames"
import { action, computed } from "mobx"
import { observer } from "mobx-react"
@@ -46,6 +46,7 @@ const MAX_CONTENT_WIDTH = 640
const TAB_PADDING = 16
const TAB_FONT_SIZE = 13
const TAB_GAP = 8
+const TAB_SECONDARY_MARGIN = 8
export interface SourcesModalManager {
isReady?: boolean
@@ -112,10 +113,32 @@ export class SourcesModal extends React.Component<
return this.manager.columnsWithSourcesExtensive
}
- @computed private get tabLabels(): string[] {
- return this.columns.map(
- (column) => column.titlePublicOrDisplayName.title
- )
+ @computed private get tabLabels(): ReactElement[] {
+ return this.columns.map((column) => {
+ const attribution = joinTitleFragments(
+ column.titlePublicOrDisplayName.attributionShort,
+ column.titlePublicOrDisplayName.titleVariant
+ )
+ return (
+
+ {column.titlePublicOrDisplayName.title}
+ {attribution && (
+ {attribution}
+ )}
+
+ )
+ })
+ }
+
+ @computed private get tabLabelWidths(): number[] {
+ return this.columns.map((column) => {
+ const title = `${column.titlePublicOrDisplayName.title}`
+ const fragments = joinTitleFragments(
+ column.titlePublicOrDisplayName.attributionShort,
+ column.titlePublicOrDisplayName.titleVariant
+ )
+ return measureTabWidth(title, fragments) + TAB_GAP
+ })
}
private renderSource(
@@ -162,12 +185,8 @@ export class SourcesModal extends React.Component<
this.modalBounds.width - 2 * this.modalPadding - 10 // wiggle room
)
- const labelWidths = this.tabLabels.map(
- (label) => measureTabWidth(label) + TAB_GAP
- )
-
// check if all tab labels fit into a single line
- if (sum(labelWidths) <= maxWidth) {
+ if (sum(this.tabLabelWidths) <= maxWidth) {
return (
Math.min(measureTabWidth(label), maxTabWidth) + TAB_GAP
+ const clippedLabelWidths = this.tabLabelWidths.map((labelWidth) =>
+ Math.min(labelWidth, maxTabWidth + TAB_GAP)
)
// check if all tab labels fit into a single line when they are clipped
@@ -195,18 +214,20 @@ export class SourcesModal extends React.Component<
}
// compute the subset of tabs that fit into a single line
- const getVisibleLabels = (labels: string[]) => {
+ const getVisibleLabels = (
+ labels: React.ReactElement[]
+ ): React.ReactElement[] => {
// take width of the "Show more" button into account
let width =
measureTabWidth("Show more") +
13 + // icon width
6 // icon padding
- const visibleLabels: string[] = []
+ const visibleLabels: React.ReactElement[] = []
for (const [label, labelWidth] of zip(labels, clippedLabelWidths)) {
width += labelWidth as number
if (width > maxWidth) break
- visibleLabels.push(label as string)
+ visibleLabels.push(label as React.ReactElement)
}
return visibleLabels
@@ -493,10 +514,16 @@ export class Source extends React.Component<{
}
}
-const measureTabWidth = (label: string): number => {
- return (
- 2 * TAB_PADDING +
- Bounds.forText(label, { fontSize: TAB_FONT_SIZE }).width +
- 2 // border
- )
+const measureTabWidth = (label: string, secondary?: string): number => {
+ const getWidth = (text: string) =>
+ Bounds.forText(text, { fontSize: TAB_FONT_SIZE }).width
+
+ const labelWidth = getWidth(label)
+ const secondaryTextWidth = secondary
+ ? getWidth(secondary) + TAB_SECONDARY_MARGIN
+ : 0
+ const padding = 2 * TAB_PADDING
+ const borderWidth = 2
+
+ return labelWidth + secondaryTextWidth + padding + borderWidth
}
diff --git a/packages/@ourworldindata/grapher/src/tabs/ExpandableTabs.tsx b/packages/@ourworldindata/grapher/src/tabs/ExpandableTabs.tsx
index fefc229c848..22b01afa0aa 100644
--- a/packages/@ourworldindata/grapher/src/tabs/ExpandableTabs.tsx
+++ b/packages/@ourworldindata/grapher/src/tabs/ExpandableTabs.tsx
@@ -9,14 +9,14 @@ export const ExpandableTabs = ({
activeIndex,
setActiveIndex,
isExpandedDefault = false,
- getVisibleLabels = (labels: string[]) => labels.slice(0, 3),
+ getVisibleLabels = (labels: React.ReactElement[]) => labels.slice(0, 3),
maxTabWidth = 240,
}: {
- labels: string[]
+ labels: React.ReactElement[]
activeIndex: number
setActiveIndex: (index: number) => void
isExpandedDefault?: boolean
- getVisibleLabels?: (tabLabels: string[]) => string[]
+ getVisibleLabels?: (tabLabels: React.ReactElement[]) => React.ReactElement[]
maxTabWidth?: number | null // if null, don't clip labels
}) => {
const [isExpanded, setExpanded] = useState(isExpandedDefault)
diff --git a/packages/@ourworldindata/grapher/src/tabs/Tabs.tsx b/packages/@ourworldindata/grapher/src/tabs/Tabs.tsx
index f8b51a0681c..51025ec053e 100644
--- a/packages/@ourworldindata/grapher/src/tabs/Tabs.tsx
+++ b/packages/@ourworldindata/grapher/src/tabs/Tabs.tsx
@@ -9,7 +9,7 @@ export const Tabs = ({
maxTabWidth = 240,
slot,
}: {
- labels: string[]
+ labels: React.ReactElement[]
activeIndex: number
setActiveIndex: (label: number) => void
horizontalScroll?: boolean
@@ -68,7 +68,7 @@ export const Tabs = ({
const isActive = index === activeIndex
return (