mirror of
https://github.com/estruyf/vscode-front-matter.git
synced 2026-08-07 09:23:00 +02:00
#159 - SEO enhancements
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
### 🎨 Enhancements
|
||||
|
||||
- [#158](https://github.com/estruyf/vscode-front-matter/issues/158): Add support for non-boolean draft/publish status fields
|
||||
- [#159](https://github.com/estruyf/vscode-front-matter/issues/159): Enhancements to SEO checks: Slug check, keyword details, more article information
|
||||
|
||||
### 🐞 Fixes
|
||||
|
||||
|
||||
+20
-1
@@ -356,8 +356,18 @@
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.table__cell__seo_details {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.table__cell__validation {
|
||||
text-align: center;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.table__cell__validation div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 2px 0;
|
||||
}
|
||||
|
||||
.table__cell__validation .valid {
|
||||
@@ -368,6 +378,15 @@
|
||||
color: #E6AF2E;
|
||||
}
|
||||
|
||||
.table__cell__validation div span + span {
|
||||
margin-left: .5rem;
|
||||
}
|
||||
|
||||
.seo__status__note {
|
||||
font-size: 10px;
|
||||
padding: 3px 0;
|
||||
}
|
||||
|
||||
/* Fields */
|
||||
.field__toggle {
|
||||
position: relative;
|
||||
|
||||
@@ -91,6 +91,12 @@
|
||||
"configuration": {
|
||||
"title": "Front Matter: use frontmatter.json for shared team settings",
|
||||
"properties": {
|
||||
"frontMatter.site.baseURL": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"markdownDescription": "Specify the base URL of your site, this will be used for SEO checks. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.site.baseURL)",
|
||||
"scope": "Site"
|
||||
},
|
||||
"frontMatter.content.autoUpdateDate": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
@@ -494,6 +500,12 @@
|
||||
"markdownDescription": "Specifies the optimal description length for SEO (set to `-1` to turn it off). [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.taxonomy.seodescriptionlength)",
|
||||
"scope": "Taxonomy"
|
||||
},
|
||||
"frontMatter.taxonomy.seoSlugLength": {
|
||||
"type": "number",
|
||||
"default": 75,
|
||||
"markdownDescription": "Specifies the optimal slug length for SEO (set to `-1` to turn it off). [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.taxonomy.seoSlugLength)",
|
||||
"scope": "Taxonomy"
|
||||
},
|
||||
"frontMatter.taxonomy.seoTitleLength": {
|
||||
"type": "number",
|
||||
"default": 60,
|
||||
|
||||
@@ -20,7 +20,6 @@ export class StatusListener {
|
||||
const draft = ContentType.getDraftField();
|
||||
if (!draft || draft.type !== "boolean") {
|
||||
frontMatterSB.hide();
|
||||
return;
|
||||
}
|
||||
|
||||
let editor = vscode.window.activeTextEditor;
|
||||
|
||||
@@ -20,6 +20,7 @@ export const SETTING_REMOVE_QUOTES = "taxonomy.noPropertyValueQuotes";
|
||||
export const SETTING_FRONTMATTER_TYPE = "taxonomy.frontMatterType";
|
||||
|
||||
export const SETTING_SEO_TITLE_LENGTH = "taxonomy.seoTitleLength";
|
||||
export const SETTING_SEO_SLUG_LENGTH = "taxonomy.seoSlugLength";
|
||||
export const SETTING_SEO_DESCRIPTION_LENGTH = "taxonomy.seoDescriptionLength";
|
||||
export const SETTING_SEO_CONTENT_MIN_LENGTH = "taxonomy.seoContentLengh";
|
||||
export const SETTING_SEO_DESCRIPTION_FIELD = "taxonomy.seoDescriptionField";
|
||||
@@ -45,6 +46,8 @@ export const SETTINGS_DASHBOARD_MEDIA_SNIPPET = "dashboard.mediaSnippet";
|
||||
|
||||
export const SETTINGS_FRAMEWORK_ID = "framework.id";
|
||||
|
||||
export const SETTING_SITE_BASEURL = "site.baseURL";
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { DashboardData } from '../models/DashboardData';
|
||||
import { Template } from '../commands/Template';
|
||||
import { DefaultFields, SETTINGS_CONTENT_FRONTMATTER_HIGHLIGHT, SETTING_AUTO_UPDATE_DATE, SETTING_CUSTOM_SCRIPTS, SETTING_SEO_CONTENT_MIN_LENGTH, SETTING_SEO_DESCRIPTION_FIELD, SETTING_SLUG_UPDATE_FILE_NAME, SETTING_PREVIEW_HOST, SETTING_DATE_FORMAT, SETTING_COMMA_SEPARATED_FIELDS, SETTING_TAXONOMY_CONTENT_TYPES, SETTING_PANEL_FREEFORM, SETTING_SEO_DESCRIPTION_LENGTH, SETTING_SEO_TITLE_LENGTH, SETTING_SLUG_PREFIX, SETTING_SLUG_SUFFIX, SETTING_TAXONOMY_CATEGORIES, SETTING_TAXONOMY_TAGS, SETTINGS_CONTENT_DRAFT_FIELD } from '../constants';
|
||||
import { DefaultFields, SETTINGS_CONTENT_FRONTMATTER_HIGHLIGHT, SETTING_AUTO_UPDATE_DATE, SETTING_CUSTOM_SCRIPTS, SETTING_SEO_CONTENT_MIN_LENGTH, SETTING_SEO_DESCRIPTION_FIELD, SETTING_SLUG_UPDATE_FILE_NAME, SETTING_PREVIEW_HOST, SETTING_DATE_FORMAT, SETTING_COMMA_SEPARATED_FIELDS, SETTING_TAXONOMY_CONTENT_TYPES, SETTING_PANEL_FREEFORM, SETTING_SEO_DESCRIPTION_LENGTH, SETTING_SEO_TITLE_LENGTH, SETTING_SLUG_PREFIX, SETTING_SLUG_SUFFIX, SETTING_TAXONOMY_CATEGORIES, SETTING_TAXONOMY_TAGS, SETTINGS_CONTENT_DRAFT_FIELD, SETTING_SEO_SLUG_LENGTH, SETTING_SITE_BASEURL } from '../constants';
|
||||
import * as os from 'os';
|
||||
import { PanelSettings, CustomScript as ICustomScript } from '../models/PanelSettings';
|
||||
import { CancellationToken, Disposable, Uri, Webview, WebviewView, WebviewViewProvider, WebviewViewResolveContext, window, workspace, commands, env as vscodeEnv } from "vscode";
|
||||
@@ -12,7 +12,7 @@ import { TagType } from '../panelWebView/TagType';
|
||||
import { DraftField, TaxonomyType } from '../models';
|
||||
import { exec } from 'child_process';
|
||||
import { fromMarkdown } from 'mdast-util-from-markdown';
|
||||
import { Content } from 'mdast';
|
||||
import { Content, Root } from 'mdast';
|
||||
import { COMMAND_NAME } from '../constants/Extension';
|
||||
import { Folders } from '../commands/Folders';
|
||||
import { Preview } from '../commands/Preview';
|
||||
@@ -22,6 +22,7 @@ import { Extension } from '../helpers/Extension';
|
||||
import { Dashboard } from '../commands/Dashboard';
|
||||
import { ImageHelper } from '../helpers/ImageHelper';
|
||||
import { CustomScript } from '../helpers/CustomScript';
|
||||
import { Link, Parent, Text } from 'mdast-util-from-markdown/lib';
|
||||
|
||||
const FILE_LIMIT = 10;
|
||||
|
||||
@@ -380,6 +381,7 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
|
||||
data: {
|
||||
seo: {
|
||||
title: Settings.get(SETTING_SEO_TITLE_LENGTH) as number || -1,
|
||||
slug: Settings.get(SETTING_SEO_SLUG_LENGTH) as number || -1,
|
||||
description: Settings.get(SETTING_SEO_DESCRIPTION_LENGTH) as number || -1,
|
||||
content: Settings.get(SETTING_SEO_CONTENT_MIN_LENGTH) as number || -1,
|
||||
descriptionField: Settings.get(SETTING_SEO_DESCRIPTION_FIELD) as string || DefaultFields.Description
|
||||
@@ -476,6 +478,7 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
|
||||
* Get article details
|
||||
*/
|
||||
private getArticleDetails() {
|
||||
const baseUrl = Settings.get<string>(SETTING_SITE_BASEURL);
|
||||
const editor = window.activeTextEditor;
|
||||
if (!editor) {
|
||||
return null;
|
||||
@@ -492,13 +495,36 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
|
||||
content = content.replace(/({{(.*?)}})/g, ''); // remove hugo shortcodes
|
||||
|
||||
const mdTree = fromMarkdown(content);
|
||||
const headings = mdTree.children.filter(node => node.type === 'heading').length;
|
||||
const paragraphs = mdTree.children.filter(node => node.type === 'paragraph').length;
|
||||
const elms: Parent[] | Link[] = this.getAllElms(mdTree);
|
||||
|
||||
const headings = elms.filter(node => node.type === 'heading');
|
||||
const paragraphs = elms.filter(node => node.type === 'paragraph').length;
|
||||
const images = elms.filter(node => node.type === 'image').length;
|
||||
const links: string[] = elms.filter(node => node.type === 'link').map(node => (node as Link).url);
|
||||
|
||||
const internalLinks = links.filter(link => !link.startsWith('http') || (baseUrl && link.toLowerCase().includes((baseUrl || "").toLowerCase()))).length;
|
||||
let externalLinks = links.filter(link => link.startsWith('http'));
|
||||
if (baseUrl) {
|
||||
externalLinks = externalLinks.filter(link => !link.toLowerCase().includes(baseUrl.toLowerCase()));
|
||||
}
|
||||
|
||||
const headers = [];
|
||||
for (const header of headings) {
|
||||
const text = header?.children?.filter((node: any) => node.type === 'text').map((node: any) => node.value).join(" ");
|
||||
if (text) {
|
||||
headers.push(text);
|
||||
}
|
||||
}
|
||||
|
||||
const wordCount = this.wordCount(0, mdTree);
|
||||
|
||||
return {
|
||||
headings,
|
||||
headings: headings.length,
|
||||
headingsText: headers,
|
||||
paragraphs,
|
||||
images,
|
||||
internalLinks,
|
||||
externalLinks: externalLinks.length,
|
||||
wordCount,
|
||||
content: article.content
|
||||
};
|
||||
@@ -507,6 +533,21 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
|
||||
return null;
|
||||
}
|
||||
|
||||
private getAllElms(node: Content | any, allElms?: any[]): any[] {
|
||||
if (!allElms) {
|
||||
allElms = [];
|
||||
}
|
||||
|
||||
if (node.children?.length > 0) {
|
||||
for (const child of node.children) {
|
||||
allElms.push(Object.assign({}, child));
|
||||
this.getAllElms(child, allElms);
|
||||
}
|
||||
}
|
||||
|
||||
return allElms;
|
||||
}
|
||||
|
||||
private counts(acc: any, node: any) {
|
||||
// add 1 to an initial or existing value
|
||||
acc[node.type] = (acc[node.type] || 0) + 1;
|
||||
|
||||
+1
-1
@@ -126,7 +126,7 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
});
|
||||
|
||||
// Settings promotion command
|
||||
subscriptions.push(vscode.commands.registerCommand(COMMAND_NAME.promote, () => { console.log('promote'); SettingsHelper.promote(); }));
|
||||
subscriptions.push(vscode.commands.registerCommand(COMMAND_NAME.promote, SettingsHelper.promote ));
|
||||
|
||||
// Collapse all sections in the webview
|
||||
const collapseAll = vscode.commands.registerCommand(COMMAND_NAME.collapseSections, () => {
|
||||
|
||||
@@ -89,8 +89,6 @@ export class CustomScript {
|
||||
articleData = `'${articleData}'`;
|
||||
}
|
||||
|
||||
console.log(articleData);
|
||||
|
||||
exec(`${script.nodeBin || "node"} ${join(wsPath, script.script)} "${wsPath}" "${contentPath}" ${articleData}`, (error, stdout) => {
|
||||
if (error) {
|
||||
Notifications.error(`${script.title}: ${error.message}`);
|
||||
|
||||
@@ -45,6 +45,7 @@ export interface DateInfo {
|
||||
|
||||
export interface SEO {
|
||||
title: number;
|
||||
slug: number;
|
||||
description: number;
|
||||
content: number;
|
||||
descriptionField: string;
|
||||
|
||||
@@ -6,6 +6,9 @@ export interface IArticleDetailsProps {
|
||||
headings: number;
|
||||
paragraphs: number;
|
||||
wordCount: number;
|
||||
internalLinks: number;
|
||||
externalLinks: number;
|
||||
images: number;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +45,33 @@ const ArticleDetails: React.FunctionComponent<IArticleDetailsProps> = ({details}
|
||||
</VsTableRow>
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
details?.internalLinks !== undefined && (
|
||||
<VsTableRow>
|
||||
<VsTableCell>Internal links</VsTableCell>
|
||||
<VsTableCell>{details.internalLinks}</VsTableCell>
|
||||
</VsTableRow>
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
details?.externalLinks !== undefined && (
|
||||
<VsTableRow>
|
||||
<VsTableCell>External links</VsTableCell>
|
||||
<VsTableCell>{details.externalLinks}</VsTableCell>
|
||||
</VsTableRow>
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
details?.images !== undefined && (
|
||||
<VsTableRow>
|
||||
<VsTableCell>Images</VsTableCell>
|
||||
<VsTableCell>{details.images}</VsTableCell>
|
||||
</VsTableRow>
|
||||
)
|
||||
}
|
||||
</VsTableBody>
|
||||
</VsTable>
|
||||
</div>
|
||||
|
||||
@@ -15,7 +15,7 @@ const SeoFieldInfo: React.FunctionComponent<ISeoFieldInfoProps> = ({ title, valu
|
||||
<VsTableCell className={`table__cell table__title`}>{title}</VsTableCell>
|
||||
<VsTableCell className={`table__cell`}>{value}/{recommendation}</VsTableCell>
|
||||
<VsTableCell className={`table__cell table__cell__validation`}>
|
||||
{ isValid !== undefined ? <ValidInfo isValid={isValid} /> : <span>-</span> }
|
||||
{ isValid !== undefined ? <ValidInfo label={undefined} isValid={isValid} /> : <span>-</span> }
|
||||
</VsTableCell>
|
||||
</VsTableRow>
|
||||
);
|
||||
|
||||
@@ -8,9 +8,39 @@ export interface ISeoKeywordInfoProps {
|
||||
description: string;
|
||||
slug: string;
|
||||
content: string;
|
||||
wordCount?: number;
|
||||
headings?: string[];
|
||||
}
|
||||
|
||||
const SeoKeywordInfo: React.FunctionComponent<ISeoKeywordInfoProps> = ({keyword, title, description, slug, content}: React.PropsWithChildren<ISeoKeywordInfoProps>) => {
|
||||
const SeoKeywordInfo: React.FunctionComponent<ISeoKeywordInfoProps> = ({keyword, title, description, slug, content, wordCount, headings}: React.PropsWithChildren<ISeoKeywordInfoProps>) => {
|
||||
|
||||
const density = () => {
|
||||
if (!wordCount) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const pattern = new RegExp('\\b' + keyword.toLowerCase() + '\\b', 'ig');
|
||||
const count = (content.match(pattern) || []).length;
|
||||
const density = (count / wordCount) * 100;
|
||||
const densityTitle = `Keyword usage ${density.toFixed(2)}% *`;
|
||||
|
||||
if (density < 0.75) {
|
||||
return <ValidInfo label={densityTitle} isValid={false} />
|
||||
} else if (density >= 0.75 && density < 1.5) {
|
||||
return <ValidInfo label={densityTitle} isValid={true} />
|
||||
} else {
|
||||
return <ValidInfo label={densityTitle} isValid={false} />
|
||||
}
|
||||
};
|
||||
|
||||
const checkHeadings = () => {
|
||||
if (!headings || headings.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const exists = headings.filter(heading => heading.split(' ').findIndex(word => word.toLowerCase() === keyword.toLowerCase()) !== -1);
|
||||
return <ValidInfo label={`Used in heading(s)`} isValid={exists.length > 0} />;
|
||||
};
|
||||
|
||||
if (!keyword) {
|
||||
return null;
|
||||
@@ -19,17 +49,33 @@ const SeoKeywordInfo: React.FunctionComponent<ISeoKeywordInfoProps> = ({keyword,
|
||||
return (
|
||||
<VsTableRow>
|
||||
<VsTableCell className={`table__cell`}>{keyword}</VsTableCell>
|
||||
<VsTableCell className={`table__cell table__cell__validation`}>
|
||||
<ValidInfo isValid={!!title && title.toLowerCase().includes(keyword.toLowerCase())} />
|
||||
</VsTableCell>
|
||||
<VsTableCell className={`table__cell table__cell__validation`}>
|
||||
<ValidInfo isValid={!!description && description.toLowerCase().includes(keyword.toLowerCase())} />
|
||||
</VsTableCell>
|
||||
<VsTableCell className={`table__cell table__cell__validation`}>
|
||||
<ValidInfo isValid={!!slug && (slug.toLowerCase().includes(keyword.toLowerCase()) || slug.toLowerCase().includes(keyword.replace(/ /g, '-').toLowerCase()))} />
|
||||
</VsTableCell>
|
||||
<VsTableCell className={`table__cell table__cell__validation`}>
|
||||
<ValidInfo isValid={!!content && content.toLowerCase().includes(keyword.toLowerCase())} />
|
||||
<VsTableCell className={`table__cell table__cell__validation table__cell__seo_details`}>
|
||||
<div>
|
||||
<ValidInfo label={`Title`} isValid={!!title && title.toLowerCase().includes(keyword.toLowerCase())} />
|
||||
</div>
|
||||
<div>
|
||||
<ValidInfo label={`Description`} isValid={!!description && description.toLowerCase().includes(keyword.toLowerCase())} />
|
||||
</div>
|
||||
<div>
|
||||
<ValidInfo label={`Slug`} isValid={!!slug && (slug.toLowerCase().includes(keyword.toLowerCase()) || slug.toLowerCase().includes(keyword.replace(/ /g, '-').toLowerCase()))} />
|
||||
</div>
|
||||
<div>
|
||||
<ValidInfo label={`Content`} isValid={!!content && content.toLowerCase().includes(keyword.toLowerCase())} />
|
||||
</div>
|
||||
{
|
||||
headings && headings.length > 0 && (
|
||||
<div>
|
||||
{checkHeadings()}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
wordCount && (
|
||||
<div>
|
||||
{density()}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</VsTableCell>
|
||||
</VsTableRow>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import { SeoKeywordInfo } from './SeoKeywordInfo';
|
||||
import { VsTable, VsTableBody, VsTableCell, VsTableHeader, VsTableHeaderCell, VsTableRow } from './VscodeComponents';
|
||||
import { VsTable, VsTableBody, VsTableHeader, VsTableHeaderCell } from './VscodeComponents';
|
||||
|
||||
export interface ISeoKeywordsProps {
|
||||
keywords: string[] | null;
|
||||
@@ -9,6 +9,8 @@ export interface ISeoKeywordsProps {
|
||||
description: string;
|
||||
slug: string;
|
||||
content: string;
|
||||
headings?: string[];
|
||||
wordCount?: number;
|
||||
}
|
||||
|
||||
const SeoKeywords: React.FunctionComponent<ISeoKeywordsProps> = ({keywords, ...data}: React.PropsWithChildren<ISeoKeywordsProps>) => {
|
||||
@@ -37,13 +39,10 @@ const SeoKeywords: React.FunctionComponent<ISeoKeywordsProps> = ({keywords, ...d
|
||||
<div className={`seo__status__keywords`}>
|
||||
<h4>Keywords</h4>
|
||||
|
||||
<VsTable bordered>
|
||||
<VsTable bordered columns={["30%", "auto"]}>
|
||||
<VsTableHeader slot="header">
|
||||
<VsTableHeaderCell className={`table__cell`}>Keyword</VsTableHeaderCell>
|
||||
<VsTableHeaderCell className={`table__cell`}>Title</VsTableHeaderCell>
|
||||
<VsTableHeaderCell className={`table__cell`}>Description</VsTableHeaderCell>
|
||||
<VsTableHeaderCell className={`table__cell`}>Slug</VsTableHeaderCell>
|
||||
<VsTableHeaderCell className={`table__cell`}>Content</VsTableHeaderCell>
|
||||
<VsTableHeaderCell className={`table__cell`}>Details</VsTableHeaderCell>
|
||||
</VsTableHeader>
|
||||
<VsTableBody slot="body">
|
||||
{
|
||||
@@ -55,6 +54,14 @@ const SeoKeywords: React.FunctionComponent<ISeoKeywordsProps> = ({keywords, ...d
|
||||
}
|
||||
</VsTableBody>
|
||||
</VsTable>
|
||||
|
||||
{
|
||||
data.wordCount && (
|
||||
<div className={`seo__status__note`}>
|
||||
* A keyword density of 1-1.5% is sufficient in most cases.
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -13,7 +13,7 @@ export interface ISeoStatusProps {
|
||||
|
||||
const SeoStatus: React.FunctionComponent<ISeoStatusProps> = (props: React.PropsWithChildren<ISeoStatusProps>) => {
|
||||
const { data, seo } = props;
|
||||
const { title } = data;
|
||||
const { title, slug } = data;
|
||||
const [ isOpen, setIsOpen ] = React.useState(true);
|
||||
const tableRef = React.useRef<HTMLElement>();
|
||||
const pushUpdate = React.useRef((value: boolean) => {
|
||||
@@ -65,6 +65,11 @@ const SeoStatus: React.FunctionComponent<ISeoStatusProps> = (props: React.PropsW
|
||||
<SeoFieldInfo title={`title`} value={title.length} recommendation={`${seo.title} chars`} isValid={title.length <= seo.title} />
|
||||
)
|
||||
}
|
||||
{
|
||||
(slug && seo.slug > 0) && (
|
||||
<SeoFieldInfo title={`slug`} value={slug.length} recommendation={`${seo.slug} chars`} isValid={slug.length <= seo.slug} />
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
(data[descriptionField] && seo.description > 0) && (
|
||||
@@ -85,6 +90,8 @@ const SeoStatus: React.FunctionComponent<ISeoStatusProps> = (props: React.PropsW
|
||||
title={title}
|
||||
description={data[descriptionField]}
|
||||
slug={data.slug}
|
||||
headings={data?.articleDetails?.headingsText}
|
||||
wordCount={data?.articleDetails?.wordCount}
|
||||
content={data?.articleDetails?.content} />
|
||||
|
||||
<ArticleDetails details={data.articleDetails} />
|
||||
|
||||
@@ -3,10 +3,11 @@ import { CheckIcon } from './Icons/CheckIcon';
|
||||
import { WarningIcon } from './Icons/WarningIcon';
|
||||
|
||||
export interface IValidInfoProps {
|
||||
label?: string;
|
||||
isValid: boolean;
|
||||
}
|
||||
|
||||
const ValidInfo: React.FunctionComponent<IValidInfoProps> = ({isValid}: React.PropsWithChildren<IValidInfoProps>) => {
|
||||
const ValidInfo: React.FunctionComponent<IValidInfoProps> = ({label, isValid}: React.PropsWithChildren<IValidInfoProps>) => {
|
||||
return (
|
||||
<>
|
||||
{
|
||||
@@ -16,6 +17,7 @@ const ValidInfo: React.FunctionComponent<IValidInfoProps> = ({isValid}: React.Pr
|
||||
<span className="warning"><WarningIcon /></span>
|
||||
)
|
||||
}
|
||||
{ label && <span>{label}</span> }
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user