feat: Add configurable card design options and enhance person details in organization chart
This commit is contained in:
@@ -11,6 +11,9 @@ SPFx-1.4.1-WebPart für SharePoint Server Subscription Edition. Es erstellt aus
|
||||
- standardmäßig vollständig aufgeklappter Baum
|
||||
- einzelne Zweige können auf- und zugeklappt werden
|
||||
- Profilbild, Anzeigename, Position, Abteilung und optional Kontaktdaten
|
||||
- Teams-ähnliche Personenkarten mit Text links und Profilbild rechts
|
||||
- aufklappbare Personendetails direkt in der Karte
|
||||
- konfigurierbare Kartenfarben und wahlweise abgerundete Ecken
|
||||
- Verlinkung auf die SharePoint-Profilseite
|
||||
- kurzzeitiger In-Memory-Profilcache zur Reduzierung wiederholter REST-Aufrufe
|
||||
- responsive Baumdarstellung mit automatisch kompakteren Karten bei breiten Organisationsebenen
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"name": "organization-chart-webpart",
|
||||
"title": "organization-chart-webpart",
|
||||
"id": "b2f88ea8-f7da-4d0e-a945-6973bf1acd1e",
|
||||
"version": "1.0.6.0",
|
||||
"version": "1.1.0.0",
|
||||
"includeClientSideAssets": true,
|
||||
"skipFeatureDeployment": true
|
||||
},
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "organization-chart-webpart",
|
||||
"version": "1.0.6",
|
||||
"version": "1.1.0",
|
||||
"private": true,
|
||||
"main": "lib/index.js",
|
||||
"engines": { "node": ">=6.9.0 <9.0.0" },
|
||||
|
||||
@@ -23,7 +23,10 @@
|
||||
"maxPeople": 100,
|
||||
"cacheMinutes": 15,
|
||||
"showPictures": true,
|
||||
"showContactDetails": false
|
||||
"showContactDetails": false,
|
||||
"cardBackgroundColor": "",
|
||||
"cardAccentColor": "",
|
||||
"roundedCards": false
|
||||
}
|
||||
}]
|
||||
}
|
||||
|
||||
@@ -34,6 +34,9 @@ export interface IOrganizationChartWebPartProps {
|
||||
cacheMinutes: number;
|
||||
showPictures: boolean;
|
||||
showContactDetails: boolean;
|
||||
cardBackgroundColor: string;
|
||||
cardAccentColor: string;
|
||||
roundedCards: boolean;
|
||||
}
|
||||
|
||||
export default class OrganizationChartWebPart extends BaseClientSideWebPart<IOrganizationChartWebPartProps> {
|
||||
@@ -61,9 +64,19 @@ export default class OrganizationChartWebPart extends BaseClientSideWebPart<IOrg
|
||||
title: this.properties.title || strings.DefaultTitle,
|
||||
showPictures: this.properties.showPictures !== false,
|
||||
showContactDetails: this.properties.showContactDetails === true,
|
||||
cardBackgroundColor: this.properties.cardBackgroundColor || '',
|
||||
cardAccentColor: this.properties.cardAccentColor || '',
|
||||
roundedCards: this.properties.roundedCards === true,
|
||||
reportsLabel: strings.ReportsLabel,
|
||||
collapseLabel: strings.CollapseLabel,
|
||||
expandLabel: strings.ExpandLabel,
|
||||
expandDetailsLabel: strings.ExpandDetailsLabel,
|
||||
collapseDetailsLabel: strings.CollapseDetailsLabel,
|
||||
departmentLabel: strings.DepartmentLabel,
|
||||
officeLabel: strings.OfficeLabel,
|
||||
emailLabel: strings.EmailLabel,
|
||||
phoneLabel: strings.PhoneLabel,
|
||||
profileLabel: strings.ProfileLabel,
|
||||
truncatedLabel: strings.TruncatedLabel
|
||||
});
|
||||
const contentHost: HTMLDivElement = this.prepareSurface(data.root.profile.displayName);
|
||||
@@ -104,7 +117,16 @@ export default class OrganizationChartWebPart extends BaseClientSideWebPart<IOrg
|
||||
groupName: strings.DisplayGroupName,
|
||||
groupFields: [
|
||||
PropertyPaneToggle('showPictures', { label: strings.ShowPicturesFieldLabel }),
|
||||
PropertyPaneToggle('showContactDetails', { label: strings.ShowContactDetailsFieldLabel })
|
||||
PropertyPaneToggle('showContactDetails', { label: strings.ShowContactDetailsFieldLabel }),
|
||||
PropertyPaneToggle('roundedCards', { label: strings.RoundedCardsFieldLabel }),
|
||||
PropertyPaneTextField('cardBackgroundColor', {
|
||||
label: strings.CardBackgroundColorFieldLabel,
|
||||
description: strings.ColorFieldDescription
|
||||
}),
|
||||
PropertyPaneTextField('cardAccentColor', {
|
||||
label: strings.CardAccentColorFieldLabel,
|
||||
description: strings.ColorFieldDescription
|
||||
})
|
||||
]
|
||||
}]
|
||||
}]
|
||||
|
||||
@@ -5,8 +5,18 @@ export interface IOrganizationChartProps {
|
||||
title: string;
|
||||
showPictures: boolean;
|
||||
showContactDetails: boolean;
|
||||
cardBackgroundColor: string;
|
||||
cardAccentColor: string;
|
||||
roundedCards: boolean;
|
||||
reportsLabel: string;
|
||||
collapseLabel: string;
|
||||
expandLabel: string;
|
||||
expandDetailsLabel: string;
|
||||
collapseDetailsLabel: string;
|
||||
departmentLabel: string;
|
||||
officeLabel: string;
|
||||
emailLabel: string;
|
||||
phoneLabel: string;
|
||||
profileLabel: string;
|
||||
truncatedLabel: string;
|
||||
}
|
||||
|
||||
@@ -32,22 +32,30 @@
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, .12);
|
||||
color: "[theme:bodyText, default:#323130]";
|
||||
display: block;
|
||||
border-bottom: 2px solid "[theme:themePrimary, default:#0078d4]";
|
||||
min-height: 92px;
|
||||
overflow: hidden;
|
||||
text-align: left;
|
||||
text-decoration: none;
|
||||
width: 230px;
|
||||
}
|
||||
.personCard:hover { border-color: "[theme:themePrimary, default:#0078d4]"; color: "[theme:bodyText, default:#323130]"; }
|
||||
.personCard:focus { outline: 2px solid "[theme:focusBorder, default:#605e5c]"; outline-offset: 1px; }
|
||||
.cardContent { align-items: center; display: flex; min-height: 92px; padding: 12px; position: relative; }
|
||||
.photoWrap { background: "[theme:themePrimary, default:#0078d4]"; border-radius: 50%; flex: 0 0 56px; height: 56px; margin-right: 12px; overflow: hidden; position: relative; }
|
||||
.personCard:hover { box-shadow: 0 2px 6px rgba(0, 0, 0, .18); color: "[theme:bodyText, default:#323130]"; }
|
||||
.cardContent { align-items: center; background: transparent; border: 0; color: inherit; cursor: pointer; display: flex; font-family: inherit; min-height: 92px; padding: 12px; position: relative; text-align: left; width: 100%; }
|
||||
.cardContent:hover { background: rgba(0, 0, 0, .03); }
|
||||
.cardContent:focus { outline: 2px solid "[theme:focusBorder, default:#605e5c]"; outline-offset: -2px; }
|
||||
.photoWrap { background: "[theme:themePrimary, default:#0078d4]"; border-radius: 50%; flex: 0 0 56px; height: 56px; margin-left: 12px; overflow: hidden; position: relative; }
|
||||
.initials { color: #fff; font-size: 18px; left: 0; line-height: 56px; position: absolute; right: 0; text-align: center; }
|
||||
.photo { height: 56px; object-fit: cover; position: relative; width: 56px; z-index: 1; }
|
||||
.personText { display: block; min-width: 0; padding-bottom: 16px; }
|
||||
.personText { display: block; flex: 1 1 auto; min-width: 0; }
|
||||
.name { display: block; font-size: 15px; font-weight: 600; line-height: 20px; }
|
||||
.jobTitle, .department, .email, .location { color: "[theme:neutralSecondary, default:#605e5c]"; display: block; font-size: 12px; line-height: 17px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.jobTitle, .department, .email, .location, .phone, .reportCount { color: "[theme:neutralSecondary, default:#605e5c]"; display: block; font-size: 12px; line-height: 17px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.email { color: "[theme:themePrimary, default:#0078d4]"; }
|
||||
.reportCount { bottom: 8px; color: "[theme:neutralSecondary, default:#605e5c]"; font-size: 11px; position: absolute; right: 10px; }
|
||||
.detailsChevron { bottom: 3px; color: "[theme:neutralSecondary, default:#605e5c]"; font-family: 'FabricMDL2Icons'; font-size: 9px; left: 50%; position: absolute; }
|
||||
.personDetails { border-top: 1px solid "[theme:neutralLight, default:#edebe9]"; padding: 8px 10px 10px; text-align: left; }
|
||||
.detailRow { margin-bottom: 5px; min-width: 0; }
|
||||
.detailLabel { color: "[theme:neutralSecondary, default:#605e5c]"; display: block; font-size: 10px; line-height: 13px; }
|
||||
.profileLink { color: "[theme:themePrimary, default:#0078d4]"; display: inline-block; font-size: 12px; margin-top: 3px; text-decoration: none; }
|
||||
.profileLink:hover { text-decoration: underline; }
|
||||
.toggle { align-items: center; background: "[theme:white, default:#ffffff]"; border: 1px solid "[theme:neutralTertiary, default:#a19f9d]"; border-radius: 50%; bottom: -14px; color: "[theme:themePrimary, default:#0078d4]"; cursor: pointer; display: flex; height: 28px; justify-content: center; left: calc(50% - 14px); padding: 0; position: absolute; width: 28px; z-index: 2; }
|
||||
.toggle:hover, .toggle:focus { background: "[theme:neutralLighter, default:#f3f2f1]"; outline: none; }
|
||||
.toggleIcon { font-family: 'FabricMDL2Icons'; font-size: 12px; line-height: 1; }
|
||||
@@ -57,7 +65,7 @@
|
||||
.compactTree .tree li, .compactTree li { padding-left: 5px; padding-right: 5px; }
|
||||
.compactTree .personCard, .compactTree .cardContent { min-height: 82px; }
|
||||
.compactTree .cardContent { padding: 9px; }
|
||||
.compactTree .photoWrap { flex-basis: 44px; height: 44px; margin-right: 9px; }
|
||||
.compactTree .photoWrap { flex-basis: 44px; height: 44px; margin-left: 9px; }
|
||||
.compactTree .initials { font-size: 15px; line-height: 44px; }
|
||||
.compactTree .photo { height: 44px; width: 44px; }
|
||||
.compactTree .name { font-size: 14px; line-height: 18px; }
|
||||
@@ -66,13 +74,11 @@
|
||||
.denseTree .tree li, .denseTree li { padding-left: 3px; padding-right: 3px; }
|
||||
.denseTree .personCard, .denseTree .cardContent { min-height: 72px; }
|
||||
.denseTree .cardContent { padding: 7px; }
|
||||
.denseTree .photoWrap { flex-basis: 36px; height: 36px; margin-right: 7px; }
|
||||
.denseTree .photoWrap { flex-basis: 36px; height: 36px; margin-left: 7px; }
|
||||
.denseTree .initials { font-size: 13px; line-height: 36px; }
|
||||
.denseTree .photo { height: 36px; width: 36px; }
|
||||
.denseTree .personText { padding-bottom: 13px; }
|
||||
.denseTree .name { font-size: 13px; line-height: 17px; }
|
||||
.denseTree .jobTitle, .denseTree .department, .denseTree .email, .denseTree .location { font-size: 10px; line-height: 14px; }
|
||||
.denseTree .reportCount { bottom: 6px; font-size: 10px; right: 7px; }
|
||||
.denseTree .jobTitle, .denseTree .department, .denseTree .email, .denseTree .location, .denseTree .phone, .denseTree .reportCount { font-size: 10px; line-height: 14px; }
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.tree { display: block; min-width: 0; width: 100%; }
|
||||
|
||||
@@ -5,15 +5,17 @@ import { IOrganizationChartProps } from './IOrganizationChartProps';
|
||||
|
||||
export interface IOrganizationChartState {
|
||||
collapsed: { [account: string]: boolean };
|
||||
expandedDetails: { [account: string]: boolean };
|
||||
cardWidth: number;
|
||||
}
|
||||
|
||||
export default class OrganizationChart extends React.Component<IOrganizationChartProps, IOrganizationChartState> {
|
||||
private viewportElement: HTMLDivElement;
|
||||
private effectiveCardWidth: number = 230;
|
||||
|
||||
public constructor(props: IOrganizationChartProps) {
|
||||
super(props);
|
||||
this.state = { collapsed: {}, cardWidth: 230 };
|
||||
this.state = { collapsed: {}, expandedDetails: {}, cardWidth: 230 };
|
||||
}
|
||||
|
||||
public componentDidMount(): void {
|
||||
@@ -30,9 +32,11 @@ export default class OrganizationChart extends React.Component<IOrganizationChar
|
||||
}
|
||||
|
||||
public render(): React.ReactElement<IOrganizationChartProps> {
|
||||
const densityClass: string = this.state.cardWidth <= 170
|
||||
const effectiveCardWidth: number = this.getEffectiveCardWidth();
|
||||
this.effectiveCardWidth = effectiveCardWidth;
|
||||
const densityClass: string = effectiveCardWidth <= 170
|
||||
? styles.denseTree
|
||||
: (this.state.cardWidth <= 205 ? styles.compactTree : '');
|
||||
: (effectiveCardWidth <= 205 ? styles.compactTree : '');
|
||||
const treeClassName: string = styles.tree + (densityClass ? ' ' + densityClass : '');
|
||||
return (
|
||||
<section className={styles.organizationChart} aria-label={this.props.title}>
|
||||
@@ -77,28 +81,65 @@ export default class OrganizationChart extends React.Component<IOrganizationChar
|
||||
}
|
||||
|
||||
private renderPerson(profile: IUserProfile): JSX.Element {
|
||||
const key: string = profile.accountName.toLowerCase();
|
||||
const detailsExpanded: boolean = this.state.expandedDetails[key] === true;
|
||||
const cardStyle: React.CSSProperties = { width: this.effectiveCardWidth };
|
||||
const backgroundColor: string = this.normalizeColor(this.props.cardBackgroundColor);
|
||||
const accentColor: string = this.normalizeColor(this.props.cardAccentColor);
|
||||
if (backgroundColor) { cardStyle.backgroundColor = backgroundColor; }
|
||||
if (accentColor) { cardStyle.borderBottomColor = accentColor; }
|
||||
if (this.props.roundedCards) { cardStyle.borderRadius = '4px'; }
|
||||
const content: JSX.Element = (
|
||||
<span className={styles.cardContent}>
|
||||
<button type='button' className={styles.cardContent} aria-expanded={detailsExpanded}
|
||||
aria-label={(detailsExpanded ? this.props.collapseDetailsLabel : this.props.expandDetailsLabel) + ': ' +
|
||||
profile.displayName}
|
||||
onClick={(): void => this.toggleDetails(key)}>
|
||||
<span className={styles.personText}>
|
||||
<span className={styles.name}>{profile.displayName}</span>
|
||||
{profile.title && <span className={styles.jobTitle}>{profile.title}</span>}
|
||||
</span>
|
||||
{this.props.showPictures && <span className={styles.photoWrap}>
|
||||
<span className={styles.initials} aria-hidden='true'>{this.initials(profile.displayName)}</span>
|
||||
{profile.pictureUrl && <img className={styles.photo} src={profile.pictureUrl} alt='' role='presentation'
|
||||
onError={(event: React.SyntheticEvent<HTMLImageElement>): void => this.hideBrokenImage(event)} />}
|
||||
</span>}
|
||||
<span className={styles.personText}>
|
||||
<span className={styles.name}>{profile.displayName}</span>
|
||||
{profile.title && <span className={styles.jobTitle}>{profile.title}</span>}
|
||||
{profile.department && <span className={styles.department}>{profile.department}</span>}
|
||||
{this.props.showContactDetails && profile.email && <span className={styles.email}>{profile.email}</span>}
|
||||
{this.props.showContactDetails && (profile.office || profile.location) &&
|
||||
<span className={styles.location}>{profile.office || profile.location}</span>}
|
||||
<span className={styles.detailsChevron} aria-hidden='true'>
|
||||
{detailsExpanded ? <span></span> : <span></span>}
|
||||
</span>
|
||||
{profile.directReports.length > 0 &&
|
||||
<span className={styles.reportCount}>{profile.directReports.length + ' ' + this.props.reportsLabel}</span>}
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
return (
|
||||
<div className={styles.personCard} style={cardStyle}>
|
||||
{content}
|
||||
{detailsExpanded && this.renderDetails(profile)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private renderDetails(profile: IUserProfile): JSX.Element {
|
||||
return (
|
||||
<div className={styles.personDetails} role='group'>
|
||||
{profile.department && this.renderDetail(this.props.departmentLabel, profile.department, styles.department)}
|
||||
{this.props.showContactDetails && (profile.office || profile.location) &&
|
||||
this.renderDetail(this.props.officeLabel, profile.office || profile.location, styles.location)}
|
||||
{this.props.showContactDetails && profile.email &&
|
||||
this.renderDetail(this.props.emailLabel, profile.email, styles.email)}
|
||||
{this.props.showContactDetails && (profile.workPhone || profile.cellPhone) &&
|
||||
this.renderDetail(this.props.phoneLabel, profile.workPhone || profile.cellPhone, styles.phone)}
|
||||
{profile.directReports.length > 0 &&
|
||||
this.renderDetail(this.props.reportsLabel, String(profile.directReports.length), styles.reportCount)}
|
||||
{profile.profileUrl && <a className={styles.profileLink} href={profile.profileUrl}>{this.props.profileLabel}</a>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private renderDetail(label: string, value: string, className: string): JSX.Element {
|
||||
return (
|
||||
<div className={styles.detailRow}>
|
||||
<span className={styles.detailLabel}>{label}</span>
|
||||
<span className={className} title={value}>{value}</span>
|
||||
</div>
|
||||
);
|
||||
return profile.profileUrl
|
||||
? <a className={styles.personCard} style={{ width: this.state.cardWidth }} href={profile.profileUrl}>{content}</a>
|
||||
: <div className={styles.personCard} style={{ width: this.state.cardWidth }}>{content}</div>;
|
||||
}
|
||||
|
||||
private onWindowResize = (): void => {
|
||||
@@ -117,7 +158,7 @@ export default class OrganizationChart extends React.Component<IOrganizationChar
|
||||
const maximumBreadth: number = this.getMaximumVisibleBreadth();
|
||||
const horizontalSpacePerCard: number = 16;
|
||||
cardWidth = Math.floor((availableWidth - (maximumBreadth * horizontalSpacePerCard)) / maximumBreadth);
|
||||
cardWidth = Math.max(144, Math.min(230, cardWidth));
|
||||
cardWidth = Math.max(144, Math.min(this.getBreadthCardWidth(maximumBreadth), cardWidth));
|
||||
}
|
||||
|
||||
if (cardWidth !== this.state.cardWidth) {
|
||||
@@ -125,6 +166,36 @@ export default class OrganizationChart extends React.Component<IOrganizationChar
|
||||
}
|
||||
}
|
||||
|
||||
private getEffectiveCardWidth(): number {
|
||||
if (this.viewportElement && this.viewportElement.clientWidth <= 600) {
|
||||
return this.state.cardWidth;
|
||||
}
|
||||
return Math.min(this.state.cardWidth, this.getBreadthCardWidth(this.getMaximumVisibleBreadth()));
|
||||
}
|
||||
|
||||
private getBreadthCardWidth(maximumBreadth: number): number {
|
||||
if (maximumBreadth <= 2) { return 230; }
|
||||
if (maximumBreadth === 3) { return 205; }
|
||||
if (maximumBreadth === 4) { return 180; }
|
||||
if (maximumBreadth === 5) { return 164; }
|
||||
return 144;
|
||||
}
|
||||
|
||||
private toggleDetails(key: string): void {
|
||||
const expandedDetails: { [account: string]: boolean } = {};
|
||||
const existingKeys: string[] = Object.keys(this.state.expandedDetails);
|
||||
for (let i: number = 0; i < existingKeys.length; i++) {
|
||||
expandedDetails[existingKeys[i]] = this.state.expandedDetails[existingKeys[i]];
|
||||
}
|
||||
expandedDetails[key] = !expandedDetails[key];
|
||||
this.setState({ expandedDetails: expandedDetails });
|
||||
}
|
||||
|
||||
private normalizeColor(value: string): string {
|
||||
const candidate: string = String(value || '').trim();
|
||||
return /^#(?:[0-9a-f]{3}|[0-9a-f]{6})$/i.test(candidate) ? candidate : '';
|
||||
}
|
||||
|
||||
private getMaximumVisibleBreadth(): number {
|
||||
const countsByLevel: number[] = [];
|
||||
this.countVisibleNodes(this.props.data.root, 0, countsByLevel);
|
||||
|
||||
@@ -16,6 +16,10 @@ define([], function() {
|
||||
CacheMinutesFieldLabel: 'Profilcache (Minuten, 0 = aus)',
|
||||
ShowPicturesFieldLabel: 'Profilbilder anzeigen',
|
||||
ShowContactDetailsFieldLabel: 'Kontaktinformationen anzeigen',
|
||||
RoundedCardsFieldLabel: 'Abgerundete Karten',
|
||||
CardBackgroundColorFieldLabel: 'Hintergrundfarbe der Karten',
|
||||
CardAccentColorFieldLabel: 'Akzentfarbe der Karten',
|
||||
ColorFieldDescription: 'Optionale Hexadezimalfarbe, zum Beispiel #ffffff.',
|
||||
ConfigureMessage: 'Es konnte keine Startperson bestimmt werden.',
|
||||
LoadingMessage: 'Organigramm wird geladen…',
|
||||
EmptyMessage: 'Für die Startperson wurden keine Profildaten gefunden.',
|
||||
@@ -24,6 +28,13 @@ define([], function() {
|
||||
ReportsLabel: 'Mitarbeiter',
|
||||
CollapseLabel: 'Untergeordnete Personen zuklappen',
|
||||
ExpandLabel: 'Untergeordnete Personen aufklappen',
|
||||
ExpandDetailsLabel: 'Personendetails anzeigen',
|
||||
CollapseDetailsLabel: 'Personendetails ausblenden',
|
||||
DepartmentLabel: 'Abteilung',
|
||||
OfficeLabel: 'Büro / Standort',
|
||||
EmailLabel: 'E-Mail',
|
||||
PhoneLabel: 'Telefon',
|
||||
ProfileLabel: 'SharePoint-Profil öffnen',
|
||||
TruncatedLabel: 'Das Organigramm wurde durch die konfigurierte Ebenen- oder Personengrenze gekürzt.',
|
||||
PeopleCountLabel: '{0} Personen'
|
||||
};
|
||||
|
||||
@@ -16,6 +16,10 @@ define([], function() {
|
||||
CacheMinutesFieldLabel: 'Profile cache (minutes, 0 = off)',
|
||||
ShowPicturesFieldLabel: 'Show profile pictures',
|
||||
ShowContactDetailsFieldLabel: 'Show contact information',
|
||||
RoundedCardsFieldLabel: 'Rounded cards',
|
||||
CardBackgroundColorFieldLabel: 'Card background color',
|
||||
CardAccentColorFieldLabel: 'Card accent color',
|
||||
ColorFieldDescription: 'Optional hexadecimal color, for example #ffffff.',
|
||||
ConfigureMessage: 'A starting person could not be determined.',
|
||||
LoadingMessage: 'Loading organization chart…',
|
||||
EmptyMessage: 'No profile data was found for the starting person.',
|
||||
@@ -24,6 +28,13 @@ define([], function() {
|
||||
ReportsLabel: 'reports',
|
||||
CollapseLabel: 'Collapse reports',
|
||||
ExpandLabel: 'Expand reports',
|
||||
ExpandDetailsLabel: 'Show person details',
|
||||
CollapseDetailsLabel: 'Hide person details',
|
||||
DepartmentLabel: 'Department',
|
||||
OfficeLabel: 'Office / location',
|
||||
EmailLabel: 'Email',
|
||||
PhoneLabel: 'Phone',
|
||||
ProfileLabel: 'Open SharePoint profile',
|
||||
TruncatedLabel: 'The organization chart was limited by the configured depth or person count.',
|
||||
PeopleCountLabel: '{0} people'
|
||||
};
|
||||
|
||||
@@ -15,6 +15,10 @@ declare interface IOrganizationChartWebPartStrings {
|
||||
CacheMinutesFieldLabel: string;
|
||||
ShowPicturesFieldLabel: string;
|
||||
ShowContactDetailsFieldLabel: string;
|
||||
RoundedCardsFieldLabel: string;
|
||||
CardBackgroundColorFieldLabel: string;
|
||||
CardAccentColorFieldLabel: string;
|
||||
ColorFieldDescription: string;
|
||||
ConfigureMessage: string;
|
||||
LoadingMessage: string;
|
||||
EmptyMessage: string;
|
||||
@@ -23,6 +27,13 @@ declare interface IOrganizationChartWebPartStrings {
|
||||
ReportsLabel: string;
|
||||
CollapseLabel: string;
|
||||
ExpandLabel: string;
|
||||
ExpandDetailsLabel: string;
|
||||
CollapseDetailsLabel: string;
|
||||
DepartmentLabel: string;
|
||||
OfficeLabel: string;
|
||||
EmailLabel: string;
|
||||
PhoneLabel: string;
|
||||
ProfileLabel: string;
|
||||
TruncatedLabel: string;
|
||||
PeopleCountLabel: string;
|
||||
}
|
||||
|
||||
@@ -47,9 +47,17 @@ assert(component.indexOf('peopleCountLabel') < 0 && component.indexOf(''
|
||||
component.indexOf('') >= 0, 'Header count removal or modern toggle icons are missing.');
|
||||
assert(styles.indexOf('.tree li:only-child { padding-top: 0; }') >= 0,
|
||||
'Single-child connector spacing must remain continuous.');
|
||||
assert(component.indexOf('getMaximumVisibleBreadth') >= 0 && component.indexOf('cardWidth') >= 0 &&
|
||||
assert(component.indexOf('getMaximumVisibleBreadth') >= 0 && component.indexOf('getBreadthCardWidth') >= 0 &&
|
||||
component.indexOf('maximumBreadth === 4') >= 0 && component.indexOf('cardWidth') >= 0 &&
|
||||
styles.indexOf('.compactTree') >= 0 && styles.indexOf('.denseTree') >= 0,
|
||||
'Responsive organization-chart card sizing is missing.');
|
||||
assert(component.indexOf('expandedDetails') >= 0 && component.indexOf('toggleDetails') >= 0 &&
|
||||
component.indexOf('renderDetails') >= 0 && component.indexOf("className={styles.cardContent}") >= 0,
|
||||
'Teams-style expandable person cards are missing.');
|
||||
assert(webPart.indexOf("PropertyPaneTextField('cardBackgroundColor'") >= 0 &&
|
||||
webPart.indexOf("PropertyPaneTextField('cardAccentColor'") >= 0 &&
|
||||
webPart.indexOf("PropertyPaneToggle('roundedCards'") >= 0,
|
||||
'Configurable card design options are missing.');
|
||||
assert(component.indexOf("role='presentation'") >= 0, 'Decorative profile images must be hidden from assistive technology.');
|
||||
['bodyText', 'neutralSecondary', 'neutralLighter', 'themePrimary', 'neutralTertiary', 'white', 'neutralLight', 'focusBorder', 'errorText']
|
||||
.forEach(function(slot) {
|
||||
|
||||
Reference in New Issue
Block a user