-
Notifications
You must be signed in to change notification settings - Fork 89
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Addition of a script to facilitate the creation of the Material Desig…
…n icons (either in React component or SVG form or both). This commit also includes documentation added to the developer guide and separate documentation describing how to use the script, how to create icons manually if required and how to use the icons in either React or the templates.
- Loading branch information
Showing
6 changed files
with
339 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
# Data Commons Material UI Icon Generation Tool | ||
|
||
## Produce via script | ||
|
||
A python script is provided to quickly and easily generate a React component | ||
that displays a Material UI Icon, an SVG of a Material UI icon for use in the | ||
Jinja templates or both. | ||
|
||
### Script Usage | ||
|
||
To generate both a React component and an SVG of a Material UI icon, run the | ||
following command: | ||
|
||
```bash | ||
python3 generate_icon.py {icon_name} | ||
```` | ||
|
||
For example: | ||
|
||
```bash | ||
python3 generate_icon.py arrow_forward | ||
```` | ||
This will download the SVG from the Material UI repository, process it and | ||
generate two files: | ||
- An SVG for use in Jinja templates in the `server/templates/resources/icons` | ||
directory. | ||
- A React component in `static/js/components/elements/icons`. | ||
To generate only the SVG for the templates, run either of the following commands: | ||
```bash | ||
python3 generate_icon.py -f arrow_forward | ||
python3 generate_icon.py --flask arrow_forward | ||
``` | ||
To generate only the React component run either of the following commands: | ||
```bash | ||
python3 generate_icon.py -r arrow_forward | ||
python3 generate_icon.py --react arrow_forward | ||
``` | ||
It is recommended to run the prettier on the generated `.tsx` file. | ||
## Produce manually | ||
If for any reason you need to generate an icon manually (for example, if an icon | ||
is not available from the repository via the script), you can do so with these | ||
directions. Note that this should only rarely be required. | ||
1. Download the SVG. If it is a Material UI font, it will likely come from | ||
https://fonts.google.com/icons | ||
2. Change the height to "1em". Change the fill to "currentColor". Remove the width. | ||
3. For use in the Jinja templates, copy this SVG into `server/templates/resources/icons`. | ||
4. For use as a React component | ||
1. open an existing component in `static/js/components/elements/icons`, and save it | ||
with the new icon's name. | ||
2. Update the source to indicate the source of the SVG you are using. This might | ||
be https://fonts.google.com/icons. | ||
3. Update the name in the comments. | ||
4. Paste the SVG over top of the old SVG. | ||
5. Add {...props} as the final prop in the SVG before the `>`. | ||
## Usage | ||
### Jinja Templates | ||
To use a generated icon in a Jinja template | ||
``` | ||
{% from 'macros/icons.html' import inline_svg %} | ||
{{ inline_svg('arrow_forward') }} | ||
``` | ||
### React | ||
You can use the React component directly inside the JSX: | ||
```jsx | ||
<ArrowForward /> | ||
``` | ||
If done so without explicitly providing color and size, the icon | ||
will inherit its color and size from the enclosing CSS, just as a | ||
font icon would. For example: | ||
```css | ||
span.big-red-icon { | ||
font-size: 50px; | ||
color: red; | ||
} | ||
``` | ||
```jsx | ||
<span className="big-red-icon"> | ||
<ArrowForward /> | ||
</span> | ||
``` | ||
This allows you to use the React component icons much as you would the Material | ||
Icons provided through Google Fonts, with the added advantage that they are inline | ||
and not subject to flashes of unstyled content. | ||
You can also style the icons directly with its props. Each added icon | ||
component can take any prop that you can send into an SVG. | ||
```jsx | ||
<ArrowForward | ||
fill="red" | ||
height="50px" | ||
/> | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
/** | ||
* Copyright {{ year }} Google LLC | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
/* | ||
* Autogenerated by generate_icon.py | ||
*/ | ||
|
||
/* | ||
* Material Icon: {{ icon name }} | ||
* Source: https://github.com/google/material-design-icons | ||
*/ | ||
|
||
import React, { ReactElement } from "react"; | ||
|
||
export const {{ icon component name }} = ( | ||
props: React.SVGProps<SVGSVGElement> | ||
): ReactElement => ( | ||
{{ svg }} | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,181 @@ | ||
# Copyright 2024 Google LLC | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
|
||
# This script imports a Material Design icon by name in snake_case. (e.g. chevron_left). | ||
# The name of the icon is provided as a parameter. | ||
# With the -r or --react flag, only the React component will be created. | ||
# With the -f or --flask flag, the version for use in the Jinja templates will be created | ||
# With no flag given, both will be created. | ||
|
||
import argparse | ||
from datetime import datetime | ||
import os | ||
import sys | ||
import xml.etree.ElementTree as ET | ||
|
||
import requests | ||
|
||
|
||
def parse_arguments(): | ||
parser = argparse.ArgumentParser( | ||
description= | ||
'Download and generate Material Design icons for use in Jinja templates and as React components.' | ||
) | ||
parser.add_argument( | ||
'icon_name', | ||
type=str, | ||
help='Name of the icon in snake_case (e.g., chevron_left)') | ||
parser.add_argument('-r', | ||
'--react', | ||
action='store_true', | ||
help='Generate only the React component version') | ||
parser.add_argument( | ||
'-f', | ||
'--flask', | ||
action='store_true', | ||
help='Generate only the SVG version for use in Jinja templates') | ||
return parser.parse_args() | ||
|
||
|
||
def convert_snake_to_camel(icon_name): | ||
components = icon_name.split('_') | ||
return ''.join(x.capitalize() for x in components) | ||
|
||
|
||
def convert_snake_to_title(icon_name): | ||
components = icon_name.split('_') | ||
return ' '.join(x.capitalize() for x in components) | ||
|
||
|
||
def download_svg(icon_name): | ||
""" | ||
Downloads the requested SVG from the Material Design icon repository. | ||
""" | ||
base_url = 'https://raw.githubusercontent.com/google/material-design-icons/refs/heads/master/symbols/web' | ||
svg_url = f'{base_url}/{icon_name}/materialsymbolsoutlined/{icon_name}_24px.svg' | ||
|
||
print(f'Downloading SVG from: {svg_url}') | ||
response = requests.get(svg_url) | ||
if response.status_code == 200: | ||
print('SVG download complete.') | ||
return response.text | ||
else: | ||
print(f'Error: Failed to download SVG. Status Code: {response.status_code}') | ||
return None | ||
|
||
|
||
def process_svg(svg_content): | ||
""" | ||
Processes the SVG content to prepare to allow it to be styled through CSS similar to how a font is | ||
""" | ||
|
||
ET.register_namespace('', "http://www.w3.org/2000/svg") | ||
|
||
try: | ||
root = ET.fromstring(svg_content) | ||
except ET.ParseError as e: | ||
print(f'Error parsing SVG: {e}') | ||
return None | ||
|
||
if 'width' in root.attrib: | ||
del root.attrib['width'] | ||
|
||
root.set('height', '1em') | ||
|
||
root.set('fill', 'currentColor') | ||
|
||
for elem in root.iter(): | ||
if 'fill' in elem.attrib: | ||
elem.set('fill', 'currentColor') | ||
|
||
processed_svg = ET.tostring(root, encoding='unicode') | ||
return processed_svg | ||
|
||
|
||
def save_svg(svg_content, output_path): | ||
os.makedirs(os.path.dirname(output_path), exist_ok=True) | ||
with open(output_path, 'w', encoding='utf-8') as f: | ||
f.write(svg_content) | ||
print(f'Saved SVG to {output_path}') | ||
|
||
|
||
def generate_react_component(icon_name, svg_content, react_dir, template_path): | ||
""" | ||
Generates a React .tsx component for the icon based on the template found in "component_template.txt" | ||
""" | ||
component_name = convert_snake_to_camel(icon_name) | ||
icon_title = convert_snake_to_title(icon_name) | ||
current_year = datetime.now().year | ||
try: | ||
with open(template_path, 'r', encoding='utf-8') as template_file: | ||
template = template_file.read() | ||
except FileNotFoundError: | ||
print(f'Error: Template file not found at {template_path}.') | ||
return | ||
|
||
svg_tag_start = svg_content.find('<svg') | ||
svg_tag_end = svg_content.find('>', svg_tag_start) | ||
|
||
# We need to add {...props} so the props of the SVG can be overridden by the component. | ||
svg_with_props = (svg_content[:svg_tag_end] + ' {...props}' + | ||
svg_content[svg_tag_end:]) | ||
|
||
component = template.replace('{{ year }}', str(current_year)) | ||
component = component.replace('{{ icon name }}', icon_title) | ||
component = component.replace('{{ icon component name }}', component_name) | ||
component = component.replace('{{ svg }}', svg_with_props) | ||
|
||
component_file_name = f'{icon_name}.tsx' | ||
component_path = os.path.join(react_dir, component_file_name) | ||
|
||
with open(component_path, 'w', encoding='utf-8') as f: | ||
f.write(component) | ||
print(f'Generated React component at {component_path}') | ||
|
||
|
||
def main(): | ||
args = parse_arguments() | ||
icon_name = args.icon_name.lower() | ||
generate_react_svg = args.react or not (args.react or args.flask) | ||
generate_flask_svg = args.flask or not (args.react or args.flask) | ||
|
||
script_dir = os.path.dirname(os.path.abspath(__file__)) | ||
root_dir = os.path.abspath(os.path.join(script_dir, '..', '..')) | ||
|
||
html_icons_dir = os.path.join(root_dir, 'server', 'templates', 'resources', | ||
'icons') | ||
react_icons_dir = os.path.join(root_dir, 'static', 'js', 'components', | ||
'elements', 'icons') | ||
|
||
template_path = os.path.join(script_dir, 'component_template.txt') | ||
|
||
svg_content = download_svg(icon_name) | ||
if not svg_content: | ||
sys.exit(1) | ||
|
||
processed_svg = process_svg(svg_content) | ||
if not processed_svg: | ||
sys.exit(1) | ||
|
||
if generate_flask_svg: | ||
html_svg_path = os.path.join(html_icons_dir, f'{icon_name}.svg') | ||
save_svg(processed_svg, html_svg_path) | ||
|
||
if generate_react_svg: | ||
generate_react_component(icon_name, processed_svg, react_icons_dir, | ||
template_path) | ||
|
||
|
||
if __name__ == '__main__': | ||
main() |